從零開始一個http服務器(三)
代碼地址 : https://github.com/flamedancer/cserver
git checkout step3
運行:
gcc request.h request.c response.h response.c main.c tools/utils.c tools/utils.h && ./a.out
測試:
瀏覽器打開 http://127.0.0.1:9734/
response 構造
- 觀察response結構
- 定義並返回response
- 測試
觀察response結構
上一節,我們成功解析了http的request,但是我們在瀏覽器訪問我們的地址http://127.0.0.1:9734/ 還是無法正常顯示。這是因為我們沒有給瀏覽器返回它能讀懂的信息。這一節我們的目標是讓瀏覽器正確的顯示信息。什么樣的才是瀏覽器能讀懂的信息呢?不妨我們用telnet來模擬向百度主頁發一個http request,來看看百度主頁返回的是什么信息。
偽造一個http request的字符串,注意 headers 中的 Host 代表我們要訪問的主機地址。
GET / HTTP/1.1
Host: www.baidu.com
User-Agent: curl/7.54.0
Accept: */*
Content-Type: application/x-www-form-urlencode
再用telnet連接www.baidu.com 並指定80端口(80為http默認端口,telnet默認端口為23), telnet www.baidu.com 80
復制黏貼上面我們構造的字符串回車后,你應該能看到如下類似的返回結果:
Trying 119.75.216.20...
Connected to www.a.shifen.com.
Escape character is '^]'.
GET / HTTP/1.1
Host: www.baidu.com
User-Agent: curl/7.54.0
Accept: */*
Content-Type: application/x-www-form-urlencode
HTTP/1.1 200 OK
Accept-Ranges: bytes
Cache-Control: private, no-cache, no-store, proxy-revalidate, no-transform
Connection: Keep-Alive
Content-Length: 2381
Content-Type: text/html
Date: Sat, 18 Aug 2018 02:12:08 GMT
Etag: "588604c8-94d"
Last-Modified: Mon, 23 Jan 2017 13:27:36 GMT
Pragma: no-cache
Server: bfe/1.0.8.18
Set-Cookie: BDORZ=27315; max-age=86400; domain=.baidu.com; path=/
<!DOCTYPE html>
<!--STATUS OK--><html> ...</html>
Connection closed by foreign host.
從HTTP/1.1 200 OK
開始就是百度放回給我們的結果。讓人驚喜的是這種結構和request很類型,除了第一行外。仔細看看:
- 第一行為 http版本號 response返回碼 response返回結果描述
- 第二行開始為headers
- 空行后,接body
定義並返回response
根據response的結構有的信息定義我們的結構體.
/* response.h
*/
#include <stdio.h>
#include "tools/utils.h"
#include "request.h"
struct http_response {
char * version;
char * code; // 狀態返回碼
char * desc; // 返回描述
struct Map * headers;
char * body;
};
void initHttpResponse(struct http_response * response);
void doResponse(
struct http_request * request,
FILE * stream
);
void outputToFile(
struct http_response * response,
FILE * stream
);
構造我們的response數據, 我們每次都返回相同的數據.
/* response.c
*/
#include <stdio.h> /* fprintf NULL */
#include <string.h> /* strlen */
#include "response.h"
#include "request.h"
#include "tools/utils.h"
void initHttpResponse(struct http_response * response) {
response->version = NULL;
response->code = NULL;
response->desc = NULL;
response->headers = NULL;
response->body = NULL;
}
void doResponse(struct http_request * request, FILE * stream) {
struct http_response responseInstance;
struct http_response * response = &responseInstance;
initHttpResponse(response);
response->version = "HTTP/1.1";
response->code = "200";
response->desc = "OK";
char * content = "<html>hello everyone</html>";
char content_len[25];
sprintf(content_len, "%lu", strlen(content));
struct Item * item = newItem(
"Content-Length",
content_len
);
struct Map map_instance;
initMap(&map_instance);
response->headers = &map_instance;
mapPush(response->headers, item);
response->body = content;
outputToFile(response, stream);
// clean
releaseMap(request->headers);
}
void outputToFile(struct http_response * response, FILE * stream) {
// output version code desc
int r = fprintf(stream, "%s %s %s \r\n",
response->version,
response->code,
response->desc
);
// output headers
struct Map* map = response->headers;
struct List* list;
struct Item* item;
int print_item_cnt = 0;
for(int i=0; i<map->table_len; i++) {
list = map->table[i];
if(list == NULL) {
continue;
}
item = list->start;
while(item != NULL) {
fprintf(stream, "%s: %s\r\n",
item->key,
item->value
);
item = item->next;
}
}
// output body
if(response->body != NULL) {
fprintf(stream, "\r\n%s", response->body);
}
}
寫一個測試用例,將本應向客服端發送的數據輸出到stdout
/* test/responseTest.c
test cmd:
gcc ../request.h ../request.c ../response.h ../response.c ../tools/utils.h ../tools/utils.c responseTest.c && ./a.out
*/
#include <stdio.h>
#include "../request.h"
#include "../response.h"
int main() {
struct http_request request;
char data[] = "POST / HTTP/1.1\r\nContent-Length: 3\r\n\r\n111";
struct Map headers;
request.headers = &headers;
parse_request(&request, data);
doResponse(&request, stdout);
}
cd 到 test 目錄
運行: gcc ../request.h ../request.c ../response.h ../response.c ../tools/utils.h ../tools/utils.c responseTest.c && ./a.out
可以看到正確的輸出:
---------------------------
method is: POST
url is: /
http version is: HTTP/1.1
the headers are :
{'Content-Length': ' 3'}
body is 111
---------------------------
HTTP/1.1 200 OK
Content-Length: 27
<html>hello everyone</html>
現在修改main函數,加上我們的reponse處理邏輯
/**
run cmd:
gcc request.h request.c response.h response.c main.c tools/utils.c tools/utils.h && ./a.out
*/
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <netinet/in.h>
#include <stdlib.h>
#include "request.h"
#include "response.h"
#define MAXREQUESTLEN 50000
void initString(char * c, int length) {
int i = 0;
while(i < length) {
*(c + i) = '\0';
i++;
}
}
int main() {
int server_sockfd, client_sockfd;
socklen_t server_len, client_len;
struct sockaddr_in server_address;
struct sockaddr_in client_address;
server_sockfd = socket(AF_INET, SOCK_STREAM, 0);
server_address.sin_family = AF_INET;
server_address.sin_addr.s_addr = inet_addr("127.0.0.1");
server_address.sin_port = htons(9734);
server_len = sizeof(server_address);
bind(server_sockfd, (struct sockaddr *)&server_address, server_len);
listen(server_sockfd, 5);
while(1) {
char ch[MAXREQUESTLEN];
initString(ch, MAXREQUESTLEN);
// char send_str[] = "hello world !\n";
client_len = sizeof(client_address);
client_sockfd = accept(server_sockfd,
(struct sockaddr *)&client_address, &client_len);
read(client_sockfd, &ch, MAXREQUESTLEN);
printf("%s\n", ch);
struct http_request request;
struct Map headers;
request.headers = &headers;
parse_request(&request, ch);
FILE* fp = fdopen(client_sockfd, "w+");
doResponse(&request, fp);
fflush(fp);
fclose(fp);
// write(client_sockfd, &send_str, sizeof(send_str)/sizeof(send_str[0]));
}
}
測試
啟動我們的server gcc request.h request.c response.h response.c main.c tools/utils.c tools/utils.h && ./a.out
再在瀏覽器訪問我們的服務器地址 http://127.0.0.1:9734/
現在瀏覽器能識別我們的返回結果了!