每一个码农可能都会偶尔有本身作一个经常使用软件的想法,好比操做系统,编译器,邮件服务器/客户端,文字编辑器等等。这里面有些很难,好比操做系统,作一个最简单的也要付出很大的努力,但是大部分经常使用工具都是能够比较容易的作一个简易版本(固然也是只能玩玩而已)。因而我作了一个很是简陋的WEB服务器 —— TinyWS。这里主要是记录下本身整个过程当中的一些想法。html
TinyWS是用C++”从头开始“作的,也就是说,除了C/C++的标准库和操做系统的系统调用,并无使用第三方库。我并不喜欢C++(甚至有些厌恶其纷繁复杂的语法规则),正因如今后,虽然其是个人工做语言,但我也学的很粗糙。此次使用它主要也是为了本身能学习一下吧,毕竟拿了公司的钱,hee。linux
若是使用Python等其余”高级“的语言,会更快的实现,事实上几乎全部的WEB框架都会自带一个(固然都比TinyWS强大的多)。但若是使用这些语言,恐怕也很难真正的”从头开始“。git
目前,代码已经托管在 https://git.oschina.net/augustus/TinyWS.gitubuntu
能够用git clone下来。因为我可能会偶尔作一些修改,不能保证git 库上的代码与blog里的彻底一致(实际上也不可能把全部的代码都贴在这里)。另外,TinyWS是基于linux写的(ubuntu 14.10 + eclipse luna,eclipse工程我也push到了git库),故在Windows上可能没法正常编译(主要是系统调用 部分可能会不一样)。浏览器
WEB的原理很简单,你们都懂,我就简单写几句,不然直接贴代码可能比较突兀。服务器
WEB实际上也是一个客户端/服务器的程序,而它们之间基本使用HTTP/HTTPS/FTP等协议通讯。协议不过是数据传输的一种方式,而对于传输的内容来讲,WEB基本是html文档,固然也能够传其余的任何文件,不过做为一个玩具,TinyWS只支持HTTP协议。网络
WEB的客户端就是浏览器,实质是一个html的解释器,而咱们要作的,就是提供一个服务器,让浏览器能够访问到HTML文档。浏览器是经过uri来访问服务器端的资源,好比一个保存在服务器上的index.html文档,在浏览器端,可使用http://serverip:port/index.html 这样的方式就能够取回这个文档并解析。咱们要解决的问题其实就是浏览器发出这个请求以后,给予正确的回应。框架
咱们知道主机之间的网络通讯实际上最终都是经过socket传数据。而socket的本质是操做系统内核实现一个映射,使得用户程序使用网络就像使用本地文件同样。即便用socket打开一个端口后,会返回一个文件描述符,以后全部的操做都和读写一个本地文件彻底相同了。了解了这个,实际上咱们就已经解决了一半的问题。eclipse
另外一半的问题就是咱们如何实现HTTP协议。好在HTTP是一个比较简单的协议,其核心是一个”请求与应答“的过程,”请求“是一些称为”方法“的操做过程,实际上就是告诉服务器,要请求服务器返回某资源(uri)或者对资源进行某些操做。经常使用的方法就是GET和POST,目前TinyWS只实现了GET方法,其余的方法可能后面也会作一下吧。socket
对于socket和HTTP,有许多专题能够查,这里就不罗嗦了。
TinyWS核心的业务实际就是接收HTTP请求,并给予正确的应答,因此这里先从上层业务讲起吧。TinyWS运行以后,首先会打开socket并监听某端口,以后就会运行RequestManager的run方法,不断的等待HTTP请求到来。请求到来以后,会解析内容,分析出客户端的请求方法和uri,从而交给相关的”方法“去处理。
// RequestManager.h class RequestManager { public: RequestManager(int connfd); void run(); private: Request* getRequestHandle(); private: int fileDescriptor; Request* request; };
其中Request 就是具体方法的基类,其子类能够是GET,POST等等。
// RequestManager.cpp namespace { class Parser { public: Parser(int connfd) { parseRequestHeaders(connfd); } const std::string getMethodName() { return method; } const std::string getUri() { return uri; } private: void parseRequestHeaders(int fd) { IoReader reader(fd); std::vector<std::string> header; reader.getLineSplitedByBlank(header); method = header[0]; uri = header[1]; version = header[2]; } private: std::string method; std::string uri; std::string version; }; } RequestManager::RequestManager(int connfd) : fileDescriptor(connfd), request(0) { } void RequestManager::run() { if(getRequestHandle()) request->execute(); } Request* RequestManager::getRequestHandle() { Parser parser(fileDescriptor); return request = RequestCreater::getRequestHandler(parser.getMethodName(), fileDescriptor, parser.getUri()); }
在CPP文件中,首先要解析客户端的请求数据,分析出method,uri,version(协议版本,这里实际上并无用到)。这个工做有Parser类完成,因为只有这一处使用,封在了匿名namespace中。解析中使用了IoReader类,它负责从socket读入数据,封装了底层的IO操做,这个后面再说。
回到正题。RequestManager的实现中,其实使用了一个工厂类( RequestCreater),根据解析出的method,创造不一样的方法实例,这里虽然只支持GET,但仍然使用了工厂,是考虑到后面还会实现POST等其余方法,应该也不算过分设计吧,hee。
// Request.h
class Request { public: void init(int fd, std::string uri); void execute(); virtual ~Request() { } protected: int getFileDescriptor() const; const std::string& getUri() const; private: virtual void doExecute() = 0; private: int fileDescriptor; std::string uri; };
Request是一个抽象类,每个子类都须要实现doExecute方法才能实例化。这里也使用了一个简单的”模板方法“,让整个继承体系对外接口统一。
// Request.cpp void Request::init(int fd, std::string uri) { this->fileDescriptor = fd; this->uri = uri; } void Request::execute() { doExecute(); } int Request::getFileDescriptor() const { return fileDescriptor; } const std::string& Request::getUri() const { return uri; }
真正干活的是Request的子类GetRequest。不过不早了,今天先到这里,下次再说吧。