📄 httpserver.cxx
字号:
/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. * * This is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this software; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, * USA. */#include <rfb/HTTPServer.h>#include <rfb/LogWriter.h>#include <rfb/util.h>#include <rdr/MemOutStream.h>#ifdef WIN32#define strcasecmp _stricmp#endifusing namespace rfb;using namespace rdr;static LogWriter vlog("HTTPServer");const int clientWaitTimeMillis = 20000;const int idleTimeoutSecs = 5 * 60;//// -=- LineReader// Helper class which is repeatedly called until a line has been read// (lines end in \n or \r\n).// Returns true when line complete, and resets internal state so that// next read() call will start reading a new line.// Only one buffer is kept - process line before reading next line!//class LineReader : public CharArray {public: LineReader(InStream& is_, int l) : CharArray(l), is(is_), pos(0), len(l), bufferOverrun(false) {} // Returns true if line complete, false otherwise bool read() { while (is.checkNoWait(1)) { char c = is.readU8(); if (c == '\n') { if (pos && (buf[pos-1] == '\r')) pos--; bufferOverrun = false; buf[pos++] = 0; pos = 0; return true; } if (pos == (len-1)) { bufferOverrun = true; buf[pos] = 0; return true; } buf[pos++] = c; } return false; } bool didBufferOverrun() const {return bufferOverrun;}protected: InStream& is; int pos, len; bool bufferOverrun;};//// -=- HTTPServer::Session// Manages the internal state for an HTTP session.// processHTTP returns true when request has completed,// indicating that socket & session data can be deleted.//class rfb::HTTPServer::Session {public: Session(network::Socket& s, rfb::HTTPServer& srv) : contentType(0), contentLength(-1), lastModified(-1), line(s.inStream(), 256), sock(s), server(srv), state(ReadRequestLine), lastActive(time(0)) { } ~Session() { } void writeResponse(int result, const char* text); bool writeResponse(int code); bool processHTTP(); network::Socket* getSock() const {return &sock;} int checkIdleTimeout();protected: CharArray uri; const char* contentType; int contentLength; time_t lastModified; LineReader line; network::Socket& sock; rfb::HTTPServer& server; enum {ReadRequestLine, ReadHeaders, WriteResponse} state; enum {GetRequest, HeadRequest} request; time_t lastActive;};// - Internal helper routinesvoidcopyStream(InStream& is, OutStream& os) { try { while (1) { os.writeU8(is.readU8()); } } catch (rdr::EndOfStream) { }}void writeLine(OutStream& os, const char* text) { os.writeBytes(text, strlen(text)); os.writeBytes("\r\n", 2);}// - Write an HTTP-compliant response to the clientvoidHTTPServer::Session::writeResponse(int result, const char* text) { char buffer[1024]; if (strlen(text) > 512) throw new rdr::Exception("Internal error - HTTP response text too big"); sprintf(buffer, "%s %d %s", "HTTP/1.1", result, text); OutStream& os=sock.outStream(); writeLine(os, buffer); writeLine(os, "Server: RealVNC/4.0"); time_t now = time(0); struct tm* tm = gmtime(&now); strftime(buffer, 1024, "Date: %a, %d %b %Y %H:%M:%S GMT", tm); writeLine(os, buffer); if (lastModified == (time_t)-1 || lastModified == 0) lastModified = now; tm = gmtime(&lastModified); strftime(buffer, 1024, "Last-Modified: %a, %d %b %Y %H:%M:%S GMT", tm); writeLine(os, buffer); if (contentLength != -1) { sprintf(buffer,"Content-Length: %d",contentLength); writeLine(os, buffer); } writeLine(os, "Connection: close"); os.writeBytes("Content-Type: ", 14); if (result == 200) { if (!contentType) contentType = guessContentType(uri.buf, "text/html"); os.writeBytes(contentType, strlen(contentType)); } else { os.writeBytes("text/html", 9); } os.writeBytes("\r\n", 2); writeLine(os, ""); if (result != 200) { writeLine(os, "<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">"); writeLine(os, "<HTML><HEAD>"); sprintf(buffer, "<TITLE>%d %s</TITLE>", result, text); writeLine(os, buffer); writeLine(os, "</HEAD><BODY><H1>"); writeLine(os, text); writeLine(os, "</H1></BODY></HTML>"); sock.outStream().flush(); }}boolHTTPServer::Session::writeResponse(int code) { switch (code) { case 200: writeResponse(code, "OK"); break; case 400: writeResponse(code, "Bad Request"); break; case 404: writeResponse(code, "Not Found"); break; case 501: writeResponse(code, "Not Implemented"); break; default: writeResponse(500, "Unknown Error"); break; }; // This return code is passed straight out of processHTTP(). // true indicates that the request has been completely processed. return true;}// - Main HTTP request processing routineboolHTTPServer::Session::processHTTP() { lastActive = time(0); while (sock.inStream().checkNoWait(1)) { switch (state) { // Reading the Request-Line case ReadRequestLine: // Either read a line, or run out of incoming data if (!line.read()) return false; // We have read a line! Skip it if it's blank if (strlen(line.buf) == 0) continue; // The line contains a request to process. { char method[16], path[128], version[16]; int matched = sscanf(line.buf, "%15s%127s%15s", method, path, version); if (matched != 3) return writeResponse(400); // Store the required "method" if (strcmp(method, "GET") == 0) request = GetRequest; else if (strcmp(method, "HEAD") == 0) request = HeadRequest; else return writeResponse(501); // Store the URI to the "document" uri.buf = strDup(path); } // Move on to reading the request headers state = ReadHeaders; break; // Reading the request headers case ReadHeaders: // Try to read a line if (!line.read()) return false; // Skip headers until we hit a blank line if (strlen(line.buf) != 0) continue; // Headers ended - write the response! { CharArray address(sock.getPeerAddress()); vlog.info("getting %s for %s", uri.buf, address.buf); contentLength = -1; lastModified = -1; InStream* data = server.getFile(uri.buf, &contentType, &contentLength, &lastModified); if (!data) return writeResponse(404); try { writeResponse(200); if (request == GetRequest) copyStream(*data, sock.outStream()); sock.outStream().flush(); } catch (rdr::Exception& e) { vlog.error("error writing HTTP document:%s", e.str()); } delete data; } // The operation is complete! return true; default: throw rdr::Exception("invalid HTTPSession state!"); }; } // Indicate that we're still processing the HTTP request. return false;}int HTTPServer::Session::checkIdleTimeout() { time_t now = time(0); int timeout = (lastActive + idleTimeoutSecs) - now; if (timeout > 0) return secsToMillis(timeout); sock.shutdown(); return 0;}// -=- Constructor / destructorHTTPServer::HTTPServer() {}HTTPServer::~HTTPServer() { std::list<Session*>::iterator i; for (i=sessions.begin(); i!=sessions.end(); i++) delete *i;}// -=- SocketServer interface implementationvoidHTTPServer::addSocket(network::Socket* sock, bool) { Session* s = new Session(*sock, *this); if (!s) { sock->shutdown(); } else { sock->inStream().setTimeout(clientWaitTimeMillis); sock->outStream().setTimeout(clientWaitTimeMillis); sessions.push_front(s); }}voidHTTPServer::removeSocket(network::Socket* sock) { std::list<Session*>::iterator i; for (i=sessions.begin(); i!=sessions.end(); i++) { if ((*i)->getSock() == sock) { delete *i; sessions.erase(i); return; } }}voidHTTPServer::processSocketEvent(network::Socket* sock) { std::list<Session*>::iterator i; for (i=sessions.begin(); i!=sessions.end(); i++) { if ((*i)->getSock() == sock) { try { if ((*i)->processHTTP()) { vlog.info("completed HTTP request"); sock->shutdown(); } } catch (rdr::Exception& e) { vlog.error("untrapped: %s", e.str()); sock->shutdown(); } return; } } throw rdr::Exception("invalid Socket in HTTPServer");}void HTTPServer::getSockets(std::list<network::Socket*>* sockets){ sockets->clear(); std::list<Session*>::iterator ci; for (ci = sessions.begin(); ci != sessions.end(); ci++) { sockets->push_back((*ci)->getSock()); }}int HTTPServer::checkTimeouts() { std::list<Session*>::iterator ci; int timeout = 0; for (ci = sessions.begin(); ci != sessions.end(); ci++) { soonestTimeout(&timeout, (*ci)->checkIdleTimeout()); } return timeout;}// -=- Default getFile implementationInStream*HTTPServer::getFile(const char* name, const char** contentType, int* contentLength, time_t* lastModified){ return 0;}const char*HTTPServer::guessContentType(const char* name, const char* defType) { CharArray file, ext; if (!strSplit(name, '.', &file.buf, &ext.buf)) return defType; if (strcasecmp(ext.buf, "html") == 0 || strcasecmp(ext.buf, "htm") == 0) { return "text/html"; } else if (strcasecmp(ext.buf, "txt") == 0) { return "text/plain"; } else if (strcasecmp(ext.buf, "gif") == 0) { return "image/gif"; } else if (strcasecmp(ext.buf, "jpg") == 0) { return "image/jpeg"; } else if (strcasecmp(ext.buf, "jar") == 0) { return "application/java-archive"; } else if (strcasecmp(ext.buf, "exe") == 0) { return "application/octet-stream"; } return defType;}
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -