从零开始一个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/

现在浏览器能识别我们的返回结果了!

从零开始一个http服务器(三)-返回response 构造的更多相关文章

  1. 从零开始一个http服务器(四)-动态返回

    从零开始一个http服务器(四) 代码地址 : https://github.com/flamedancer/cserver git checkout step4 运行: make clean &am ...

  2. 从零开始一个http服务器(二)-请求request解析

    从零开始一个http服务器 (二) 代码地址 : https://github.com/flamedancer/cserver git checkout step2 解析http request 观察 ...

  3. 从零开始一个http服务器(六)-多路复用和压力测试

    从零开始一个http服务器(六)-多路复用和压力测试 代码地址 : https://github.com/flamedancer/cserver git checkout step6 运行: make ...

  4. 从零开始一个http服务器(五)-模拟cgi

    从零开始一个http服务器-模拟cgi(五) 代码地址 : https://github.com/flamedancer/cserver git checkout step5 运行: make cle ...

  5. 从零开始一个http服务器(一)-开始

    从零开始一个http服务器 (一) 代码地址 : https://github.com/flamedancer/cserver git checkout step1 一个简单的socket serve ...

  6. 从零开始用 Flask 搭建一个网站(三)

    从零开始用 Flask 搭建一个网站(二) 介绍了有关于数据库的运用,接下来我们在完善一下数据在前端以及前端到后端之间的交互.本节涉及到前端,因此也会讲解一下 jinja2 模板.jQuery.aja ...

  7. node.js 核心http模块,起一个服务器,返回一个页面

    let http=require("http"); //引入核心http模块 let fs=require("fs"); let mime={ '.js':'a ...

  8. 用java写一个web服务器

    一.超文本传输协议 Web服务器和浏览器通过HTTP协议在Internet上发送和接收消息.HTTP协议是一种请求-应答式的协议——客户端发送一个请求,服务器返回该请求的应答.HTTP协议使用可靠的T ...

  9. 用python自建一个DNS服务器

    前段日子一直在做公司的DNS调度程序,不过由于性能比较差,方案最终废弃掉了.两个半月心血,不想白白浪费掉,于是改了改,把商业秘密相关的部分去掉,变成了一个公共的DNS服务器.其实说的简单点,就是一个可 ...

随机推荐

  1. 【Leetcode】【Medium】Path Sum II

    Given a binary tree and a sum, find all root-to-leaf paths where each path's sum equals the given su ...

  2. Python学习---Python下[字典]的学习

    Python中唯一的映射类型(哈希表)       -->Java中的HashMap<K,V> Python对key进行了哈希函数运算,根据计算的结果决定value的存储地址,所以字 ...

  3. Linux统计文件个数或是代码行数

    统计指定后缀名的文件总个数命令: find . -name *.cpp | wc -l 统计一个目录下代码总行数以及单个文件行数: find . -name *.h | xargs wc -l lin ...

  4. Mac终端下 连接 ubuntu 16.04 ssh root@*.*.*.* permission denied 问题解决方案

    默认 Ubuntu 不开启 ssh 服务 (1)检查是否开启SSH服务  命令:ps -e|grep ssh  查看SSH服务是否开启,或者通过命令:service sshd status 可以查看某 ...

  5. python .loc vs .iloc区别

    1.loc意义:通过行标签索引行数据 例: loc[n]表示索引的是第n行(index 是整数) loc[‘d’]表示索引的是第’d’行(index 是字符) 2. .iloc   :通过行号获取行数 ...

  6. BZOJ 3680: 吊打XXX (模拟退火)

    //yy:今天简单入门学了下ORZ 爬山算法:兔子朝着比现在高的地方跳去.它找到了不远处的最高山峰.但是这座山不一定是珠穆朗玛峰.这就是爬山算法,它不能保证局部最优值就是全局最优值. 模拟退火:兔子喝 ...

  7. Java 中 Vector 和 ArrayList 的区别

    首先看这两类都实现List接口,而List接口一共有三个实现类,分别是ArrayList.Vector和LinkedList.List用于存放多个元素,能够维护元素的次序,并且允许元素的重复.3个具体 ...

  8. Java---页面之间传值跳转

    从首页A进入页面B,然后从B页面登录,成功后跳转到A页面,并打印一句话“登录成功”,传值需要用的后台的. 在B页面写: <%     session.setAttribute("key ...

  9. 利用SPF记录缺失发送伪造邮件

    SPF,也就是 Sender Policy Framework 的缩写,是一种以IP地址认证电子邮件发件人身份的技术,是非常高效的垃圾邮件解决方案. 如何查询所属域名邮箱的SPF记录? 查询的结果,从 ...

  10. Linux 进程与信号的概念和操作 linux process and signals

    进程 主要参考: http://www.bogotobogo.com/Linux/linux_process_and_signals.php 译者:李秋豪 信号与进程几乎控制了操作系统的每个任务. 在 ...