LINUX.ORG.RU

пишем web сервер


0

1

как принемать запрос?
если с GET все понятно - вызывать recv до тех пор пока в принятом пакете в конце не будет 0D 0A 0D 0A

то c POST все не так просто. получается нужно вызывать recv и после каждого вызова запускать поиск 0D 0A 0D 0A и в случае если нашли - парсить заголовок на предмет Content-Length и прикидывать сколько байт нам еще нужно дочитать?

однако «некоторые ошибочные реализации HTTP/1.0 клиентов генерируют дополнительные CRLF после запроса POST» что делать с этим? читать только то что должно быть прочитано согласно Content-Length и ни байтом больше (если оно уже не прочитано ранее)?

предложите какой-то другой алгоритм?

★★★★

сначала сделай по rfc, потом потести. Может уже и не осталось таких ущербных клиентов.

true_admin ★★★★★
()
Ответ на: комментарий от true_admin

делаю и тестю - конец пакета то явно не определен ни длинной (вначале и заранее) ни фактом закрытия сокета...

quest ★★★★
() автор топика
Ответ на: комментарий от Zhbert

>Зачем?
не важно, нужно, такая задача.

Возьми исходники апача или любого другого и посмотри.

смотрел, что-то типа того

quest ★★★★
() автор топика

Очевидно, что читать столько сколько написано в Content-Length, а потом закрывать соединение. Это же HTTP 1.0, тут проблем никаких не будет.

Reset ★★★★★
()
Ответ на: комментарий от Reset

>Очевидно, что читать столько сколько написано в Content-Length, а потом закрывать соединение.

Мне нужно еще выдать ответ прежде чем закрыть соединение.


Это же HTTP 1.0, тут проблем никаких не будет.


«Для совместимости с HTTP/1.0 приложениями HTTP/1.1 запросы, содержащие тело сообщения (message-body) ДОЛЖНЫ включать допустимое поле заголовка Content-Length, если не известно, что сервер является HTTP/1.1 совместимым.»

quest ★★★★
() автор топика
Ответ на: комментарий от Reset

выдаю) ничего если перед выдачей будут теоретически не прочитаны некие байты конца запроса?

quest ★★★★
() автор топика
Ответ на: комментарий от quest

Всё будет ok. Посмотри примеры Алекса Отта по boost::asio, там есть реализация http 1.0, буквально сотня строк. На базе этого кода можно потом и http 1.1 сделать.

Reset ★★★★★
()
Ответ на: комментарий от Reset

еще есть darkhttpd тоже оч маленький и понятный

sergej ★★★★★
()
Ответ на: комментарий от true_admin

quest
Я написал для собственных нужд и практики
простой HTTP-сервер. И столкнулся с этой же
неприятностью, но она исходила не от
какого-то допотопного клиента, а от моего
ИЕ-7 (HTTP/1.1). С тестовой страницы POST приходил
то с лишними переносами, то без них. Зависело
от длины короткого текста <textarea>.

Я не знаю каков закон и как это обрабатывают
настоящие фирменные серверы, но при пользовании
ИЕ каких либо неудобств я не замечал.

Теоретически это могло привести к тому, что
в режиме keep-alive очередной запрос мог бы
начинаться с переносов, оставшихся от
предыдущего запроса.

Я поступил просто: при анализе очередного
запроса, если он начинался с переносов, то
пропускал их числом до восьми. Думаю от
этого хуже не будет.

oleg_2
()

>вызывать recv до тех пор пока в принятом пакете в конце не будет

если своим сервером хотите обслуживать маленький поток пользователей, то сойдет. в протвном случае настоятельно рекомендую использовать неблокирующие сокеты.

Deleted
()
Ответ на: комментарий от Deleted

>в протвном случае настоятельно рекомендую использовать неблокирующие сокеты.

угу

quest ★★★★
() автор топика
Ответ на: комментарий от beastie

>вот тебе простенький web-server на 250 строк: nweb: a tiny, safe Web server (static pages only)

обсуждаемый тут момент в нем реализован мягко говоря через зад и только для GET запроса.

quest ★★★★
() автор топика
Ответ на: комментарий от true_admin

>Парсишь content-lenght и отмеряешь ровно столько, в чём проблема?

в принципе это все не проблема, просто я был удивлен как через зад нужно решать прием POST запроса - ведь пакет может придти частями

quest ★★★★
() автор топика
Ответ на: комментарий от quest

В смысле что надо несколько recv делать? Так этож tcp, поточный протокол. Потом полюбому большой POST будет приходить кусками. Увы, tcp ориентировано на потоки а не на обмен сообщениями, поэтому, надеюсь, когда-нить его таки закопают в пользу более грамотного решения (SCTP сакс).

true_admin ★★★★★
()
Ответ на: комментарий от true_admin

то что несколько recv это нормально, не нормально что придется после каждого recv делать попытку детектировать что заголовок получен, парсить заголовок, вычленять content-lenght и уже от туда понимать нужно еще что-то получать либо уже все пришло да и то как уже было сказано возможны нюансы

quest ★★★★
() автор топика
Ответ на: комментарий от quest

да, я про то же. tcp очень неудобен для большинства случаев. Поэтому сетевое программирование гемор, половина времени уходит на всякую хрень.

true_admin ★★★★★
()
Ответ на: комментарий от Reset

хмм, даже не знаю. в спеку не вчитывался. просто увидел про «рвать соединение» и вспомнилось про кипэлайвы :)

Deleted
()

Традиционно, если по протоколу(любому) входящее сообщение должно кончится, а из сокета все прут байты, байты из сокета читают, но в буфер входящих сообщений не складывают. В какой момент закрывать сокет вопрос отдельный.

imatveev13
()
Ответ на: комментарий от imatveev13

quest Да. Но при ближайшем рассмотрении оказалось не так уж и сложно. Всё равно нужно выпарсивать не только content-lenght, но и другие поля. Вы не указали язык и другие особенности сервера. Я писал на СИ. В двух вариантах: -селектовый прокси-сервер-туннель (один рабочий процесс на всех); -многопроцессный HTTP-сервер (каждому клиенту - свой процесс).

Вот кусок работающего кода для селектового прокси-сервера: Здесь сначала набираю весь заголовок, потом парсю.


Здесь:
lin[n].buf       -буфер 8k;
lin[n].len       -длина данных в lin[n].buf;
lin[n].analiz_i  -вспомогательная переменная, что бы не проходить заново, сначала равна 0;
Sbuf             -длина буфера lin[n].buf;

Возврат:
-1   -ошибка;
0    -заголовок не набран, читать еще;
1    -заголовок набран;


//--------------------------- analiz_http_cli_proxy_njx_2 ---------------------------------

int analiz_http_cli_proxy_njx_2(int n, _lin  lin[], int Sbuf, int dflt_port)

{
    int k, ret=0, port1, fl_host;
    char *p1, *p2, *p3, *uk1, *uk2;
    char host1[100], cport1[8], method[16], *p4;
    char *delet_arr[2];

    int fl_conn=0, fl_my_ip=0;
    char buf[100];

    //--- анализ http заголовка ---

    p1=lin[n].buf + lin[n].analiz_i;
    p2=lin[n].buf + lin[n].len;
    p3=lin[n].buf + Sbuf;

    //--- ищем конец заголовка ---

    if(lin[n].analiz_i>2) p1=p1-2;
    do{
        while(p1<p2 && *p1!='\n') p1++;
        if(p1>=p2){ err=1; goto RET1;}
        p1++; if(p1>=p2){ err=2; goto RET1;}
        if(*p1=='\r'){ p1++; if(p1>=p2){ err=3; goto RET1;}}
        if(*p1=='\n'){ p1++; goto RET2;}
    }while(1);

RET1:

    if(p2 >= p3) return(-1);  //--- ошибка заголовка (слишком длинный) ---
    lin[n].analiz_i=(int)(p1-lin[n].buf);
    return(0);                    //--- заголовок не набран ---

RET2:

    здесь весь заголовок в буфере, а возможно еще и часть POST,
    распарсиваем заголовок. Результаты в массиве lin.

Для многопроцессного сервера я сделал однопроходовый парсер. Он выпарсивает «на ходу», не дожидаясь приема всего заголовка. Чтение сокета встроено прямо в этот парсер.

Вот для примера кусок кода: Здесь: size_buf[0] -длина данных в буфере; Sadres -длина буфера adres; tmrd -тайм-аут.


        ....

    //--- берём adres ---
    j=0;
    while(buf[i]!=' ' && buf[i]!='\r'&& buf[i]!='\n'){
        adres[j]=buf[i];
        i++; if(i>=size_buf[0]){ i=inc1_1(i,sd,buf,size_buf,tmrd); if(i<0){err=10; goto ERR;}}
        j++; if(j>=Sadres){adres[j-1]=0; err=11; goto ERR;}
    }
    adres[j]=0;  if(adres[0]==0){err=12; goto ERR;}
    //--- пропускаем пробелы ---
    j=0;
    while(buf[i]==' '){
        i++; if(i>=size_buf[0]){ i=inc1_1(i,sd,buf,size_buf,tmrd); if(i<0){err=13; goto ERR;}}
        j++; if(j>=25){err=14; goto ERR;}
    }

        ....


//------------------------------- inc1_1 ----------------------------

//--- size_buf[0] -длина заряженного в буфер куска ---
//--- size_buf[1] -длина буфера ---
//--- size_buf[2] -возврат вызова read ---

// Возврат:
//    -1, -2  -ошибка read
//    -3      -ошибка select
//    -4      -time-out
//    -8      -http-заголовок длиннее буфера
//    -9      -конец файла


int inc1_1(int i, int sd, char *buf, int size_buf[], int tmrd)

{
    if(size_buf[0]>=size_buf[1])return(-8); //--- http-заголовок длиннее буфера ---

    size_buf[2]=read_sd(sd,buf+i,(size_buf[1]-i),tmrd);
    if(size_buf[2]<=0){goto ERR;}
    size_buf[0]=i+size_buf[2];
    return(i);
ERR:
    if(size_buf[2]<0)return(size_buf[2]);  //--- ошибка чтения ---
    else return(-9);                       //--- конец файла ---
}



//--------------------------------- read_sd ---------------------------------

//    Работает с глобальными переменными:
//    - err0   -хранит errno;

int read_sd(int sd, char *buf, int Sbuf, int tm_val)
{
    fd_set rdsd;
    int k;
    struct timeval tv;

    k=read(sd, buf, Sbuf);
    if(k>=0){  return(k);}
    if(k<0 && errno!=EAGAIN){ err0=errno; return(-2);} //--- ошибка read ---
    FD_ZERO( &rdsd );
    FD_SET( sd, &rdsd );
    tv.tv_sec =tm_val;
    tv.tv_usec=0;
    k=select(sd+1, &rdsd, NULL, NULL, &tv );
    if(k<0){ err0=errno; return(-3);}       //--- ошибка select ---
    if(k==0){ err0=0; return(-4);}          //--- time-out ---
    k=read(sd, buf, Sbuf);
    if(k<0){ err0=errno; return(-1);}       //--- ошибка read ---
    return(k);
}



oleg_2
()
Ответ на: комментарий от true_admin

>да, я про то же. tcp очень неудобен для большинства случаев. Поэтому сетевое программирование гемор, половина времени уходит на всякую хрень.

достаточно было предусмотреть что вначале пакета запроса идет длина последующего пакета запроса и все было бы гораздо прозрачней

quest ★★★★
() автор топика

вообщем всем спасибо, реализуем

quest ★★★★
() автор топика
Ответ на: комментарий от true_admin

тогда бы был свой спец телнет для этого) сейчас то что говорить - это уже есть

quest ★★★★
() автор топика
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.