Sockets: Criando um httpd simples (Parte 3)

Minha implementação de rotinas de leitura com restrição de buffer é esta: Mantenho um buffer alocado dinamicamente com ponteiros para o início e um byte além do final do buffer e um ponteiro adicional apontando para o próxima posição vazia. Faço leituras em sequência até o buffer “acabar” e, se for o caso, envio uma resposta de erro, caso contrário, na leitura “bloqueada”, espero ocorrer o timeout para decidir que a leitura acabou… Para isso tenho que criar uma estrutura que chamarei de buffer object:

typedef
struct buffobj_s {
  char *begin, *end;
  char *current;
} buffobj_t;

E as rotinas de manipulação do objeto:

int buffobj_create(buffobj_t *bo, size_t size)
{
  char *p;

  if (!bo || !size) return -1;

  /* O buffer sempre tem 1 byte extra! */
  if (!(p = (char *)malloc(size+1)))
    return -1;

  bo->begin = bo->current = p;
  bo->end = p + size;

  return 0;
}

void buffobj_destroy(buffobj *bo)
{
  if (bo && bo->buffer)
  {
    free(bo->begin);
    bo->begin = bo->end = bo->current = NULL;
  }
}

/* Macros para pegar infos do buffobj_t */
#define buffobj_capacity(bo) \
  ((size_t)((bo)->end - (bo)->begin))

#define buffobj_length(bo) \
  ((size_t)((bo)->current - (bo)->begin))

#define buffobj_remaining(bo) \
  ((size_t)((bo)->end - (bo)->current))

#define buffobj_reset(bo) \
  { (bo)->current = (bo)->begin; }

Um buffer object pode assumir 4 estados: vazio e dealocado, vazio com n bytes alocados:

buffer_empty

Com n bytes alocados e m<n-1 bytes usados, e com n bytes alocados e cheio (m=n-1):

buffer_used

O byte extra do buffer existe porque podemos querer trabalhar com strings e colocar um ‘\0’ no final.

Um exemplo do uso dos buffers objects é a rotina de leitura do header de uma requisição (que é auto explicativa) fica assim:

/* Tempo de timeout para poll(). */
#define RECV_TIMEOUT 3000

/* Lê o header de uma requisição e o coloca no buffer
   object 'bo'. Se houverem bytes adicionais além da
   sequência de terminação do header ("\r\n\r\n"),
   copia para o buffer object 'bopl', do payload da
   requisição.

   Os buffers objects 'bo' e 'bopl' deverão ter sido
   criados antes de chamar essa função!

   Retorna -1 em caso de erro e 0 em caso de sucesso. 

   Ao retornar -1 errno conterá um dos erros possíveis
   de poll() ou recv(), mas dois outros erros podem ser
   retornados: ECANCELLED, se o requisitande fechou a
   conexão durante uma leitura, ou EBADMSG se o buffer
   object 'bo' ficar cheio e uma sequência de terminação
   não for encontrada. */
int RECV_REQ_HDR(int fd, 
                 buffobj_t *bo, buffobj_t *bopl)
{
  /* p é usada para encontrar a sequência de
     encerramento. */
  char *p = NULL;

  /* Zera os erros. */
  errno = 0;

  /* Enquanto tivermos bytes sobrando
     no buffer e enquanto não encontrarmos
     a sequência de finalização... */
  while (buffobj_remaining(bo) && !p)
  {
    /* bloqueio condicional com timeout. */
    int r;
poll_again:
    struct pollfd pfd = { .fd = fd, .evnets = POLLIN };
    r = poll(&pfd, 1, RECV_TIMEOUT);

    /* Ocorreu um erro? */
    if (r == -1)
    { 
      /* A syscall foi interrompida por um sinal?
         Executa de novo! */
      if (errno == EINTR)
        goto poll_again;

      /* Senão é erro mesmo! */
      return -1;
    }

    /* Ocorreu timeout? Marca o fim do buffer 
       e sai do loop. */
    if (!r)
    {
      *bo->current = '\0';
      break;
    }

    /* 'bytes' é usada para avançar o ponteiro
       corrente do buffer. */
    ssize_t bytes;

    /* Tentar ler para o espaço que resta do 
       buffer. */
recv_again:
    bytes = recv(fd, 
                 bo->current, 
                 buffobj_remaining(bo), 0);

    /* Ocorreu um erro? */
    if (bytes == -1)
    {
      /* A syscall foi interrompida por sinal?
         Executa de novo! */
      if (errno == EINTR)
        goto recv_again;

      /* Senão é erro mesmo! */
      return -1;
    }

    /* O requisitante fechou a conexão? */
    if (!bytes)
    {
      /* Seta errno e sai com erro. */
      errno = ECANCELLED; 
      return -1;
    }

    /* Atualiza o ponteiro corrente do buffer. */
    bo->current += bytes;

    /* Procura a sequência "\r\n\r\n"... */
    *bo->current = '\0';
    p = strstr(bo->begin, "\r\n\r\n");
  } /* fim do loop. */

  /* Se ainda não encontramos a sequência de
     encerramento, tenta encontrar de novo. 
     podemos ter saído do loop pq ele está cheio
     ou porque houve um timeout. */
  if (!p)
    p = (char *)strstr(bo->begin, "\r\n\r\n");

  /* Se não achou mesmo a sequência de finalização
     limpa o buffer e sai com erro. */
  if (!p)
  {
    buffobj_reset(bo);
    *bo->current = '\0'; /* só por garantia! */
    errno = EBADMSG;
    return -1;
  }

  /* Encontramos a sequência!
     Usa-a como fim de string. */
  *p = '\0';

  /* Neste ponto podemos ter algum byte no buffer
     que faz parte do payload da requisição. 

     Outra função lerá e processará o payload...
     Precisamos processar o header da requisição
     primeiro. Mas, antes, se bytes extras 
     existirem no buffer object, os copiamos para
     o buffer object do payload, externo a essa 
     função.

     Teremos que fazer o buffer object do payload maior
     ou de igual tamanho do que o buffer object da 
     requisição antes de chamarmos essa função, se não 
     quisermos tratar erros no bloco do if, abaixo. */
  p += 4;
  if ((bytes = bo->current - p) > 0)
  {
    memcpy(bopl->begin, p, bytes);
    bopl->current = bopl->begin + bytes;
  }

  return 0;
}
Anúncios