精华内容
参与话题
问答
  • 简单HTTP服务器实现

    千次阅读 2018-10-30 20:46:03
    我们这里实现一个简单的HTTP服务器,无论浏览器向我们请求什么数据,我们都返回一个hello world   //实现最简单的http服务端程序 //返回hello world //http是应用协议,在传输层使用的是tcp协议所以我们的程序...

       我们这里实现一个简单的HTTP服务器,无论浏览器向我们请求什么数据,我们都返回一个hello world

      

    //实现最简单的http服务端程序
    //返回hello world
    //http是应用协议,在传输层使用的是tcp协议所以我们的程序本质上是tcp服务器
    //我们http指定监听10000端口,所以在请求访问的时候也要手动指定否则默认80端口
    
    #include<stdio.h>
    #include<stdlib.h>
    #include<unistd.h>
    #include<string.h>
    #include<errno.h>
    #include<sys/socket.h>
    #include<netinet/in.h>
    
    int main(int argc,char* argv[])
    {
        int sockfd=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
        if(sockfd<0)
        {
            perror("socket error\n");
            return -1;
        }
        struct sockaddr_in lst_addr;
        lst_addr.sin_family=AF_INET;
        lst_addr.sin_port=htons(9998);
        lst_addr.sin_addr.s_addr=inet_addr("192.168.76.130");
        socklen_t len=sizeof(struct sockaddr_in);
        int ret=bind(sockfd,(struct sockaddr*)&lst_addr,len);
        if(ret<0)
        {
            perror("bind error\n");
            return -1;
        }
        if(listen(sockfd,5)<0)
        {
            perror("listen error\n");
            return -1;
        }
        while(1)
        {
            struct sockaddr_in cli_addr;
            int newfd=accept(sockfd,(struct sockaddr*)&cli_addr,&len);
            if(newfd<0)
            {
                perror("accept error\n");
                continue;
            }
            char buff[1024]={0};
            int ret=recv(newfd,buff,1023,0);
            if(ret>0)
            {
                printf("req:%s\n",buff);
    			//这里我们打印的是浏览器HTTP的请求头部
            }
            //返回的http头部信息要包括
            //首行 HTTP/1.1 200 OK
            //头信息:
            // 空行
            // 正文
            char *rsp="<h1>hello world</h1>";
            memset(buff,0x00,1024);
            sprintf(buff,"%s\r\n%s%d\r\n%s\r\n\r\n%s%","HTTP/1.1 200 OK","Content-Length: ",strlen(rsp),"Content-Type:text/html:charset=UTF-8",rsp);
    		//这里我们返回的是简单的字符串hello world
    		//%s \r\n 第一行输出了HTTP/1.1 200 OK
    		//%S %d \r\n 这里第二行输出了Content-Length: strlen(rsp) 这里我们是想通过这个关键字告诉浏览器我们这次发送的数据是多少
    		//%s \r\n 这一行输出了Content-Type:text/html:charset=UTF-8 是告诉浏览器我们的文件格式和编码格式
    		//\r\n 这一行是HTTP头部头部和正文之间的空行
    		//%s 这里才是我们最终传输的数据
    		//这一部分的内容才真正算是我们的HTTP协议的部分,这里是我们用HTTP协议和浏览器交流的部分。
    		printf("%s", buff);
            send(newfd,buff,strlen(buff),0);
            close(newfd);
        }
    }
    

     

    当我们运行程序之后,在浏览器输入网络地址:网络号就可以来访问我们的服务器。第一次很有可能会失败,因为我们的Linux有防火墙,这里我们必须要关闭一下Linux自身的防火墙。

    运用 sudo service iptables stop来关闭我们系统的防火墙。

    然后运行我们的程序,之后在浏览器输入192.168.76.130:9998

    这是浏览器给我们的请求头信息

    这里我们也打印了我们给浏览器的头信息和数据

    这时候我们看我们的浏览器

    能够正常返回我们的hello world代表服务器没有问题。

    展开全文
  • HTTP服务器实现(一)

    千次阅读 2018-08-22 16:37:32
    实现一个HTTP服务器就是实现一个程序可以接受客户端发送给服务器进程的请求消息,通过解析这些请求消息,做出相应的响应。下面我们先来梳理一下整体的思路: 进行服务器的初始化: int init_server(char* ip...

    实现一个HTTP服务器就是实现一个程序可以接受客户端发送给服务器进程的请求消息,通过解析这些请求消息,做出相应的响应。下面我们先来梳理一下整体的思路:

    • 进行服务器的初始化:

    int init_server(char* ip, int port)
    {
        int sock = socket(AF_INET, SOCK_STREAM, 0); 
        if(sock < 0)
        {   
            perror("socket");
            return -1; 
        }   
        struct sockaddr_in server;
        server.sin_family  = AF_INET;
        server.sin_addr.s_addr = inet_addr(ip);
        server.sin_port = htons(port);
        if(bind(sock, (struct sockaddr*)&server, sizeof(server)) < 0)
        {                                                                                                                                
            perror("bind");
            close(sock);
            return -1; 
        }
        if(listen(sock, 5) < 0)                                                                                                          
        {
            perror("listen");
            close(sock);
            return -1;
        }
        return sock;
    }
    
    • 下面,进入事件循环,收到客户端消息,解析并作出响应。

    可是,如果在循环中直接读取请求,处理请求,作出响应,这样的话,在本次循环未结束前(也就是本次请求未处理完成前),因为只调用了一次accept,无法处理其他客户端发来的请求(能建立连接,因为这是由内核负责的)。这时候,我们应该循环调用accept接收客户端连接,在之后将处理该客户端发送的请求这件事交给另外一个线程去做,实现多线程的服务器程序。

    int main(int argc, char* argv[])
    {
        if(argc != 3)
        {
            printf("usage : ./http_server [IP] [port]\n");
            return -1;
        }
        //进行服务器的初始化
        int sock = init_server(argv[1], atoi(argv[2]));
        printf("sock = %d\n", sock);
    
        if(sock < 0)
        {
            printf("服务器初始化失败\n");                                                                                                
            return -1;
        }
        printf("server init ok\n");
        //进入事件循环
        while(1)                                                                                                                         
        {
            struct sockaddr_in client;
            socklen_t len = sizeof(client);
            int new_sock = accept(sock, (struct sockaddr*)&client, &len);
            printf("new_sock:%d\n", new_sock);
            if(new_sock < 0)
            {
                perror("accept");
                continue;
            }
    
            pthread_t pid;
            pthread_create(&pid, NULL, ThreadEntry, (void*)new_sock);     //这里采用传值传递的方式进行,因为这是两个执行流,如果传址可能n
                                                                         //若采用将该变量声明为静态变量,可能会导致线程不安全
            pthread_detach(pid);                                                                                                         
        }
        return 0;
    }
    
    void* ThreadEntry(void* arg)
    {
        int64_t sock = (int64_t)arg;
        //从sock中读取请求并处理
        HandlerRequest(sock);
        return NULL;                                                                                                                     
    }
    
    • 下面,解析请求,并做出响应

    想要正确的解析请求,就要了解HTTP请求的格式:

    HTTP请求格式:

    首           行:方法  url  HTTP协议版本

    header部分:请求的属性值,以冒号分隔的键值对的形式出现

    空          行:遇到空行说明header部分结束了

    body 部 分:如果存在,header部分一定存在Content-Length属性说明body部分的长度

    HTTP协议

    于是,我们可以整理出这一部分的整体逻辑:

    静态页面是指不需要进行动态生成的,直接将HTTP服务器工作目录下现有的页面发送给客户端的页面叫做静态页面;动态页面是指页面的内容时变化的,需要服务器进行“计算”,将结果写回客户端。

    void HandlerRequest(int sock)
    {
        //读取首行并解析
        //  读取首行并解析
        Req req;
        memset(&req, 0, sizeof(req));
        if(ReadLine(sock, req.first_line) < 0)
        {
            printf("ReadLine error! read first_line: %s\n", req.first_line);
            err_code = 404;
            //构造404的响应代码
            goto END;                                                                                                                    
        }
        printf("ReadLined\n");
        printf("first_line:%s\n", req.first_line);
        if(ParseFirstLine(req.first_line, &req.method, &req.url) < 0)
        {                                                                                                                                
            printf("ParseFirstLine error! method: %s   url: %s\n", req.method, req.url);
            err_code = 404;
            //构造404的响应代码
            goto END;
        }
        printf("ParseFirstLine ok\n");
        printf("method=%s\n", req.method);
        //  解析URL
        if(Parseurl(req.url, &req.url_path, &req.query_string) < 0)
        {
            printf("Parseurl error! query_string : %s\n", req.query_string);
            err_code = 404;
            //构造404的响应代码
            goto END;
        }                                                                                                                                
        printf("Parseurl ok\n");
        printf("query_string=%s\n", req.query_string);
        //  解析header(此处为了简单,只保留content_length)
        if(HandlerHeader(sock, &req.content_length) < 0)
        {
            printf("HandlerHeader error!");
            err_code = 404;
            //构造404的响应代码
            goto END;
        }
        printf("HandlerHeader ok\n");
        //根据情况决定执行静态还是动态页面
        //  若方法是GET且没有query_string,生成静态页面
        if(strcmp(req.method, "GET") == 0 && req.query_string == NULL)
            err_code = HandlerStaticFile(sock, &req);                                                                                    
        //  若方法是GET且有query_string,生成动态页面
        else if(strcmp(req.method,"GET") == 0 && req.query_string != NULL)
            err_code = HanndlerCGI(sock, &req);
        //  若方法为POST(登录页面)
        else if(strcmp(req.method, "POST") == 0)
            err_code = HanndlerCGI(sock, &req);
        else
        {
            printf("method not found! method : %s\n", req.method);
            err_code = 404;
            //构造404的响应代码
            goto END;
        }
    END:
        //这次请求处理结束的收尾工作                                                                                                     
        if(err_code != 200)
        {
            Handler404(sock);
        }
        close(sock);
    }
    

    这里将具体的请求解析和计算,以及响应任务交给了不同页面逻辑代码。下一篇文章就会进行具体页面处理逻辑的实现了。

    展开全文
  • 从零实现一个http服务器

    万次阅读 多人点赞 2018-05-18 12:44:42
    我始终觉得,天生的出身很重要,但后天的努力更加重要,所以如今的很多“科班”往往不如后天努力的“非科班”。...我面试过很多求职者,一说到http协议,他们能滔滔不绝,然后我问他http协议的具...

    我始终觉得,天生的出身很重要,但后天的努力更加重要,所以如今的很多“科班”往往不如后天努力的“非科班”。所以,我们需要重新给“专业”和“专家”下一个定义:所谓专业,就是别人搞你不搞,这就是你的“专业”;你和别人同时搞,你比别人搞的好,就是“专家”。

    说到http协议和http请求,很多人都知道,但是他们真的“知道”吗?我面试过很多求职者,一说到http协议,他们能滔滔不绝,然后我问他http协议的具体格式是啥样子的?很多人不清楚,不清楚就不清楚吧,他甚至能将http协议的头扯到html文档头部<head>。当我问http GET和POST请求的时候,GET请求是什么形式一般人都可以答出来,但是POST请求的数据放在哪里,服务器如何识别和解析这些POST数据,很多人又说不清道不明了。当说到http服务器时,很多人离开了apache、Nginx这样现成的http server之外,自己实现一个http服务器无从下手,如果实际应用场景有需要使用到一些简单http请求时,使用apache、Nginx这样重量级的http服务器程序实在劳师动众,你可以尝试自己实现一个简单的。

    上面提到的问题,如果您不能清晰地回答出来,可以阅读一下这篇文章,这篇文章在不仅介绍http的格式,同时带领大家从零实现一个简单的http服务器程序。

    一、项目背景

    最近很多朋友希望我的flamingo服务器支持http协议,我自己也想做一个微信小程序,小程序通过http协议连接通过我的flamingo服务器进行聊天。flamingo是一个开源的即时通讯软件,目前除了服务器端,还有pc端、android端,后面会支持更多的终端。关于flamingo的介绍您可以参考这里:https://blog.csdn.net/analogous_love/article/details/69481542,这是我不断维护一个项目,其最新代码下载地址是:https://github.com/baloonwj/flamingo,更新日志:https://github.com/baloonwj/flamingo/issues/1。下面是flamingo的部分截图:


    二、http协议介绍

    1. http协议是应用层协议,一般建立在tcp协议的基础之上(当然你的实现非要基于udp也是可以的),也就是说http协议的数据收发是通过tcp协议的。

    2. http协议也分为head和body两部分,但是我们一般说的html中的<head>和<body>标记不是http协议的头和身体,它们都是http协议的body部分。


    那么http协议的头到底长啥样子呢?我们来介绍一下http协议吧。

    http协议的格式如下:

    GET或POST 请求的url路径(一般是去掉域名的路径) HTTP协议版本号
    字段1名: 字段1值\r\n
    字段2名: 字段2值\r\n
          ...
    字段n名 : 字段n值\r\n
    \r\n
    http协议包体内容

    也就是说http协议由两部分组成:包头和包体,包头与包体之间使用一个\r\n分割,由于http协议包头的每一行都是以\r\n结束,所以http协议包头一般以\r\n\r\n结束。

    举个例子,比如我们在浏览器中请求http://www.hootina.org/index_2013.php这个网址,这是一个典型的GET方法,浏览器组装的http数据包格式如下: 

    GET /index_2013.php HTTP/1.1\r\n
    Host: www.hootina.org\r\n
    Connection: keep-alive\r\n
    Upgrade-Insecure-Requests: 1\r\n
    User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.146 Safari/537.36\r\n
    Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8\r\n
    Accept-Encoding: gzip, deflate\r\n
    Accept-Language: zh-CN,zh;q=0.9,en;q=0.8\r\n
    \r\n

    上面这个请求只有包头没有包体,http协议的包体不是必须的,也就是说GET请求一般没有包体。

    如果GET请求带参数,那么一般是附加在请求的url后面,参数与参数之间使用&分割,例如请求http://www.hootina.org/index_2013.php?param1=value1&param2=value2&param3=value3,我们看下这个请求组装的的http协议包格式:

    GET /index_2013.php?param1=value1&param2=value2&param3=value3 HTTP/1.1\r\n
    Host: www.hootina.org\r\n
    Connection: keep-alive\r\n
    Upgrade-Insecure-Requests: 1\r\n
    User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.146 Safari/537.36\r\n
    Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8\r\n
    Accept-Encoding: gzip, deflate\r\n
    Accept-Language: zh-CN,zh;q=0.9,en;q=0.8\r\n
    \r\n

    对比一下,你现在知道http协议的GET参数放在协议包的什么位置了吧。

    那么POST的数据放在什么位置呢?我们再12306网站(https://kyfw.12306.cn/otn/login/init)中登陆输入用户名和密码:


    然后发现浏览器以POST方式组装了http协议包发送了我们的用户名、密码和其他一些信息,组装的包格式如下:

    POST /passport/web/login HTTP/1.1\r\n
    Host: kyfw.12306.cn\r\n
    Connection: keep-alive\r\n
    Content-Length: 55\r\n
    Accept: application/json, text/javascript, */*; q=0.01\r\n
    Origin: https://kyfw.12306.cn\r\n
    X-Requested-With: XMLHttpRequest\r\n
    User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.146 Safari/537.36\r\n
    Content-Type: application/x-www-form-urlencoded; charset=UTF-8\r\n
    Referer: https://kyfw.12306.cn/otn/login/init\r\n
    Accept-Encoding: gzip, deflate, br\r\n
    Accept-Language: zh-CN,zh;q=0.9,en;q=0.8\r\n
    Cookie: _passport_session=0b2cc5b86eb74bcc976bfa9dfef3e8a20712; _passport_ct=18d19b0930954d76b8057c732ce4cdcat8137; route=6f50b51faa11b987e576cdb301e545c4; RAIL_EXPIRATION=1526718782244; RAIL_DEVICEID=QuRAhOyIWv9lwWEhkq03x5Yl_livKZxx7gW6_-52oTZQda1c4zmVWxdw5Zk79xSDFHe9LJ57F8luYOFp_yahxDXQAOmEV8U1VgXavacuM2UPCFy3knfn42yTsJM3EYOy-hwpsP-jTb2OXevJj5acf40XsvsPDcM7; BIGipServerpool_passport=300745226.50215.0000; BIGipServerotn=1257243146.38945.0000; BIGipServerpassport=1005060362.50215.0000\r\n
    \r\n
    username=balloonwj%40qq.com&password=iloveyou&appid=otn

    其中username=balloonwj%40qq.com&password=iloveyou&appid=otn就是我们的POST数据,但是大家需要注意的以下几种,不要搞错:

    1. 我的用户名是balloonwj@qq.com,到POST里面变成balloonwj%40qq.com,其中%40是@符号的16进制转码形式。这个码表可以参考这里:http://www.w3school.com.cn/tags/html_ref_urlencode.html

    2.这里有三个变量,分别是username、password和appid,他们之间使用&符号分割,但是请注意的是,这不意味着传递多个POST变量时必须使用&符号分割,只不过这里是浏览器html表单(输入用户名和密码的文本框是html表单的一种)分割多个变量采用的默认方式而已。你可以根据你的需求,来自由定制,只要让服务器知道你的解析方式即可。比如可以这么分割:

    方法一:
    username=balloonwj%40qq.com|password=iloveyou|appid=otn
    
    方法二:
    username:balloonwj%40qq.com\r\n
    password:iloveyou\r\n
    appid:otn\r\n
    
    方法三
    username,password,appid=balloonwj%40qq.com,iloveyou,otn

    不管怎么分割,只要你能自己按一定的规则解析出来就可以了。

    不知道你注意到没有,上面的POST数据放在http包体中,服务器如何解析呢?可能你没明白我的意思,看下图:


    如上图所示,由于http协议是基于tcp协议的,tcp协议是流式协议,包头部分可以通过多出的\r\n来分界,包体部分如何分界呢?这是协议本身要解决的问题。目前一般有两种方式,第一种方式就是在包头中有个content-Length字段,这个字段的值的大小标识了POST数据的长度,上图中55就是数据username=balloonwj%40qq.com&password=iloveyou&appid=otn的长度,服务器收到一个数据包后,先从包头解析出这个字段的值,再根据这个值去读取相应长度的作为http协议的包体数据。还有一个格式叫做http chunked技术(分块),大致意思是将大包分成小包,具体的详情有兴趣的读者可以自行搜索学习。

    三、http客户端实现

    如果您能掌握以上说的http协议,你就可以自己通过代码组装http协议发送http请求了(也是各种开源http库的做法)。我们先简单地介绍一下如何模拟发送http。举个例子,我们要请求http://www.hootina.org/index_2013.php,那么我们可以先通过域名得到ip地址,即通过socket API gethostbyname()得到www.hootina.org的ip地址,由于http服务器默认的端口号是80,有了域名和ip地址之后,我们使用socket API connect()去连接服务器,然后根据上面介绍的格式组装成http协议包,利用socket API send()函数发出去,如果服务器有应答,我们可以使用socket API recv()去接受数据,接下来就是解析数据(先解析包头和包体)。

    四、http服务器实现

    我们这里简化一些问题,假设客户端发送的请求都是GET请求,当客户端发来http请求之后,我们拿到http包后就做相应的处理。我们以为我们的flamingo服务器实现一个支持http格式的注册请求为例。假设用户在浏览器里面输入以下网址,就可以实现一个注册功能:

    http://120.55.94.78:12345/register.do?p={"username": "13917043329", "nickname": "balloon", "password": "123"}

    这里我们的http协议使用的是12345端口号而不是默认的80端口。如何侦听12345端口,这个是非常基础的知识了,这里就不介绍了。当我们收到数据以后:

    void HttpSession::OnRead(const std::shared_ptr<TcpConnection>& conn, Buffer* pBuffer, Timestamp receivTime)
    {
        //LOG_INFO << "Recv a http request from " << conn->peerAddress().toIpPort();
        
        string inbuf;
        //先把所有数据都取出来
        inbuf.append(pBuffer->peek(), pBuffer->readableBytes());
        //因为一个http包头的数据至少\r\n\r\n,所以大于4个字符
        //小于等于4个字符,说明数据未收完,退出,等待网络底层接着收取
        if (inbuf.length() <= 4)
            return;
    
        //我们收到的GET请求数据包一般格式如下:
        /*
        GET /register.do?p={%22username%22:%20%2213917043329%22,%20%22nickname%22:%20%22balloon%22,%20%22password%22:%20%22123%22} HTTP/1.1\r\n
        Host: 120.55.94.78:12345\r\n
        Connection: keep-alive\r\n
        Upgrade-Insecure-Requests: 1\r\n
        User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.146 Safari/537.36\r\n
        Accept-Encoding: gzip, deflate\r\n
        Accept-Language: zh-CN, zh; q=0.9, en; q=0.8\r\n
        \r\n
         */
        //检查是否以\r\n\r\n结束,如果不是说明包头不完整,退出
        string end = inbuf.substr(inbuf.length() - 4);
        if (end != "\r\n\r\n")
            return;
    
        //以\r\n分割每一行
        std::vector<string> lines;
        StringUtil::Split(inbuf, lines, "\r\n");
        if (lines.size() < 1 || lines[0].empty())
        {
            conn->forceClose();
            return;
        }
    
        std::vector<string> chunk;
        StringUtil::Split(lines[0], chunk, " ");
        //chunk中至少有三个字符串:GET+url+HTTP版本号
        if (chunk.size() < 3)
        {
            conn->forceClose();
            return;
        }
    
        LOG_INFO << "url: " << chunk[1] << " from " << conn->peerAddress().toIpPort();
        //inbuf = /register.do?p={%22username%22:%20%2213917043329%22,%20%22nickname%22:%20%22balloon%22,%20%22password%22:%20%22123%22}
        std::vector<string> part;
        //通过?分割成前后两端,前面是url,后面是参数
        StringUtil::Split(chunk[1], part, "?");
        //chunk中至少有三个字符串:GET+url+HTTP版本号
        if (part.size() < 2)
        {
            conn->forceClose();
            return;
        }
    
        string url = part[0];
        string param = part[1].substr(2);
            
        if (!Process(conn, url, param))
        {
            LOG_ERROR << "handle http request error, from:" << conn->peerAddress().toIpPort() << ", request: " << pBuffer->retrieveAllAsString();
        }
    
        //短连接,处理完关闭连接
        conn->forceClose();
    }

    代码注释都写的很清楚,我们先利用\r\n分割得到每一行,其中第一行的数据是:

    GET /register.do?p={%22username%22:%20%2213917043329%22,%20%22nickname%22:%20%22balloon%22,%20%22password%22:%20%22123%22} HTTP/1.1

    其中%22是双引号的url转码形式,%20是空格的url转码形式,然后我们根据空格分成三段,其中第二段就是我们的网址和参数:

    /register.do?p={%22username%22:%20%2213917043329%22,%20%22nickname%22:%20%22balloon%22,%20%22password%22:%20%22123%22}

    然后我们根据网址与参数之间的问号将这个分成两段:第一段是网址,第二段是参数:

    bool HttpSession::Process(const std::shared_ptr<TcpConnection>& conn, const std::string& url, const std::string& param)
    {
        if (url.empty())
            return false;
    
        if (url == "/register.do")
        {
            OnRegisterResponse(param, conn);
        }
        else if (url == "/login.do")
        {
            OnLoginResponse(param, conn);
        }
        else if (url == "/getfriendlist.do")
        {
    
        }
        else if (url == "/getgroupmembers.do")
        {
    
        }
        else
            return false;
    
        
        return true;
    }

    然后我们根据url匹配网址,如果是注册请求,会走注册处理逻辑:

    void HttpSession::OnRegisterResponse(const std::string& data, const std::shared_ptr<TcpConnection>& conn)
    {
        string retData;
        string decodeData;
        URLEncodeUtil::Decode(data, decodeData);
        BussinessLogic::RegisterUser(decodeData, conn, false, retData);
        if (!retData.empty())
        {
            std::string response;
            URLEncodeUtil::Encode(retData, response);
            MakeupResponse(retData, response);
            conn->send(response);
    
            LOG_INFO << "Response to client: cmd=msg_type_register" << ", data=" << retData << conn->peerAddress().toIpPort();;
        }
    }

    注册结果放在retData中,为了发给客户端,我们将结果中的特殊字符如双引号转码,如返回结果是:

    {"code":0, "msg":"ok"}

    会被转码成:

    {%22code%22:0,%20%22msg%22:%22ok%22}

    然后,将数据组装成http协议发给客户端,给客户端的应答协议与http请求协议有一点点差别,就是将请求的url路径换成所谓的http响应码,如200表示应答正常返回、404页面不存在。应答协议格式如下:

    GET或POST 响应码 HTTP协议版本号
    字段1名: 字段1值\r\n
    字段2名: 字段2值\r\n
          ...
    字段n名 : 字段n值\r\n
    \r\n
    http协议包体内容

    举个例子如:

    HTTP/1.1 200 OK\r\n
    Content-Type: text/html\r\n
    Content-Length:42\r\n
    \r\n
    {%22code%22:%200,%20%22msg%22:%20%22ok%22}

    注意,包头中的Content-Length长度必须正好是包体{%22code%22:%200,%20%22msg%22:%20%22ok%22}的长度,这里是42。这也符合我们浏览器的返回结果:


    当然,需要注意的是,我们一般说http连接一般是短连接,这里我们也实现了这个功能(看上面的代码:conn->forceClose();),不管一个http请求是否成功,服务器处理后立马就关闭连接。

    当然,这里还有一些没处理好的地方,如果你仔细观察上面的代码就会发现这个问题,就是不满足一个http包头时的处理,如果某个客户端(不是使用浏览器)通过程序模拟了一个连接请求,但是迟迟不发含有\r\n\r\n的数据,这路连接将会一直占用。我们可以判断收到的数据长度,防止别有用心的客户端给我们的服务器乱发数据。我们假定,我们能处理的最大url长度是2048,如果用户发送的数据累积不含\r\n\r\n,且超过2048个,我们认为连接非法,将连接断开。代码修改成如下形式:

    void HttpSession::OnRead(const std::shared_ptr<TcpConnection>& conn, Buffer* pBuffer, Timestamp receivTime)
    {
        //LOG_INFO << "Recv a http request from " << conn->peerAddress().toIpPort();
        
        string inbuf;
        //先把所有数据都取出来
        inbuf.append(pBuffer->peek(), pBuffer->readableBytes());
        //因为一个http包头的数据至少\r\n\r\n,所以大于4个字符
        //小于等于4个字符,说明数据未收完,退出,等待网络底层接着收取
        if (inbuf.length() <= 4)
            return;
    
        //我们收到的GET请求数据包一般格式如下:
        /*
        GET /register.do?p={%22username%22:%20%2213917043329%22,%20%22nickname%22:%20%22balloon%22,%20%22password%22:%20%22123%22} HTTP/1.1\r\n
        Host: 120.55.94.78:12345\r\n
        Connection: keep-alive\r\n
        Upgrade-Insecure-Requests: 1\r\n
        User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.146 Safari/537.36\r\n
        Accept-Encoding: gzip, deflate\r\n
        Accept-Language: zh-CN, zh; q=0.9, en; q=0.8\r\n
        \r\n
         */
        //检查是否以\r\n\r\n结束,如果不是说明包头不完整,退出
        string end = inbuf.substr(inbuf.length() - 4);
        if (end != "\r\n\r\n")
            return;
        //超过2048个字符,且不含\r\n\r\n,我们认为是非法请求
        else if (inbuf.length() >= MAX_URL_LENGTH)
        {
            conn->forceClose();
            return;
        }
    
        //以\r\n分割每一行
        std::vector<string> lines;
        StringUtil::Split(inbuf, lines, "\r\n");
        if (lines.size() < 1 || lines[0].empty())
        {
            conn->forceClose();
            return;
        }
    
        std::vector<string> chunk;
        StringUtil::Split(lines[0], chunk, " ");
        //chunk中至少有三个字符串:GET+url+HTTP版本号
        if (chunk.size() < 3)
        {
            conn->forceClose();
            return;
        }
    
        LOG_INFO << "url: " << chunk[1] << " from " << conn->peerAddress().toIpPort();
        //inbuf = /register.do?p={%22username%22:%20%2213917043329%22,%20%22nickname%22:%20%22balloon%22,%20%22password%22:%20%22123%22}
        std::vector<string> part;
        //通过?分割成前后两端,前面是url,后面是参数
        StringUtil::Split(chunk[1], part, "?");
        //chunk中至少有三个字符串:GET+url+HTTP版本号
        if (part.size() < 2)
        {
            conn->forceClose();
            return;
        }
    
        string url = part[0];
        string param = part[1].substr(2);
            
        if (!Process(conn, url, param))
        {
            LOG_ERROR << "handle http request error, from:" << conn->peerAddress().toIpPort() << ", request: " << pBuffer->retrieveAllAsString();
        }
    
        //短连接,处理完关闭连接
        conn->forceClose();
    }

    但这只能解决发送非法数据的情况,如果一个客户端连上来不给我们发任何数据,这段逻辑就无能为力了。如果不断有客户端这么做,会浪费我们大量的连接资源,所以我们还需要一个定时器去定时检测哪些http连接超过一定时间内没给我们发数据,找到后将连接断开。这又涉及到服务器定时器如何设计了,关于这部分请参考我写的其他文章。

    限于作者经验水平有限,文中难免有错乱之处,欢迎拍砖。另外,关于上面的代码,可以去github上下载,地址是:

    https://github.com/baloonwj/flamingo


    全文完。


    欢迎关注公众号『easyserverdev』。如果有任何技术或者职业方面的问题需要我提供帮助,可通过这个公众号与我取得联系,此公众号不仅分享高性能服务器开发经验和故事,同时也免费为广大技术朋友提供技术答疑和职业解惑,您有任何问题都可以在微信公众号直接留言,我会尽快回复您。




    展开全文
  • HTTP服务器

    2020-11-13 17:30:09
    HTTP服务器一、HTTP服务器概述1. HTTP服务器分类1.1 JBOSS服务器1.2 Glassfish服务器1.3 Weblogic服务器1.4 Websphere服务器1.5 Tomcat服务器1.5.1 Tomcat内部工作文件结构二、HTTP网络协议包1. 分类三、HTTP请求...

    一、HTTP服务器概述

    • HTTP服务器是服务器中的一种,其行为与HTTP协议相关
    • HTTP服务器可以接收来自于浏览器发送的HTTP请求协议包,并自动对HTTP请求协议包内容进行解析,解析后,自动定位被访问的资源文件,将资源文件中的内容/命令/运行结果写入到HTTP响应协议包中,最后,将HTTP响应协议包发送回发起请求的浏览器上(HTTP服务器有求必应)

    1. HTTP服务器分类

    1.1 JBOSS服务器

    • JBOSS服务器是由JBOSS公司研发的基于J2EE的开放源代码的应用服务器,可以在任何商业应用中免费使用,但JBOSS核心服务器不包括支持servlet/JSP的web容器,一般与tomcat或jetty绑定使用

    1.2 Glassfish服务器

    • Glassfish是一款由Sun公司开发的(现由甲骨文公司赞助)开源的免费的应用服务器,它既是EJB容器也是WEB容器,Glassfish支持最新版的Java EE标准,拥有自身互联网通信规则

    1.3 Weblogic服务器

    • WebLogic是美国Oracle公司出品的一个application server,确切的说是一个基于JAVAEE架构的中间件,WebLogic是用于开发、集成、部署和管理大型分布式Web应用、网络应用和数据库应用的Java应用服务器,将Java的动态功能和Java Enterprise标准的安全性引入大型网络应用的开发、集成、部署和管理之中

    1.4 Websphere服务器

    • Websphere是IBM公司研发的一个HTTP服务器,功能强大,目前主要应用于电子商务领域,Websphere可以创建电子商务站点,把应用扩展到联合的移动设备,整合已有的应用并提供自动业务流程,主要安装在Linux系统中,不适合安装在Windows中

    1.5 Tomcat服务器

    • Tomcat是Apache的开源项目,是一个轻量级的应用服务器,最新的Servlet 和JSP规范总是能在Tomcat中得到体现。因为Tomcat技术先进、性能稳定,而且开源,资源占用小因而深受Java爱好者的喜爱并得到了部分软件开发商的认可,成为目前比较流行的Web应用服务器

    1.5.1 Tomcat内部工作文件结构

    • bin 管理命令中心
    • conf 配置文件
    • lib 运行加载jar包(依赖)
    • logs 运行过程中产生的日志文件
    • temp 运行过程中产生的临时文件
    • webapps 默认应用部署目录,存放已编译项目
    • work 供web应用使用,存放运行时编译的文件(由各种jsp生成的servlet)

    二、HTTP网络协议包

    • 在基于B/S结构的互联网通信,所有在网络中传递的信息都是保存在HTTP网络协议包

    1. 网络协议包

    • 网络协议包是一组有规律的二进制数据,这组数据存在于固定空间,每一个空间存放特定信息,这样接收方在接收网络协议包之后,就可以到固定空间得到对应信息,网络协议包极大降低了接收方对接收二进制数据编译解析的难度
    0000IP地址0000端口号0000资源文件名0000
    

    1. 分类

    • HTTP请求协议包
    • HTTP响应协议包

    三、HTTP请求协议包

    1. 在浏览器准备发送请求时,负责创建一个HTTP请求协议包
    2. 浏览器将请求信息以二进制形式保存在HTTP请求协议包的各个空间
    3. 浏览器负责将HTTP请求协议包发送到指定服务端计算机

    四、HTTP响应协议包

    1. HTTP服务器接收到请求协议包之后自动解析请求信息,定位到被访问的资源文件
    2. HTTP服务器将定位到的资源文件内容或资源文件命令以二进制形式写入到HTTP响应协议包各个空间
    3. HTTP服务器负责将HTTP响应协议包发送回发起请求的浏览器上

    五、HTTP请求协议包内部空间

    • 按照自上而下分为4个空间

    1. 请求行

    {
    	URL:请求地址
    	method:请求方式
    }
    

    2. 请求头

    {
    	请求参数信息(GET,即GET要求浏览器将请求参数信息写入到请求头)
    }
    

    3. 空白行

    {
    	没有任何内容,起到隔离作用(隔离请求头与请求体)
    }
    

    4. 请求体

    {
    	请求参数信息(POST,即POST要求浏览器将请求参数信息写入到请求体)
    }
    
    • 空白行与请求体不能直接观察到

    六、HTTP响应协议包内部空间

    • 按照自上而下分为4个空间

    1. 状态行

    {
    	HTTP状态码
    }
    

    2. 响应头

    {
    	content-type(指定浏览器采用对应编译器对响应体二进制数据进行解析,比如text/html;charset=utf-8、image/png)
    }
    

    3. 空白行

    {
    	没有任何内容,起到隔离作用(隔离请求头与请求体)
    }
    

    4. 响应体

    {
    	被访问静态资源文件内容/命令
    	被访问动态资源文件运行结果
    }
    
    展开全文
  • 用C++实现HTTP服务器 - Windows平台(开放源代码)

    万次阅读 多人点赞 2011-07-08 17:19:47
    用C++实现HTTP服务器 - Windows平台 软件名: Que's HTTP Server (点击下载最新版含源代码) 作者: 阙荣文 - Que's C++ Studio  版权说明: 免费,开放源代码,禁止用作商业用途. 日期: 2011.7.8 1
  • 基于HTTP实现的小型web服务器

    千次阅读 2018-09-12 22:26:09
    主要流程为:服务器获得请求–&gt;响应并处理请求–&gt;返回结果。 完整的HTTP过渡到TCP实现客户端与服务器的交互过程 1.当客户端执行网络请求的时候,从url中解析出url的主机名,并将主机地址转换成ip 2....
  • 什么是http服务器

    万次阅读 多人点赞 2019-02-25 10:13:43
    本篇文章旨在从服务器后台开发的角度剖析一个简单的http服务器的运行原理. 我们知道浏览器是http(s)的客户端,目的是连接远程的http服务器,然后服务器返回浏览器数据.浏览器接收数据解析数据之后展现出来.我们看到的...
  • HTTP服务器(一)

    千次阅读 2019-02-01 22:10:58
    基本疑问知识点荟萃
  • http服务器实现(一)

    千次阅读 2017-12-28 12:01:30
    深入学习http服务器,这是本文的目的,而不是实现一个真正可用的http服务器。毕竟实现一个成熟可用http服务器的难度很大。软件都经历过很多版本的迭代,在不断测试、bug调试和完善功能的过程中,最终才变得成熟可用...
  • WEB/HTTP服务器搭建

    万次阅读 2019-01-19 21:29:25
    HTTP 对于软件都有服务和客户,有服务端和客户端 服务 就是在操作系统运行一个或者多个程序,并为客户端提供相应所需的服务 协议 就是计算机网络中进行数据交换而建立的规则、标准或约定的集合。只有遵守这个...
  • 基于HTTP协议实现的小型web服务器

    万次阅读 多人点赞 2017-04-28 07:37:28
    1、实现最基本的HTTP/1.0版本的web服务器,客户端能够使用GET、POST方法请求资源 2、服务器将客户请求的资源以html页面的形似呈现,并能够进行差错处理(如:客户请求的资源不存在时,服务器能够返回一个404的页面...
  • C++socket网络编程大全实战http服务器(支持php)视频培训教程概况:本课程会同时演示在linux和windows中的编程,课程中的线程和正则表达式都使用c++提供库。本课程包含了socket网络编程常用的所有特性,包括tcp、udp...
  • 区分Web服务器、HTTP服务器、应用程序服务器

    万次阅读 多人点赞 2018-04-04 16:58:08
    进程听到和看到web服务器、HTTP服务器、应用程序服务器,但一直不知道它们有什么区别,迷惑了好久,今天查看的很多博客,终于算是梳理通了,下面我就来总结一下它们的区别,顺别了解一些服务器。 首先我们要知道web...
  • 一 常见的WEB服务器和应用服务器  在UNIX和LINUX平台下使用最广泛的免费web服务器是W3C、NCSA和APACHE服务器,而Windows平台NT/2000/2003使用IIS的WEB服务器。  在选择使用WEB服务器应考虑的本身特性因素有...
  • 在之前的博文中, 我陆续说过如何搭建ftp, sftp, tftp服务器, 在本文中, 我们来继续聊如何实战搭建一个http服务器http服务器有很多种, 如tomcat,apache等, 然而, 很多新手无法搭建和配置成功, 颇为受挫,...
  • WEB服务器、应用程序服务器、HTTP服务器有何区别? IIS、Apache、Tomcat、Weblogic、WebSphere都各属于哪种服务器? 这些问题困惑了很久,今天终于梳理清楚了: Web服务器的基本功能就是提供Web信息浏览服务...
  • Java 简单实现HTTP服务器

    千次阅读 2019-01-03 16:56:34
    HTTP服务器  解释: 个人理解,http服务器就是解析http请求信息,并解析信息;然后根据信息做后续事情。   Http请求格式与响应格式      核心代码   package com_2.Httpserver; import java.io....
  • 使用C#开发HTTP服务器系列之更简单的实现方式

    万次阅读 热门讨论 2016-07-02 15:56:45
    到目前为止,我已经发布了3篇HTTP服务器开发的系列文章。对我个人而言,我非常享受这个从无到有的过程,或许我现在写的这个Web服务器有各种不完美的因素,可是当有一天我需要一个轻量级的服务器的时候,我在无形中...
  • Web服务器、HTTP服务器及应用服务器有何区别?Apache、Nginx、IIS、Tomcat、JBoss、Weblogic、WebSphere 都各属于哪种服务器? Web服务器是指驻留在Internet上的计算机程序,它的基本功能是提供Web信息浏览服务。...
  • HTTP服务器和Web应用服务器用什么区别? HTTP服务器也称为Web服务器,主要功能是提供网上信息浏览服务,例如Apache,Nginx,IIS是比较常用的HTTP服务器;使用浏览器访问Web站点或者Web应用,则必须通过HTTP服务器; ...

空空如也

1 2 3 4 5 ... 20
收藏数 2,503,790
精华内容 1,001,516
关键字:

http服务器