代码已经托管在 https://git.oschina.net/augustus/TinyWS.git
可以用git clone下来。由于我可能会偶尔做一些修改,不能保证git 库上的代码与blog里的完全一致(实际上也不可能把所有的代码都贴在这里)。另外,TinyWS是基于linux写的(ubuntu 14.10 + eclipse luna,eclipse工程我也push到了git库), 故在Windows上可能无法正常编译 (主要是系统调用 部分可能会不同)。
前面的内容可参考上一篇 http://www.cnblogs.com/cuiluo/p/4217205.html
接上一篇,这个类就是处理GET方法的强求的。它继承自Request,需要override doExecute 方法。它的功能是解析出请求的URI,并将其传给Response类的对象进一步处理。目前的实现中,使用CGI的方式处理动态请求(所谓动态请求,就是浏览器的请求中指明要运行服务器端的某个程序,并得到返回结果),但是这部分代码其实是没有测试过的。本类中,后面三个方法都是处理动态请求的uri和参数的。
// GetRequest.h
class GetRequest : public Request { virtual void doExecute(); bool parseUri(std::string& filename, std::string& cgiargs); bool parseStaticContentUri(std::string& filename); bool parseDynamicContentUri(std::string& filename, std::string& cgiargs); void assignCigArgs(std::string& cgiargs); void doAssignCigArgs(std::string::size_type pos, std::string& cgiargs); };
在具体实现中,静态网页的存储路径是 " test-files " ,这个主要是为了测试,如果请求没有携带指定的文件名,那么就返回此路径下的 " index.html " 文件。服务于动态请求的程序都存放在 " cgi-bin " 目录下,当然,目前此路径下也没有程序。在doExecute方法中,解析了URI后,就直接创建一个Response的对象做进一步处理。
// GetRequest.cpp void GetRequest::doExecute() { std::string filename, cgiargs; bool isStatic = parseUri(filename, cgiargs); Response(getFileDescriptor(), filename, cgiargs, isStatic).respond(); } bool GetRequest::parseUri(std::string& filename, std::string& cgiargs) { if (getUri().find("cgi-bin") == std::string::npos) return parseStaticContentUri(filename); else return parseDynamicContentUri(filename, cgiargs); } bool GetRequest::parseStaticContentUri(std::string& filename) { std::string uri = getUri(); filename = "test-files" + uri; if (uri[uri.length() - 1] == '/') filename += "index.html"; return true; } bool GetRequest::parseDynamicContentUri(std::string& filename, std::string& cgiargs) { assignCigArgs(cgiargs); filename = "." + getUri(); return false; } void GetRequest::assignCigArgs(std::string& cgiargs) { std::string uri = getUri(); std::string::size_type pos = uri.find_first_of("?"); doAssignCigArgs(pos, cgiargs); } void GetRequest::doAssignCigArgs(std::string::size_type pos, std::string& cgiargs) { if (pos != std::string::npos) cgiargs = getUri().substr(pos, getUri().length() - 1); else cgiargs.clear(); }
Response类的作用是,根据请求的uri,返回被请求的文件,如果是动态请求,则执行相关的程序。
Response的设计很不好。首先,他的构造函数需要一个 bool值标识是否为静态网页,这会引起内部处理的很多if ... else分支,这其实是我十分痛恨的;其次它的一个成员是 struct stat 类型,此为linux系统调用提供的一个标识文件属性的结构,它本不应该属于这个层级,Response应该是专注业务的,和操作系统打交道的工作应该交给别人。
所以这里是后面我重构的重点,或许可以使用工厂或状态模式等方法处理一下,不过对于一个只有两种选择的状态,用工厂就显得太兴师动众了。目前打算使用状态模式,已经加了一个 enum State { STATIC, DYNAMIC},不过还没有继续。后续如果修改了代码,会提到git库,这里就不会再同步修改了。
class Response { public: Response(int fd, std::string name, std::string cgiargs, bool isStc); void respond(); private: const std::string getFiletype(); void preResond(); void respondOK(); void respondStatic(); void respondDynamic(); const void execveCgiProgram(); const void doExecveCgiProgram(); const std::string buildRespond0KHeaders(); const std::string buildRespondStaticHeaders(); const std::string buildForbiddenMsg(); const std::string buildRespondErrorHeaders(const std::string errNum, const std::string shortMsg); const std::string buildRespondErrorBody(const std::string errNum, const std::string shortMsg, const std::string longMsg); void respondError(const std::string errNum, const std::string shortMsg, const std::string longMsg); private: enum State { STATIC, DYNAMIC}; struct stat sbuf; int fileDescriptor; std::string fileName; std::string cgiArgs; bool isStatic; };
现在,关于TinyWS上层业务相关的部分就介绍完了。后面的部分会涉及到Socket和IO操作相关的内容,留作下次再说。