自主web服务器

Posted qnbk

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了自主web服务器相关的知识,希望对你有一定的参考价值。

自主web服务器

描述
采用C/S模型,编写支持中小型应用的http,并结合mysql,理解常见互联网应用行为,做完该项目,你可以从技术上完全理解从上网开始,到关闭浏览器的所有操作中的技术细节。

www

WWW是环球信息网的缩写,(亦作“Web”、“WWW”、“‘W3’”,英文全称为“World Wide Web”),中文名字为“万维网”,"环球网"等,常简称为Web。
分为Web客户端和Web服务器程序。 WWW可以让Web客户端(常用浏览器)访问浏览Web服务器上的页面。 是一个由许多互相链接的超文本组成的系统,通过互联网访问。在这个系统中,每个有用的事物,称为一样“资源”;并且由一个全局“统一资源标识符”(URI)标识;这些资源通过超文本传输协议(Hypertext Transfer Protocol)传送给用户,而后者通过点击链接来获得资源。

万维网联盟(英语:World Wide Web Consortium,简称W3C),又称W3C理事会。1994年10月在麻省理工学院(MIT)计算机科学实验室成立。万维网联盟的创建者是万维网的发明者蒂姆·伯纳斯-李。

http分层概念

特点

客户/服务器模式(B/S,C/S)

  • 简单快速,HTTP服务器的程序规模小,因而通信速度很快。
  • 灵活,HTTP允许传输任意类型的数据对象,正在传输的类型由Content-Type加以标记。
  • 无连接,每次连接只处理一个请求。服务器处理完客户的请求,并收到客户的应答后,即断开连接。采用这种方式可以节省传输时间。(http/1.0具有的功能,http/1.1兼容)
  • 无状态

http协议每当有新的请求产生,就会有对应的新响应产生。协议本身并不会保留你之前的一切请求或者响应,这是为了更快的处理大量的事务,确保协议的可伸缩性。

可是,随着web的发展,因为无状态而导致业务处理变的棘手起来。比如保持用户的登陆状态。http/1.1虽然也是无状态的协议,但是为了保持状态的功能,引入了cookie技术。

URI & URL & URN

  • URI,是uniform resource identifier,统一资源标识符,用来唯一的标识一个资源
  • URL,是uniform resource locator,统一资源定位符,它是一种具体的URI,即URL可以用来标识一个资源,而且还指明了如何locate这个资源。
  • URN,uniform resource name,统一资源命名,是通过名字来标识资源

URI是以一种抽象的,高层次概念定义统一资源标识,而URL和URN则是具体的资源标识的方式。URL和URN都是一种URI。

URL是 URI 的子集。任何东西,只要能够唯一地标识出来,都可以说这个标识是 URI 。如果这个标识是一个可获取到上述对象的路径,那么同时它也可以是一个 URL ;但如果这个标识不提供获取到对象的路径,那么它就必然不是URL 。
URI: /hello/index.html
URL: www.xxx.com:/hello/index.html

浏览器URL格式

  • HTTP(超文本传输协议)是基于TCP的连接方式进行网络连
  • HTTP/1.1版本中给出一种持续连接的机制(长链接)
  • 绝大多数的Web开发,都是构建在HTTP协议之上的Web应用

HTTP URL (URL是一种特殊类型的URI,包含了如何获取指定资源)的格式如下:

http://host[":"port][abs_path]
  • http表示要通过HTTP协议来定位网络资源
  • host表示合法的Internet主机域名或者IP地址,本主机IP:127.0.0.1
  • port指定一个端口号,为空则使用缺省端口80
  • abs_path指定请求资源的URI
  • 如果URL中没有给出abs_path,那么当它作为请求URI时,必须以“/”的形式给出,通常浏览器自动完成。

如果用户的url没有指明要访问的某种资源(路径),虽然浏览器会自动添加/ ,但是浏览器还是不知道需要访问什么资源,此时,浏览器默认返回对应服务的首页

HTTP请求与响应


利用套接字获取请求报文

Makefile:

bin=httpserver
cc=g++
LD_FLAGS=-std=c++11 -lpthread
src=main.cc 
$(bin):$(src)
		$(cc) -o $@ $^ $(LD_FLAGS)
.PHONY:clean
clean:
		rm -f  $(bin)

TcpServer.hpp

#pragma once 
#include <iostream>
#include <sys/socket.h>
#include <cstdlib>
#include <cstring>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>
#include <unistd.h>
#define Backlog 5
class TcpServer

  private:
    int port;
    int listen_sock;
    static TcpServer *svr;
  private://把tcpserver设置为单例模式
    TcpServer(int _port):port(_port),listen_sock(-1)
    
    TcpServer(const TcpServer &s)
  public:
    static TcpServer *getinstance(int port)//获取单例
    
      static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;

      if(svr == nullptr)
      
        pthread_mutex_lock(&lock);
        if(svr == nullptr)
        
          svr = new TcpServer(port);
          svr->InitServer();
        
        pthread_mutex_unlock(&lock);
      
      return svr;
    

    void InitServer()
    
      Socket();
      Bind();
      Listen();


    
    void Socket()
    
      listen_sock = socket(AF_INET,SOCK_STREAM,0);
      if(listen_sock < 0)
      
        exit(1);
      
      //socket 地址复用:服务器崩了,连接还在,此时服务器重启不了,因为监听套接字对应的端口号
      //默认是绑定的,端口没有被完全释放,导致服务器无法立即重启,所以需要地址复用
      int opt = 1;
      setsockopt(listen_sock,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));
    
    void Bind()
    
      
      struct sockaddr_in local;
      memset(&local,0,sizeof(local));
      local.sin_family = AF_INET;
      local.sin_port = htons(port);
      local.sin_addr.s_addr = INADDR_ANY;//云服务器不能直接绑定公网ip
      
      if(bind(listen_sock,(struct sockaddr*)&local,sizeof(local)) <0)
      
        std::cout<<"bind error"<<std::endl;
        exit(2);

      
     
    
    void Listen()
    
      if(listen(listen_sock,Backlog) < 0)
      
        exit(3);
      

    
    int Sock()
    
      return listen_sock;
    
    ~TcpServer()
    
      if(listen_sock >= 0)
	     close(listen_sock);
    
;

HttpServer.hpp

#pragma once 
#include "TcpServer.hpp"
#include "Protocol.hpp"
#include <iostream>
#include <pthread.h>
#define PORT 8081
class HttpServer

  private:
    int port;
    TcpServer *tcp_server;
    bool stop;
  public:
    HttpServer(int _port = PORT):port(_port),tcp_server(nullptr),stop(false)
    
    void InitServer()
    
      tcp_server = TcpServer::getinstance(port);
    
    void Loop()
    
      int listen_sock = tcp_server->Sock();
      //std::cout<<"HttpServer::Loop sock "<<listen_sock<<std::endl;
      while(!stop)
      
        
          struct sockaddr_in peer;
          socklen_t len = sizeof(peer);
          int sock = accept(listen_sock,(struct sockaddr*)&peer,&len);
          if(sock < 0)
          
            continue;
          

          int  *_sock = new int(sock);
          pthread_t tid;
          pthread_create(&tid,nullptr,Entrance::HandlerRequest,_sock);
          pthread_detach(tid);


      
    
    
    ~HttpServer()
    

;

Protocol.hpp


#pragma once 
#include <iostream>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
class Entrance

  public:
    static void *HandlerRequest(void *_sock)//处理线程
    
      int sock = *(int *)_sock;
      delete (int*)_sock;
      std::cout<<"get a new link..."<<sock<<std::endl;
#ifndef DEGUB 
#define DEBUG
      char buffer[4096];
      recv(sock,buffer,sizeof(buffer),0);
      std::cout<<"-----------begin-----------------"<<std::endl;
      std::cout<<buffer << std::endl;
      std::cout<<"-----------end-----------------"<<std::endl;
#endif 
      close(sock);
      return nullptr;
    


;

main.cc

#include <iostream>
#include <string>
#include <memory>
#include "HttpServer.hpp"

//#include "TcpServer.hpp"
static void Usage(std::string proc)

  std::cout<<"Usage: "<<proc << " port "<<std::endl;

int main(int argc,char *argv[])

  if(argc != 2)
  
    Usage(argv[0]);
    exit(4);
  
  //std::cout<<"hello http"<<std::endl;
  int port = atoi(argv[1]);
  std::shared_ptr<HttpServer> http_server(new HttpServer(port));
  http_server->InitServer();
  http_server->Loop();
  for(;;)
  

  
  return 0;


CGI

CGI(Common Gateway Interface) 是WWW技术中最重要的技术之一,有着不可替代的重要地位。CGI是外部应用程序(CGI程序)与WEB服务器之间的接口标准,是在CGI程序和Web服务器之间传递信息的过程。

首先需要理解GET方法和POST方法的区别

  • GET方法从浏览器传参数给http服务器时,是需要将参数跟到URI后面的
  • POST方法从浏览器传参数给http服务器时,是需要将参数放的请求正文的。
  • GET方法,如果没有传参,http按照一般的方式进行,返回资源即可
  • GET方法,如果有参数传入,http就需要按照CGI方式处理参数,并将执行结果(期望资源)返回给浏览器
  • POST方法,一般都需要使用CGI方式来进行处理



如何看待子CGI程序?
子CGI的标准输出是浏览器
子CGI的标准输入是浏览器

具体实现

Protocol.hpp

#pragma once 
#include <iostream>
#include <string>
#include <vector>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <string>
#include <sstream>
#include <unordered_map>
#include <sys/stat.h>
#include <fcntl.h>
#include "Util.hpp"
#include "Log.hpp"
#include <algorithm>
#include <sys/wait.h>
#include <sys/sendfile.h>
#define SEP ": "
#define OK 200
#define NOT_FOUND 404
#define BAD_REQUEST 400
#define WEB_ROOT "wwwroot"
#define HOME_PAGE "index.html"
#define HTTP_VERSION "HTTP/1.0"
#define LINE_END "\\r\\n"
#define PAGE_404 "404.html"
#define SERVER_ERROR 500
/*
class Code2Desc

  private:
    std::unordered_map<int,std::string> Code;
  public:
    Code2Desc()
    
    void InitCode2Desc()
    
      Code.insert(200,"OK");
    
    ~Code2Desc()
    
;
*/

static std::string Code2Desc(int code)

  std::string desc;
  switch(code)
  
    case 200:
      desc="OK";break;
    case 404:
      desc = "Not Found";break;
    default:
      break;
  
  return desc;

static std::string Suffix2Desc(const std::string &suffix)

  static std::unordered_map<std::string,std::string> Suffix2desc = 
    ".html","text/html",
    ".css","text/css",
    ".js","appplication/javascript",
    ".jpg","application/x-jpg",
  ;
  auto iter =  Suffix2desc.find(suffix);
  if(iter != Suffix2desc.end())
  
    return iter->second;
  
  return ".text/html";

class HttpRequest 

  public:
    std::string request_line;//请求行
    std::vector<std::string> request_header;//请求报头
    std::string blank;//空行
    std::string request_body;//正文

    //解析完毕之后的结果
    std::string method;//请求方法 GET:有正文 POST:需要读取正文
    std::string uri;//请求资源 path?args
    std::string version;//请求版本

    std::unordered_map<std::string,std::string> header_kv;//HOST: XXX.XXX Connection: XXX
    int content_length;//正文长度
    std::string path;//想访问的资源
    std::string query_string;//path后面的参数
    std::string suffix;//请求资源的后缀
    bool cgi;
    int size;
  public:
    HttpRequest():content_length(0),cgi(false)
    
    ~HttpRequest()
    
;
class HttpResponse

  public:
    std::string status_line;//状态行
    std::vector<std::string> response_header;//响应报头
    std::string blank;//空行
    std::string response_body;//正文

    int status_code;//状态码
    int fd;
    int size;//想访问的资源的大小
  public:
    HttpResponse():blank(LINE_END),status_code(OK),fd(-1)
    ~HttpResponse()
;

//读取请求,分析请求,构建响应
//IO通信
class Endpoit

  //对端:完成业务逻辑
  private:
    int sock;
    HttpRequest http_request;
    HttpResponse http_response;
  private:
    void RecvHttpRequestLine()
    
      auto &line = http_request.request_line;
      Util::Readline(sock,line);
      line.resize(line.size()-1);
      LOG(INFO,http_request.request_line);
    
    void RecvHttpRequestHeader()
    
      //如何保证请求报头已经读取完毕
      //1、空格结束 2、都是按照行进行陈列的
      std::string line;
      while(true)
      
        line.clear();
        Util::Readline(sock,line);
        if(line == "\\n")
        
        http_request.blank = line;
          break;
        
        line.resize(line.size()-1);//去掉\\n
        http_request.request_header.push_back(line);
        LOG(INFO,line);
          
    
    void ParseHttprequestLine()
    
      auto &line = http_request.request_line;
      // 方法 URI HTTP/版本
      //stringstream :默认按照空格把字符串打散切分
      std::stringstream ss(line);
      ss >> http_request.method >> http_request.uri >> http_request.version;
      auto &method = http_request.method;
      std::transform(method.begin(),method.end(),method.begin(),::toupper);
      LOG(INFO,line);

    
    void ParseHttprequestHeader()
    
      std::string key;
      std::string value;
      for(auto &iter : http_request.request_header)
      
        
        if(Util::CutString(iter,key,value,SEP))
        
          http_request.header_kv.insert(key,value);
        
      

    
    bool IsNeedRecvHttpRequestBody()
    
      auto method = http_request.method;
      if(method == "POST")
      
        auto &header_kv = http_request.header_kv;
        auto iter =  header_kv.find("Content-Length");
        if(iter != header_kv.end())
        
          //找到了
          LOG(INFO,"Post Method,Content-Length"+iter->second);
          http_request.content_length = atoi(iter->second.c_str());
          return true;
        
      
      return false;
    
    void RecvHttpRequestBody()
    
      if(IsNeedRecvHttpRequestBody())
      
        int content_length = http_request.content_length;
        auto &body = http_request.request_body;
        char ch = 0;
        while(content_length)
        
          ssize_t s = recv(sock,&ch,1,0);
          if(s > 0)
          
            body.push_back(ch);
            content_length--;
          
          else 
          
            break;
          
          
        
        //LOG(INFO,body);
      
    
    int ProcessNonCGI()
    
      //eg:  HTTP/1.0 200 OK
      http_response.fd = open(http_request.path.c_str(),O_RDONLY);
      if(http_response.fd >= 0)
      
        LOG(INFO,http_request.path+"open success!");
        /*
        //获取信息成功
        http_response.status_line = HTTP_VERSION;
        http_response.status_line += " ";
        http_response.status_line +=std::to_string(http_response.status_code);
        http_response.status_line += " ";
        http_response.status_line += Code2Desc(http_response.status_code);
        http_response.status_line += LINE_END;
        http_response.size = size;

        std::string header_line = "Content-Type: ";
        header_line += Suffix2Desc(http_request.suffix);
        header_line += LINE_END;
        http_response.response_header.push_back(header_line);
        header_line = "Content-Length: ";
        header_line += std::to_string(size);
        header_line += LINE_END;    
        http_response.response_header.push_back(header_line);
*/
        return OK;
      


      //http_response.response_body是用户层的缓冲区 :磁盘-》内核层-》用户层
      //sendfile:把数据从一个文件描述符拷贝给另一给文件描述符,只在内核进行
      介绍自主小型Web服务器实现

自主Web服务器Http_Server

自主开发第二天

自主实现的web服务器

实战项目自主web服务器

实战项目自主web服务器