网络作业5简易web服务器

发布于 2019-06-09  16 次阅读


https://github.com/likole/web_server

要求实现简易的web服务器和浏览器,时间原因就写了个web服务器

  • 支持get方法(支持查询字符串)和post方法(仅支持x-www-urlencoded格式)
  • 支持默认文档:".../"等价于".../index.html"(优先级更高)或".../index.php"
  • 根据Host字段来确定读取哪个目录下的文件,如果http协议为1.0版本或未提供Host字段,则使用默认路径
  • 可响应图片等
  • 可实现cookie机制

由于只是上机作业且赶时间,就粗略实现了功能没有做什么优化

对于php文件,通过fastcgi协议从php-fpm获取响应,该部分代码如下:

    void getFromUpstream() {
        Upstream fc;
        fc.setRequestId(rand());
        fc.startConnect();
        fc.sendStartRequestRecord();
        char abs_path_buff[1024];
        if (!realpath(getPath().c_str(), abs_path_buff)) {
            responseCode = 404;
            if (DEBUG)cout << "404 文件不存在" << endl;
            return;
        }
        fc.sendParams("SCRIPT_FILENAME", abs_path_buff);
        fc.sendParams("REQUEST_METHOD", method);
        if (methodType == GET) {
            fc.sendParams("QUERY_STRING", paramsString);
        }
        if (methodType == POST && requestHeaders.count("Content-Type")) {
            fc.sendParams("CONTENT_TYPE", requestHeaders["Content-Type"]);
        }
        if (methodType == POST && requestHeaders.count("Content-Length")) {
            fc.sendParams("CONTENT_LENGTH", requestHeaders["Content-Length"]);
        }
        fc.sendParams("REQUEST_URI", url);
        fc.sendParams("DOCUMENT_URI", url);
        if (requestHeaders.count("Cookie")) {
            fc.sendParams("HTTP_COOKIE", requestHeaders["Cookie"]);
        }
        fc.sendEmptyParams();
        if (methodType == POST) {
            fc.sendData(paramsString);
        }
        fc.sendEndRequestRecord();
        string out, error;
        fc.readFromPhp(out, error);
        stringstream ss(out);
        string t;
        while (getline(ss, t)) {
            if (t == "\r")break;
            t = t.substr(0, t.length() - 1);
            string k = t.substr(0, t.find(": ")), v = t.substr(t.find(": ") + 2);
            responseHeaders[k] = v;
        }
        while (getline(ss, t)) {
            responseBody += t + "\n";
        }
    }

Upstream类实现如下:

#ifndef WEB_SERVER_UPSTREAM_H
#define WEB_SERVER_UPSTREAM_H

#define FCGI_BEGIN_REQUEST       1
#define FCGI_ABORT_REQUEST       2
#define FCGI_END_REQUEST         3
#define FCGI_PARAMS              4
#define FCGI_STDIN               5
#define FCGI_STDOUT              6
#define FCGI_STDERR              7
#define FCGI_DATA                8
#define FCGI_GET_VALUES          9
#define FCGI_GET_VALUES_RESULT  10
#define FCGI_UNKNOWN_TYPE       11

typedef struct {
    unsigned char version;              //版本
    unsigned char type;                 //操作类型
    unsigned char requestIdB1;          //请求id
    unsigned char requestIdB0;
    unsigned char contentLengthB1;      //内容长度
    unsigned char contentLengthB0;
    unsigned char paddingLength;        //填充字节的长度
    unsigned char reserved;             //保留字节
} FCGI_Header;

typedef struct {
    unsigned char roleB1;
    unsigned char roleB0;
    unsigned char flags;
    unsigned char reserved[5];
} FCGI_BeginRequestBody;

typedef struct {
    FCGI_Header header;
    FCGI_BeginRequestBody body;

} FCGI_BeginRequestRecord;

typedef struct {
    unsigned char appStatusB3;      //结束状态,0为正常
    unsigned char appStatusB2;
    unsigned char appStatusB1;
    unsigned char appStatusB0;
    unsigned char protocolStatus;   //协议状态
    unsigned char reserved[3];
} FCGI_EndRequestBody;

typedef struct {
    FCGI_Header header;         //结束头
    FCGI_EndRequestBody body;   //结束体
} FCGI_EndRequestRecord;

typedef struct {
    unsigned char type;
    unsigned char reserved[7];
} FCGI_UnknownTypeBody;

typedef struct {
    FCGI_Header header;
    FCGI_UnknownTypeBody body;
} FCGI_UnknownTypeRecord;

#include <string>
#include <assert.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <errno.h>
#include <string.h>
#include <stdio.h>

using namespace std;

class Upstream {
private:
    int sockfd;
    int requestId;

    static const int CONTENT_BUFF_LEN = 1024;

    FCGI_Header makeHeader(int type,
                           int contentLength, int paddingLength) {
        FCGI_Header header;
        header.version = 1;
        header.type = (unsigned char) type;
        header.requestIdB1 = (unsigned char) ((requestId >> 8) & 0xff);
        header.requestIdB0 = (unsigned char) (requestId & 0xff);
        header.contentLengthB1 = (unsigned char) ((contentLength >> 8) & 0xff);
        header.contentLengthB0 = (unsigned char) (contentLength & 0xff);
        header.paddingLength = (unsigned char) paddingLength;
        header.reserved = 0;
        return header;
    }

    FCGI_BeginRequestBody makeBeginRequestBody() {
        FCGI_BeginRequestBody body;
        body.roleB1 = (unsigned char) ((1 >> 8) & 0xff);
        body.roleB0 = (unsigned char) (1 & 0xff);
        body.flags = (unsigned char) (0);
        bzero(&body.reserved, sizeof(body.reserved));
        return body;
    }

    bool makeNameValueBody(std::string name, int nameLen,
                           std::string value, int valueLen,
                           unsigned char *bodyBuffPtr, int *bodyLenPtr) {
        unsigned char *startBodyBuffPtr = bodyBuffPtr;
        if (nameLen < 128) {
            *bodyBuffPtr++ = (unsigned char) nameLen;
        } else {
            *bodyBuffPtr++ = (unsigned char) ((nameLen >> 24) | 0x80);
            *bodyBuffPtr++ = (unsigned char) (nameLen >> 16);
            *bodyBuffPtr++ = (unsigned char) (nameLen >> 8);
            *bodyBuffPtr++ = (unsigned char) nameLen;
        }
        if (valueLen < 128) {
            *bodyBuffPtr++ = (unsigned char) valueLen;
        } else {
            *bodyBuffPtr++ = (unsigned char) ((valueLen >> 24) | 0x80);
            *bodyBuffPtr++ = (unsigned char) (valueLen >> 16);
            *bodyBuffPtr++ = (unsigned char) (valueLen >> 8);
            *bodyBuffPtr++ = (unsigned char) valueLen;
        }

        for (auto ch : name) {
            *bodyBuffPtr++ = ch;
        }
        for (auto ch : value) {
            *bodyBuffPtr++ = ch;
        }
        *bodyLenPtr = bodyBuffPtr - startBodyBuffPtr;
        return true;
    }

public:
    Upstream() {
        sockfd = 0;
        requestId = 0;
    }

    ~Upstream() {
        close(sockfd);
    }

    void setRequestId(int requestId) { this->requestId = requestId; }

    void startConnect() {
        struct sockaddr_un server_address;
        sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
        assert(sockfd > 0);
        bzero(&server_address, sizeof(server_address));
        server_address.sun_family = AF_UNIX;
        strcpy(server_address.sun_path, "/tmp/php-cgi-72.sock");
        int result = connect(sockfd, (struct sockaddr *) &server_address, sizeof(server_address));
        assert(result >= 0);
    }

    bool sendStartRequestRecord() {
        FCGI_BeginRequestRecord beginRecord;

        beginRecord.header = makeHeader(FCGI_BEGIN_REQUEST, sizeof(beginRecord.body), 0);
        beginRecord.body = makeBeginRequestBody();

        int ret = write(sockfd, (char *) &beginRecord, sizeof(beginRecord));
        assert(ret == sizeof(beginRecord));
        return true;
    }

    bool sendParams(string name, string value) {
        unsigned char bodyBuff[1024];
        bzero(bodyBuff, sizeof(bodyBuff));
        int bodyLen;
        makeNameValueBody(name, name.size(), value, value.size(), bodyBuff, &bodyLen);
        FCGI_Header nameValueHeader;
        nameValueHeader = makeHeader(FCGI_PARAMS, bodyLen, 0);
        int nameValueRecordLen = bodyLen + 8;
        char nameValueRecord[nameValueRecordLen];
        memcpy(nameValueRecord, (char *) &nameValueHeader, 8);
        memcpy(nameValueRecord + 8, bodyBuff, bodyLen);
        int ret = write(sockfd, nameValueRecord, nameValueRecordLen);
        assert(ret == nameValueRecordLen);
        return true;
    }

    bool sendEmptyParams() {
        FCGI_Header nameValueHeader = makeHeader(FCGI_PARAMS, 0, 0);
        int ret = write(sockfd, (char *) &nameValueHeader, 8);
        assert(ret == 8);
        return true;
    }

    bool sendData(string data) {
        FCGI_Header dataHeader = makeHeader(FCGI_STDIN, data.length(), 0);
        int ret = write(sockfd, (char *) &dataHeader, 8);
        assert(ret == 8);
        ret = write(sockfd, data.c_str(), data.length());
        assert(ret == data.length());
        return true;
    }

    bool sendEndRequestRecord() {
        FCGI_Header endHeader;
        endHeader = makeHeader(FCGI_PARAMS, 0, 0);
        int ret = write(sockfd, (char *) &endHeader, 8);
        assert(ret == 8);
        return true;
    }

    bool readFromPhp(string &ctx, string &error) {
        FCGI_Header responderHeader;
        char content[CONTENT_BUFF_LEN];
        int contentLen;
        char tmp[8];
        int ret;
        while (read(sockfd, &responderHeader, 8) > 0) {
            if (responderHeader.type == FCGI_STDOUT) {
                contentLen = (responderHeader.contentLengthB1 << 8) + (responderHeader.contentLengthB0);
                bzero(content, CONTENT_BUFF_LEN);
                ret = read(sockfd, content, contentLen);
                assert(ret == contentLen);
                ctx += content;
                if (responderHeader.paddingLength > 0) {
                    ret = read(sockfd, tmp, responderHeader.paddingLength);
                    assert(ret == responderHeader.paddingLength);
                }
            } else if (responderHeader.type == FCGI_STDERR) {
                contentLen = (responderHeader.contentLengthB1 << 8) + (responderHeader.contentLengthB0);
                bzero(content, CONTENT_BUFF_LEN);
                ret = read(sockfd, content, contentLen);
                assert(ret == contentLen);
                error += content;
//                fprintf(stdout, "error:%s\n", content);
                if (responderHeader.paddingLength > 0) {
                    ret = read(sockfd, tmp, responderHeader.paddingLength);
                    assert(ret == responderHeader.paddingLength);
                }
            } else if (responderHeader.type == FCGI_END_REQUEST) {
                FCGI_EndRequestBody endRequest;
                ret = read(sockfd, &endRequest, sizeof(endRequest));
                assert(ret == sizeof(endRequest));
            }
        }
        return true;
    }
};


#endif //WEB_SERVER_UPSTREAM_H