Starting from zero an HTTP server-requesting a request resolution (II)

Source: Internet
Author: User

Starting from zero an HTTP server (ii)

Code Address: Https://github.com/flamedancer/cserver
git checkout Step2

Parsing HTTP request
    • Observing the HTTP data received
    • Parse method URL version of request
    • Parse Header
    • Parsing body
Observing the HTTP data received
上一节我们完成了一个简单的基于TCP/IP的socket server 程序。而HTTP正式基于TCP/IP的应用层协议,所以只要我们的程序能读懂HTTP数据,并做出符合HTTP协议的响应,那么就能完成HTTP的通信。上一节最后我们用telnet成功连接了我们的服务器,但只是向它传送了一些没有意义的字符。如果是浏览器,会传送什么呢?我们试着在浏览器地址栏输入我们的服务器地址: 127.0.0.1:9734 后访问,发现浏览器说“127.0.0.1 发送的响应无效。”, 那是说我们返回给浏览器的数据浏览器读不懂,因为现代的浏览器默认用http协议请求访问我们的服务器,而我们的返回的数据只是"helloworld"字符串,并不符合http协议的返回格式。虽然如此,但浏览器却是很有诚意的给我们的服务器发标准的http请求,不信我们看下我们的服务器收到的信息:
GET / HTTP/1.1Host: 127.0.0.1:9734Connection: keep-aliveCache-Control: max-age=0Upgrade-Insecure-Requests: 1User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8Accept-Encoding: gzip, deflate, brAccept-Language: zh-CN,zh;q=0.9,en;q=0.8
先观察一会儿,看起来第一行是http请求的类型,第二行开始是一些":"号分割的键值对。的确如此,第一行告诉我们是用的GET请求,请求的url是"/",用的是1.1的HTTP版本。第二行开始是HTTP的请求头部。除了GET请求外,另一种常用的请求是POST。用浏览器发POST请求稍麻烦,我们就借用curl工具来发送个HTTP POST请求给服务器看下数据又会是怎们样的:

curl -d "message=nice to meet you" 127.0.0.1:9734/hello, the server receives the information:

POST /hell HTTP/1.1Host: 127.0.0.1:9734User-Agent: curl/7.54.0Accept: */*Content-Length: 24Content-Type: application/x-www-form-urlencodedmessage=nice to meet you

You can see the header information after a blank line and post the body data information. Also note is the Content-length header, which represents the size of the body data of the post.

Parse method URL version of request
先来解析最简单的第一行: "POST /hell HTTP/1.1", 只需要用空格split出三个字符串就好了。  
// request.hstruct http_request {    char * method;    char * url;    char * version;    char * body;};void parse_request(    struct http_request * request,    char * http_data);
/* request.c*/#include "request.h"void parse_request(    struct http_request * request,    char * http_data) {    char * start = http_data;    // 解析第一行    char * method = start;    char * url = 0;    char * version = 0;    for(;*start && *start != '\n'; start++) {        //  method url version 是由 空格 分割的        if(*start == ' ') {            if(url == 0) {                url = start + 1;            } else {                version = start + 1;            }            *start = '\0';        }    }    *start = '\0';    request->method = method;    request->url = url;    request->version = version;}
编写测试用例:
/* test/requestTest.c*/#include <stdio.h>#include "../request.h"int main() {    struct http_request request;    char data[] = "POST / HTTP/1.1\n";    parse_request(&request, data);    printf("method is %s; url is %s; version is %s \n", request.method, request.url, request.version);}
在test目录下执行:` gcc  ../request.h ../request.c requestTest.c && ./a.out`,可以看到我们解析的方法正确。
Parse Header
header的解析看起来比较复杂,每一行很容易看出是用":"分割的key-value对,所以我们可以用HashMap来表达。如何判断header数据的结束呢,通过前面的观察,可以发现如果是POST会有一个空行和body隔开,是GET的话只能检查客户端的数据是否发完,发完就代表header也结尾了。在正式解析header之前,我们先构造基本数据的数据结构,以方便以后使用。1. 创建链表结构体2. 创建哈希表结构体3. 按行解析header,遇到空行或字符串结尾停止
    1. Create a linked list structure body
      First declare the list structure body
      • A list element structure that holds the actual value, plus a pointer to the next
      • Represents the structure of a linked list, storing the key attributes of a linked list, such as size, tail pointer
/* tools/utils.h*/struct ListItem {    struct ListItem* next;    char* value;};struct List {    struct ListItem* start;    struct ListItem* end;    int length;};

Then declare the method we want to use: Initialize, add element, print List

void initListItem(struct ListItem * listItem);void initList(struct List * listItem);void listAppend(struct List* list, struct ListItem* item);void listPrint(struct List* List);

Method implementation

  #include <errno.h>/* errno */#include <stdio.h>/* NULL */#include "utils.h    "Void Initlistitem (struct ListItem * ListItem) {listitem->next=null; Listitem->value=null;}    void initlist (struct list * list) {list->start=list->end=null; list->length=0;} /* Add item1 at the end of list. If the list is empty, the end points point to item2. Otherwise, the next item at the end points to item, and the trailing end is item3.    Length + 1*/void listappend (struct list* List, struct listitem* item) {item->next = NULL;    if (List->start = = NULL) {List->start = List->end = Item;        } else {list->end->next = Item;    List->end = Item; } list->length++;}    void Listprint (struct list* List) {struct listitem* point = list->start;    printf ("[");        for (int i=0; i<list->length; i++) {if (i>0) {printf (",");        } printf ("'%s '", Point->value);    Point = point->next; } printf ("]\n");}  

Test
We're trying to add two elements, and then we'll print a list.

/* test/utilsTest.c    test cmd :    gcc  ../tools/utils.h ../tools/utils.c utilsTest.c && ./a.out*/#include <assert.h>#include <stdio.h>  /*  printf */#include "../tools/utils.h"void listAppendTest() {    struct List list_instance;    struct List* list = &list_instance;    struct ListItem listItem_instance;    struct ListItem* listItem = &listItem_instance;    listItem->value = "hello world";    struct ListItem listItem_instance2;    struct ListItem* listItem2 = &listItem_instance2;    listItem2->value = "nice to meet you";    assert(list->length == 0);    listAppend(list, listItem);    assert(list->length == 1);    listAppend(list, listItem2);    listPrint(list);    printf("test listAppend OK\n");}int main() {    listAppendTest();}

See the output as

['hello world', 'nice to meet you']

Perfect!

    1. Create a Hashtable structure body
      Unlike ListItem, our mapitem requires two attributes to represent key and value, respectively, for convenience, we directly retrofit the ListItem to be compatible with the map.
      Change ListItem to item:
struct Item {    struct Item* next;    char* key;    char* value;};

Then we construct our map structure. The main inside is the hash table represented by an array, the elements of the table are not purely item and the list is used to insert elements in the same index when the hash collision is encountered. In addition, we need a method for calculating the hash value of a string.

/* tools/utils.h*//* .... 省略部分代码*/#define HashTableLen 100struct Map {    struct List* table[HashTableLen];    int table_len;    int item_cnt;};void initMap(struct Map* map);void releaseMap(struct Map* map);int hashCode(char * str);void mapPush(struct Map* map, struct Item* item);void mapPrint(struct Map* map);void mapGet(char * key);
/* TOOLS/UTILS.C *//* ....    Omit part of code */void initmap (struct map* Map) {map->table_len = Hashtablelen;    map->item_cnt = 0;    for (int i=0; i<map->table_len; i++) {map->table[i] = NULL;            }}void releasemap (struct map* Map) {for (int i=0; i<map->table_len; i++) {if (Map->table[i]! = NULL) {            Free (map->table[i]);        Map->table[i] = NULL;    }}}int hashcode (struct item* Item) {char* str = item->key;    int code;    int len = 0;    int maxlen = 100;        for (code=0; *str! = ' *str ' && len < maxlen; str++) {code = code +/-+ * (+/-);    len++; } return code% Hashtablelen;}    void Mappush (struct map* Map, struct item* Item) {int index = hashcode (item);         if (map->table[index] = = NULL) {struct list* list = malloc (sizeof (struct List));         Initlist (list);         if (list = = NULL) {perror ("Error:out of Storeage"); } Map->table[index] = list;   } listappend (Map->table[index], item); map->item_cnt++;}    void Mapprint (struct map* Map) {struct list* List;    struct item* Item;    int print_item_cnt = 0;    printf ("{");         for (int i=0; i<map->table_len; i++) {list = map->table[i];         if (list = = NULL) {continue;         } item = list->start;             while (item = NULL) {printf ("'%s ': '%s '", Item->key, Item->value);             item = item->next;             print_item_cnt++;             if (print_item_cnt! = map->item_cnt) {printf (","); }}} printf ("}\n");}

Test code

void mapPushTest() {    struct Map map_instance;    initMap(&map_instance);    struct Map* map = &map_instance;    struct Item item_instance;    initItem(&item_instance);    struct Item* item = &item_instance;    item->key = "h";    item->value = "hello world";    mapPush(map, item);    mapPrint(map);    struct Item item_instance2;    initItem(&item_instance2);    struct Item* item2 = &item_instance2;    item2->key = "h2";    item2->value = "nice to meet you";    mapPush(map, item2);    mapPrint(map);    

{' h ': ' Hello World '}
{' h ': ' Hello World ', ' H2 ': ' Nice to meet '}

3. 解析header代码有了map结构体后,解析header就方便多了,只要按行根据":" 拆分成 key和value就行了?``` c/* 第二行开始为 header  解析hedaer*/start++;   // 第二行开始initMap(request->headers);char * line = start;char * key;char * value;while( *line != '\r' && *line != '\0') {    char * key;    char * value;    while(*(start++) != ':');    *(start - 1) = '\0';    key = line;    value = start;    // todo 超过 MAXREQUESTLEN 的 判断    while(start++, *start!='\0' && *start!='\r');    *start++ = '\0'; // \r -> \0    start++;   // skip \n    printf("key is %s \n", key);    printf("value is %s \n", value);    line = start;    struct Item * item = (struct Item *) malloc(sizeof(struct Item));    initItem(item);    item->key = key;    item->value = value;    mapPush(request->headers, item);    mapPrint(request->headers);}releaseMap(request->headers);
Parsing body

Parsing body is very simple, if the last line is not a blank line, the description is the body data, the empty line behind the body data.
There is a key in the header, ' content-length ' represents how long the body is, and we can use this field to determine the end of the body.

/* 如果最后一行不是空行  说明有body数据 */if(*line == '\r') {    char * len_str = mapGet(request->headers, "Content-Length");    if(len_str != NULL) {        int len = atoi(len_str);        // 跳过 两个 \n        line = line + 2;        * (line + len) = '\0';        request->body = line;    }}printf("the request body is %s \n", request->body);
Finish printing our results.
/*  打印 request 信息 */printf("---------------------------\n");printf("method is: %s \n", request->method);printf("url is: %s \n", request->url);printf("http version is: %s \n", request->version);printf("the headers are :\n");mapPrint(request->headers);printf("body is %s \n", request->body);printf("---------------------------\n");

Performgcc request.h request.c main.c tools/utils.c tools/utils.h && ./a.out
And then open a new terminal to executecurl -d "message=nice to meet you" 127.0.0.1:9734/hello-everyone
See the output:

POST /hello-everyone HTTP/1.1Host: 127.0.0.1:9734User-Agent: curl/7.54.0Accept: */*Content-Length: 24Content-Type: application/x-www-form-urlencodedmessage=nice to meet you---------------------------method is: POSTurl is: /hello-everyonehttp version is: HTTP/1.1the headers are :{'User-Agent': ' curl/7.54.0', 'Content-Type': ' application/x-www-form-urlencoded', 'Host': ' 127.0.0.1:9734', 'Accept': ' */*', 'Content-Length': ' 24'}body is message=nice to meet you---------------------------

Starting from zero an HTTP server-requesting a request resolution (II)

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.