2019年3月7日 星期四

簡單的 http server

用 c++ 的 thread 寫一個 http 伺服器:
// tcpserver.cpp
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <stdarg.h>
#include <thread>
using namespace std;
template<class T>
class superBuffer   {
    private:
        unsigned msize;
        T*          address;
    public:
        const unsigned &length = msize;
        T*    const    &array  = address;
        ~superBuffer()              { if (address != nullptr) delete [ ] address; }
        superBuffer(unsigned len){ msize = len; address = new T [msize+1]; }
 };
class strBuffer: public superBuffer<char> {
    public:
        unsigned size;
        void  flush()                            {size = 0; array[0] = 0;}
        strBuffer(int len): superBuffer(len)    {flush() ;              }
        strBuffer & operator ~ (void)           {flush() ; return *this;}
        strBuffer & operator - (unsigned len)   {if (size >= len) size -= len; array[size]=0; return *this;}
        strBuffer & operator + (const char *src){ return (*this)(src); }     
        strBuffer & operator + (strBuffer &msg) { return (*this)(msg.array); }
        strBuffer & operator ( )(const char *fmt,...){ // object can be a callable function
            va_list  args;
            va_start(args, fmt);
            size += vsprintf(array + size, fmt, args);
            va_end(args);  
            return *this;
          }
 };
bool telnet(char * buffer, const char *cmd)     {
        if (buffer[0]==cmd[0] && buffer[1]==cmd[1] && buffer[2]==cmd[2] && buffer[3]==cmd[3])        return true;
        return false;
 }
char *httpGET(char * buffer, int bufsize) {
        auto src = buffer;
        if (bufsize >=15 && src[0]=='G' && src[1]=='E' && src[2]=='T' && src[3]==' ') {
            char *header = strstr(src,"HTTP/1.");
            if (header == nullptr) return nullptr;  
            src += 4;
            int len = header - src - 1;
            for (int i=0; i<len; i++) buffer[i] = src[i];
            buffer[len] = 0;
            bufsize = len;
          return header;
        }
        return nullptr;
 }
bool httpSendDoc(int fd, bool ok = true)            {
        auto msg = strBuffer(32);
        if (ok) msg("HTTP/1.0 200 OK\n");
        else    msg("HTTP/1.0 404 Not Found\n");
        auto str = strBuffer(256);
        str + msg;
        str + "Server: Welcome\n";
        str + "Content-Type: text/html\n";
        str ( "Content-Length: %11ld\n", msg.size);
        str + "Connection: close\n\n";
        str + msg;
        write(fd, str.array, str.size);
        return ok;
 }
bool httpSendfile(int fd, char *pathsrc)  {
        auto str = strBuffer(2000);
        str + "."  + pathsrc;
        FILE *fin = fopen(str.array, "rb");
        if (fin == nullptr)    return httpSendDoc(fd, false);      
        fseek(fin,0, SEEK_END);
        long fsize = ftell(fin);
        fseek(fin,0, SEEK_SET);
        (~str)+ "HTTP/1.0 200 OK\n";
        str   + "Server: Welcome\n";
        str   + "Cache-Control: must-revalidate, post-check=0, pre-check=0\n";
        str   + "Expires: 0\n";
        str   + "Content-Type: application/octet-stream\n";
        str   ( "Content-Length: %11ld\n", fsize);//bug: change %11d to %11ld
        str   + "Content-Disposition: attachment; filename=tempxxxx\n\n";      
        // bug: need double \n\n at the end of header to start file transfer  
        write(fd, str.array, str.size);
        do  {
            str.size = fread(str.array, 1, 2000, fin);
            if( str.size > 0 ) write(fd, str.array, str.size);              
        } while (str.size == 2000);
        fclose(fin);     
        return true;
 }
class tcpServer {
    private:
        static  bool serverRunning;
        static  int  sessions;
        static  void tcpSession(int fd);
        static  bool readEvent(int fd, int timeout);
        int     tcpPort;
    public:
        tcpServer(int port) {        tcpPort = port;         }  
        void operator ( ) ( ) { // this is a functor
            int servicefd = socket(AF_INET, SOCK_STREAM, 0);
            struct     sockaddr_in host;
            host.sin_family      = AF_INET;    // IPv4
            host.sin_port          = htons(tcpPort);
            inet_pton(AF_INET, "127.0.0.1", &host.sin_addr) ;
            socklen_t hostsize = sizeof(host);
            int  ok = 1;
            setsockopt(servicefd, SOL_SOCKET, SO_REUSEADDR, &ok, sizeof(ok));
            bind(servicefd, (sockaddr *) &host, sizeof(host));
            listen(servicefd, 10);
            while (serverRunning) try    {
                if (readEvent(servicefd, 2) && sessions<10) {
                    thread(tcpSession, accept(servicefd, (sockaddr *) &host, &hostsize)).detach();
                }
            } catch(const char *msg) { printf("%s\n",msg);    break;   }
            while (sessions > 0) sleep(1);
            close(servicefd);
        }
 };
int  tcpServer::sessions = 0;
bool tcpServer::serverRunning = true;
bool tcpServer::readEvent(int fd, int timeout) {
        fd_set     rdset;
        FD_ZERO(&rdset);
        FD_SET(fd, &rdset);
        timeval timer;
        timer.tv_sec  = timeout;
        timer.tv_usec = 0;    // + 0u second
        if (select(fd+1, &rdset, 0, 0, &timer) < 0) throw "SOCKET ERROR!";
        if (FD_ISSET(fd, &rdset)) return true;    // socket read
        return false;// socket timeout
 }
void tcpServer::tcpSession(int fd) {
    sessions++;
    auto const buffer = new char[2000];
    while (serverRunning) try {
        if (readEvent(fd, 5)) {
            int size = read(fd, buffer, 1999);
            if (size==0)         throw "Read size=0";
            buffer[size] =0;
            if (telnet(buffer, "exit"))            serverRunning = false;
            else if (telnet(buffer, "quit"))          httpSendDoc(fd);
            else if (httpGET(buffer, size)) {
                if( buffer[0]=='/' && buffer[1]==0 ) httpSendDoc(fd);
                else                         httpSendfile(fd, buffer);
            }
            throw "Connection: close";
        } else throw "Time is up";;
    } catch (const char *msg) { printf("%s\n",msg); break;    }
    delete [] buffer;
    close(fd);
    sessions--;
 }
int main( ){
    auto A  = tcpServer(5000); // 構建 port = 5000 的 functor
    auto tA = thread(A);            // 啟動 functor 的 thread
    tA.join( );// 等待 thread 結束
 }
用 g++  tcpserver.cpp  -pthread && ./a.out 編譯並執行

沒有留言: