[Top][All Lists]
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[Gnash-commit] /srv/bzr/gnash/rtmp r10000: split the server side of HTTP
From: |
rob |
Subject: |
[Gnash-commit] /srv/bzr/gnash/rtmp r10000: split the server side of HTTP support into it's own class in Cygnal, so we can support CGI bins easier. |
Date: |
Sat, 21 Feb 2009 11:55:04 -0700 |
User-agent: |
Bazaar (1.5) |
------------------------------------------------------------
revno: 10000
committer: address@hidden
branch nick: rtmp
timestamp: Sat 2009-02-21 11:55:04 -0700
message:
split the server side of HTTP support into it's own class in Cygnal, so we
can support CGI bins easier.
added:
cygnal/http_server.cpp
cygnal/http_server.h
=== added file 'cygnal/http_server.cpp'
--- a/cygnal/http_server.cpp 1970-01-01 00:00:00 +0000
+++ b/cygnal/http_server.cpp 2009-02-21 18:55:04 +0000
@@ -0,0 +1,953 @@
+// http.cpp: HyperText Transport Protocol handler for Cygnal, for Gnash.
+//
+// Copyright (C) 2005, 2006, 2007, 2008 Free Software Foundation, Inc.
+//
+// This program 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 3 of the License, or
+// (at your option) any later version.
+//
+// This program 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 program; if not, write to the Free Software
+// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+//
+
+#ifdef HAVE_CONFIG_H
+#include "gnashconfig.h"
+#endif
+
+#include <boost/thread/mutex.hpp>
+#include <boost/shared_ptr.hpp>
+#include <boost/shared_array.hpp>
+#include <boost/scoped_array.hpp>
+#include <boost/tokenizer.hpp>
+#include <boost/date_time/posix_time/posix_time.hpp>
+#include <boost/date_time/gregorian/gregorian.hpp>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <string>
+#include <iostream>
+#include <cstring>
+#include <sstream>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <algorithm>
+#include "GnashSystemIOHeaders.h" // read()
+
+#include "amf.h"
+#include "element.h"
+#include "cque.h"
+#include "http_server.h"
+#include "log.h"
+#include "network.h"
+#include "handler.h"
+#include "utility.h"
+#include "buffer.h"
+#include "diskstream.h"
+#include "cache.h"
+
+// Not POSIX, so best not rely on it if possible.
+#ifndef PATH_MAX
+# define PATH_MAX 1024
+#endif
+
+using namespace gnash;
+using namespace std;
+
+static boost::mutex stl_mutex;
+
+namespace cygnal
+{
+
+static Cache& cache = Cache::getDefaultInstance();
+
+HTTPServer::HTTPServer()
+{
+// GNASH_REPORT_FUNCTION;
+}
+
+HTTPServer::~HTTPServer()
+{
+// GNASH_REPORT_FUNCTION;
+}
+
+HTTPServer::http_method_e
+HTTPServer::processClientRequest(int fd)
+{
+// GNASH_REPORT_FUNCTION;
+ bool result = false;
+
+ boost::shared_ptr<amf::Buffer> buf(_que.peek());
+ if (buf) {
+ _cmd = extractCommand(buf->reference());
+ switch (_cmd) {
+ case HTTP::HTTP_GET:
+ result = processGetRequest(fd);
+ break;
+ case HTTP::HTTP_POST:
+ result = processPostRequest(fd);
+ break;
+ case HTTP::HTTP_HEAD:
+ result = processHeadRequest(fd);
+ break;
+ case HTTP::HTTP_CONNECT:
+ result = processConnectRequest(fd);
+ break;
+ case HTTP::HTTP_TRACE:
+ result = processTraceRequest(fd);
+ break;
+ case HTTP::HTTP_OPTIONS:
+ result = processOptionsRequest(fd);
+ break;
+ case HTTP::HTTP_PUT:
+ result = processPutRequest(fd);
+ break;
+ case HTTP::HTTP_DELETE:
+ result = processDeleteRequest(fd);
+ break;
+ default:
+ break;
+ }
+ }
+
+ if (result) {
+ return _cmd;
+ } else {
+ return HTTP::HTTP_NONE;
+ }
+}
+
+// A GET request asks the server to send a file to the client
+bool
+HTTPServer::processGetRequest(int fd)
+{
+ GNASH_REPORT_FUNCTION;
+
+// boost::uint8_t buffer[readsize+1];
+// const char *ptr = reinterpret_cast<const char *>(buffer);
+// memset(buffer, 0, readsize+1);
+
+// _handler->wait();
+// _handler->dump();
+
+ cerr << "QUE = " << _que.size() << endl;
+
+ if (_que.size() == 0) {
+ return false;
+ }
+
+ boost::shared_ptr<amf::Buffer> buf(_que.pop());
+// cerr << "YYYYYYY: " << (char *)buf->reference() << endl;
+// cerr << hexify(buf->reference(), buf->allocated(), false) << endl;
+
+ if (buf == 0) {
+ // log_debug("Que empty, net connection dropped for fd #%d",
getFileFd());
+ log_debug("Que empty, net connection dropped for fd #%d", fd);
+ return false;
+ }
+
+ clearHeader();
+ processHeaderFields(*buf);
+
+ string url = _docroot + _filespec;
+ // See if the file is in the cache and already opened.
+ boost::shared_ptr<DiskStream> filestream(cache.findFile(url));
+ if (filestream) {
+ cerr << "FIXME: found file in cache!" << endl;
+ } else {
+ filestream.reset(new DiskStream);
+// cerr << "New Filestream at 0x" << hex << filestream.get() << endl;
+
+// cache.addFile(url, filestream); FIXME: always reload from disk
for now.
+
+ // Oopen the file and read the first chunk into memory
+ if (filestream->open(url)) {
+ formatErrorResponse(HTTPServer::NOT_FOUND);
+ } else {
+ // Get the file size for the HTTPServer header
+ if (filestream->getFileType() == DiskStream::FILETYPE_NONE) {
+ formatErrorResponse(HTTPServer::NOT_FOUND);
+ } else {
+ cache.addPath(_filespec, filestream->getFilespec());
+ }
+ }
+ }
+
+ // Send the reply
+ amf::Buffer &reply = formatHeader(filestream->getFileType(),
+ filestream->getFileSize(),
+ HTTPServer::OK);
+ writeNet(fd, reply);
+
+ size_t filesize = filestream->getFileSize();
+ size_t bytes_read = 0;
+ int ret;
+ size_t page = 0;
+ if (filesize) {
+#ifdef USE_STATS_CACHE
+ struct timespec start;
+ clock_gettime (CLOCK_REALTIME, &start);
+#endif
+ size_t getbytes = 0;
+ if (filesize <= filestream->getPagesize()) {
+ getbytes = filesize;
+ } else {
+ getbytes = filestream->getPagesize();
+ }
+ if (filesize >= CACHE_LIMIT) {
+ do {
+ filestream->loadToMem(page);
+ ret = writeNet(fd, filestream->get(), getbytes);
+ if (ret <= 0) {
+ break;
+ }
+ bytes_read += ret;
+ page += filestream->getPagesize();
+ } while (bytes_read <= filesize);
+ } else {
+ filestream->loadToMem(filesize, 0);
+ ret = writeNet(fd, filestream->get(), filesize);
+ }
+ filestream->close();
+#ifdef USE_STATS_CACHE
+ struct timespec end;
+ clock_gettime (CLOCK_REALTIME, &end);
+ double time = (end.tv_sec - start.tv_sec) + ((end.tv_nsec -
start.tv_nsec)/1e9);
+ cerr << "File " << _filespec
+ << " transferred " << filesize << " bytes in: " << fixed
+ << time << " seconds for net fd #" << fd << endl;
+#endif
+ }
+
+ log_debug("http_handler all done transferring requested file \"%s\".",
_filespec);
+
+ return true;
+}
+
+// A POST request asks sends a data from the client to the server. After
processing
+// the header like we normally do, we then read the amount of bytes specified
by
+// the "content-length" field, and then write that data to disk, or decode the
amf.
+bool
+HTTPServer::processPostRequest(int fd)
+{
+ GNASH_REPORT_FUNCTION;
+
+// cerr << "QUE1 = " << _que.size() << endl;
+
+ if (_que.size() == 0) {
+ return false;
+ }
+
+ boost::shared_ptr<amf::Buffer> buf(_que.pop());
+ if (buf == 0) {
+ log_debug("Que empty, net connection dropped for fd #%d", getFileFd());
+ return false;
+ }
+// cerr << __FUNCTION__ << buf->allocated() << " : " <<
hexify(buf->reference(), buf->allocated(), true) << endl;
+
+ clearHeader();
+ boost::uint8_t *data = processHeaderFields(*buf);
+ size_t length = strtol(getField("content-length").c_str(), NULL, 0);
+ boost::shared_ptr<amf::Buffer> content(new amf::Buffer(length));
+ int ret = 0;
+ if (buf->allocated() - (data - buf->reference()) ) {
+// cerr << "Don't need to read more data: have " << buf->allocated() << "
bytes" << endl;
+ content->copy(data, length);
+ ret = length;
+ } else {
+// cerr << "Need to read more data, only have " << buf->allocated() << "
bytes" << endl;
+ ret = readNet(fd, *content, 2);
+ data = content->reference();
+ }
+
+ if (getField("content-type") == "application/x-www-form-urlencoded") {
+ log_debug("Got file data in POST");
+ string url = _docroot + _filespec;
+ DiskStream ds(url, *content);
+ ds.writeToDisk();
+// ds.close();
+ // oh boy, we got ourselves some encoded AMF objects instead of a
boring file.
+ } else if (getField("content-type") == "application/x-amf") {
+ log_debug("Got AMF data in POST");
+#if 0
+ amf::AMF amf;
+ boost::shared_ptr<amf::Element> el =
amf.extractAMF(content.reference(), content.end());
+ el->dump(); // FIXME: do something intelligent
+ // with this Element
+#endif
+ }
+
+ // Send the reply
+
+ // NOTE: this is a "special" path we trap until we have real CGI support
+ if ((_filespec == "/echo/gateway")
+ && (getField("content-type") == "application/x-amf")) {
+// const char *num = (const char *)buf->at(10);
+ log_debug("Got CGI echo request in POST");
+// cerr << "FIXME 2: " << hexify(content->reference(),
content->allocated(), true) << endl;
+
+ vector<boost::shared_ptr<amf::Element> > headers =
parseEchoRequest(*content);
+ //boost::shared_ptr<amf::Element> &el0 = headers[0];
+ //boost::shared_ptr<amf::Element> &el1 = headers[1];
+ //boost::shared_ptr<amf::Element> &el3 = headers[3];
+
+ if (headers.size() >= 4) {
+ if (headers[3]) {
+ amf::Buffer &reply = formatEchoResponse(headers[1]->getName(),
*headers[3]);
+// cerr << "FIXME 3: " << hexify(reply.reference(), reply.allocated(),
true) << endl;
+// cerr << "FIXME 3: " << hexify(reply.reference(), reply.allocated(),
false) << endl;
+ writeNet(fd, reply);
+ }
+ }
+ } else {
+ amf::Buffer &reply = formatHeader(_filetype, _filesize, HTTPServer::OK);
+ writeNet(fd, reply);
+ }
+
+ return true;
+}
+
+bool
+HTTPServer::processPutRequest(int /* fd */)
+{
+// GNASH_REPORT_FUNCTION;
+ log_unimpl("PUT request");
+
+ return false;
+}
+
+bool
+HTTPServer::processDeleteRequest(int /* fd */)
+{
+// GNASH_REPORT_FUNCTION;
+ log_unimpl("DELETE request");
+ return false;
+}
+
+bool
+HTTPServer::processConnectRequest(int /* fd */)
+{
+// GNASH_REPORT_FUNCTION;
+ log_unimpl("CONNECT request");
+ return false;
+}
+
+bool
+HTTPServer::processOptionsRequest(int /* fd */)
+{
+// GNASH_REPORT_FUNCTION;
+ log_unimpl("OPTIONS request");
+ return false;
+}
+
+bool
+HTTPServer::processHeadRequest(int /* fd */)
+{
+// GNASH_REPORT_FUNCTION;
+ log_unimpl("HEAD request");
+ return false;
+}
+
+bool
+HTTPServer::processTraceRequest(int /* fd */)
+{
+// GNASH_REPORT_FUNCTION;
+ log_unimpl("TRACE request");
+ return false;
+}
+
+amf::Buffer &
+HTTPServer::formatErrorResponse(http_status_e code)
+{
+// GNASH_REPORT_FUNCTION;
+
+ char num[12];
+ // First build the message body, so we know how to set Content-Length
+ _buffer += "<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">\r\n";
+ _buffer += "<html><head>\r\n";
+ _buffer += "<title>";
+ sprintf(num, "%d", code);
+ _buffer += num;
+ _buffer += " Not Found</title>\r\n";
+ _buffer += "</head><body>\r\n";
+ _buffer += "<h1>Not Found</h1>\r\n";
+ _buffer += "<p>The requested URL ";
+ _buffer += _filespec;
+ _buffer += " was not found on this server.</p>\r\n";
+ _buffer += "<hr>\r\n";
+ _buffer += "<address>Cygnal (GNU/Linux) Server at ";
+ _buffer += getField("host");
+ _buffer += " </address>\r\n";
+ _buffer += "</body></html>\r\n";
+
+ // First build the header
+ formatDate();
+ formatServer();
+ formatContentLength(_filesize);
+ formatConnection("close");
+ formatContentType(_filetype);
+
+ // All HTTPServer messages are followed by a blank line.
+ terminateHeader();
+
+ return _buffer;
+}
+
+amf::Buffer &
+HTTPServer::formatGetReply(http_status_e code)
+{
+
+// GNASH_REPORT_FUNCTION;
+
+ return formatHeader(_filesize, code);
+}
+
+amf::Buffer &
+HTTPServer::formatGetReply(size_t size, http_status_e code)
+{
+// GNASH_REPORT_FUNCTION;
+
+ formatHeader(size, code);
+
+// int ret = Network::writeNet(_header.str());
+// boost::uint8_t *ptr = (boost::uint8_t *)_body.str().c_str();
+// buf->copy(ptr, _body.str().size());
+// _handler->dump();
+
+#if 0
+ if (_header.str().size()) {
+ log_debug (_("Sent GET Reply"));
+ return _buffer;
+ } else {
+ clearHeader();
+ log_debug (_("Couldn't send GET Reply, no header data"));
+ }
+#endif
+
+ return _buffer;
+}
+
+amf::Buffer &
+HTTPServer::formatPostReply(rtmpt_cmd_e /* code */)
+{
+ GNASH_REPORT_FUNCTION;
+
+ formatDate();
+ formatServer();
+ formatContentType(DiskStream::FILETYPE_AMF);
+ // All HTTPServer messages are followed by a blank line.
+ terminateHeader();
+ return _buffer;
+
+#if 0
+ formatHeader(_filesize, code);
+ boost::shared_ptr<amf::Buffer> buf = new amf::Buffer;
+ if (_header.str().size()) {
+ buf->resize(_header.str().size());
+ string str = _header.str();
+ buf->copy(str);
+ _handler->pushout(buf);
+ _handler->notifyout();
+ log_debug (_("Sent GET Reply"));
+ return true; // Default to true
+ } else {
+ clearHeader();
+ log_debug (_("Couldn't send POST Reply, no header data"));
+ }
+#endif
+
+ return _buffer;
+}
+
+#if 0
+// Parse an Echo Request message coming from the Red5 echo_test. This
+// method should only be used for testing purposes.
+vector<boost::shared_ptr<amf::Element > >
+HTTPServer::parseEchoRequest(boost::uint8_t *data, size_t size)
+{
+// GNASH_REPORT_FUNCTION;
+
+ vector<boost::shared_ptr<amf::Element > > headers;
+
+ // skip past the header bytes, we don't care about them.
+ boost::uint8_t *tmpptr = data + 6;
+
+ boost::uint16_t length;
+ length = ntohs((*(boost::uint16_t *)tmpptr) & 0xffff);
+ tmpptr += sizeof(boost::uint16_t);
+
+ // Get the first name, which is a raw string, and not preceded by
+ // a type byte.
+ boost::shared_ptr<amf::Element > el1(new amf::Element);
+
+ // If the length of the name field is corrupted, then we get out of
+ // range quick, and corrupt memory. This is a bit of a hack, but
+ // reduces memory errors caused by some of the corrupted tes cases.
+ boost::uint8_t *endstr = std::find(tmpptr, tmpptr+length, '\0');
+ if (endstr != tmpptr+length) {
+ log_debug("Caught corrupted string! length was %d, null at %d",
+ length, endstr-tmpptr);
+ length = endstr-tmpptr;
+ }
+ el1->setName(tmpptr, length);
+ tmpptr += length;
+ headers.push_back(el1);
+
+ // Get the second name, which is a raw string, and not preceded by
+ // a type byte.
+ length = ntohs((*(boost::uint16_t *)tmpptr) & 0xffff);
+ tmpptr += sizeof(boost::uint16_t);
+ boost::shared_ptr<amf::Element > el2(new amf::Element);
+
+// std::string name2(reinterpret_cast<const char *>(tmpptr), length);
+// el2->setName(name2.c_str(), name2.size());
+ // If the length of the name field is corrupted, then we get out of
+ // range quick, and corrupt memory. This is a bit of a hack, but
+ // reduces memory errors caused by some of the corrupted tes cases.
+ endstr = std::find(tmpptr, tmpptr+length, '\0');
+ if (endstr != tmpptr+length) {
+ log_debug("Caught corrupted string! length was %d, null at %d",
+ length, endstr-tmpptr);
+ length = endstr-tmpptr;
+ }
+ el2->setName(tmpptr, length);
+ headers.push_back(el2);
+ tmpptr += length;
+
+ // Get the last two pieces of data, which are both AMF encoded
+ // with a type byte.
+ amf::AMF amf;
+ boost::shared_ptr<amf::Element> el3 = amf.extractAMF(tmpptr, tmpptr +
size);
+ headers.push_back(el3);
+ tmpptr += amf.totalsize();
+
+ boost::shared_ptr<amf::Element> el4 = amf.extractAMF(tmpptr, tmpptr +
size);
+ headers.push_back(el4);
+
+ return headers;
+}
+
+// format a response to the 'echo' test used for testing Gnash. This
+// is only used for testing by developers. The format appears to be
+// two strings, followed by a double, followed by the "onResult".
+amf::Buffer &
+HTTPServer::formatEchoResponse(const std::string &num, amf::Element &el)
+{
+// GNASH_REPORT_FUNCTION;
+ boost::shared_ptr<amf::Buffer> data;
+
+ amf::Element nel;
+ if (el.getType() == amf::Element::TYPED_OBJECT_AMF0) {
+ nel.makeTypedObject();
+ string name = el.getName();
+ nel.setName(name);
+ if (el.propertySize()) {
+ // FIXME: see about using std::reverse() instead.
+ for (int i=el.propertySize()-1; i>=0; i--) {
+// for (int i=0 ; i<el.propertySize(); i++) {
+ boost::shared_ptr<amf::Element> child = el.getProperty(i);
+ nel.addProperty(child);
+ }
+ data = nel.encode();
+ } else {
+ data = el.encode();
+ }
+ } else {
+ data = el.encode();
+ }
+
+ return formatEchoResponse(num, data->reference(), data->allocated());
+}
+
+amf::Buffer &
+HTTPServer::formatEchoResponse(const std::string &num, amf::Buffer &data)
+{
+// GNASH_REPORT_FUNCTION;
+ return formatEchoResponse(num, data.reference(), data.allocated());
+}
+
+amf::Buffer &
+HTTPServer::formatEchoResponse(const std::string &num, boost::uint8_t *data,
size_t size)
+{
+// GNASH_REPORT_FUNCTION;
+
+ //boost::uint8_t *tmpptr = data;
+
+ // FIXME: temporary hacks while debugging
+ amf::Buffer fixme("00 00 00 00 00 01");
+ amf::Buffer fixme2("ff ff ff ff");
+
+ _buffer = "HTTPServer/1.1 200 OK\r\n";
+ formatContentType(DiskStream::FILETYPE_AMF);
+// formatContentLength(size);
+ // FIXME: this is a hack ! Calculate a real size!
+ formatContentLength(size+29);
+
+ // Pretend to be Red5 server
+ formatServer("Jetty(6.1.7)");
+
+ // All HTTPServer messages are followed by a blank line.
+ terminateHeader();
+
+ // Add the binary blob for the header
+ _buffer += fixme;
+
+ // Make the result response, which is the 2nd data item passed in
+ // the request, a slash followed by a number like "/2".
+ string result = num;
+ result += "/onResult";
+ boost::shared_ptr<amf::Buffer> res = amf::AMF::encodeString(result);
+ _buffer.append(res->begin()+1, res->size()-1);
+
+ // Add the null data item
+ boost::shared_ptr<amf::Buffer> null = amf::AMF::encodeString("null");
+ _buffer.append(null->begin()+1, null->size()-1);
+
+ // Add the other binary blob
+ _buffer += fixme2;
+
+ amf::Element::amf0_type_e type =
static_cast<amf::Element::amf0_type_e>(*data);
+ if ((type == amf::Element::UNSUPPORTED_AMF0)
+ || (type == amf::Element::NULL_AMF0)) {
+ _buffer += type;
+ // Red5 returns a NULL object when it's recieved an undefined one in
the echo_test
+ } else if (type == amf::Element::UNDEFINED_AMF0) {
+ _buffer += amf::Element::NULL_AMF0;
+ } else {
+ // Add the AMF data we're echoing back
+ if (size) {
+ _buffer.append(data, size);
+ }
+ }
+
+ return _buffer;
+}
+#endif
+
+/// These methods extract data from an RTMPT message. RTMP is an
+/// extension to HTTPServer that adds commands to manipulate the
+/// connection's persistance.
+//
+/// The URL to be opened has the following form:
+/// http://server/<comand>/[<client>/]<index>
+/// <command>
+/// denotes the RTMPT request type, "OPEN", "SEND", "IDLE", "CLOSE")
+/// <client>
+/// specifies the id of the client that performs the requests
+/// (only sent for established sessions)
+/// <index>
+/// is a consecutive number that seems to be used to detect missing packages
+HTTPServer::rtmpt_cmd_e
+HTTPServer::extractRTMPT(boost::uint8_t *data)
+{
+ GNASH_REPORT_FUNCTION;
+
+ string body = reinterpret_cast<const char *>(data);
+ string tmp, cid, indx;
+ HTTPServer::rtmpt_cmd_e cmd;
+
+ // force the case to make comparisons easier
+ std::transform(body.begin(), body.end(), body.begin(),
+ (int(*)(int)) toupper);
+ string::size_type start, end;
+
+ // Extract the command first
+ start = body.find("OPEN", 0);
+ if (start != string::npos) {
+ cmd = HTTPServer::OPEN;
+ }
+ start = body.find("SEND", 0);
+ if (start != string::npos) {
+ cmd = HTTPServer::SEND;
+ }
+ start = body.find("IDLE", 0);
+ if (start != string::npos) {
+ cmd = HTTPServer::IDLE;
+ }
+ start = body.find("CLOSE", 0);
+ if (start != string::npos) {
+ cmd = HTTPServer::CLOSE;
+ }
+
+ // Extract the optional client id
+ start = body.find("/", start+1);
+ if (start != string::npos) {
+ end = body.find("/", start+1);
+ if (end != string::npos) {
+ indx = body.substr(end, body.size());
+ cid = body.substr(start, (end-start));
+ } else {
+ cid = body.substr(start, body.size());
+ }
+ }
+
+ _index = strtol(indx.c_str(), NULL, 0);
+ _clientid = strtol(cid.c_str(), NULL, 0);
+ end = body.find("\r\n", start);
+// if (end != string::npos) {
+// cmd = HTTPServer::CLOSE;
+// }
+
+ return cmd;
+}
+
+HTTPServer::http_method_e
+HTTPServer::extractCommand(boost::uint8_t *data)
+{
+ GNASH_REPORT_FUNCTION;
+
+// string body = reinterpret_cast<const char *>(data);
+ HTTPServer::http_method_e cmd = HTTP::HTTP_NONE;
+
+ // force the case to make comparisons easier
+// std::transform(body.begin(), body.end(), body.begin(),
+// (int(*)(int)) toupper);
+
+ // Extract the command
+ if (memcmp(data, "GET", 3) == 0) {
+ cmd = HTTP::HTTP_GET;
+ } else if (memcmp(data, "POST", 4) == 0) {
+ cmd = HTTP::HTTP_POST;
+ } else if (memcmp(data, "HEAD", 4) == 0) {
+ cmd = HTTP::HTTP_HEAD;
+ } else if (memcmp(data, "CONNECT", 7) == 0) {
+ cmd = HTTP::HTTP_CONNECT;
+ } else if (memcmp(data, "TRACE", 5) == 0) {
+ cmd = HTTP::HTTP_TRACE;
+ } else if (memcmp(data, "PUT", 3) == 0) {
+ cmd = HTTP::HTTP_PUT;
+ } else if (memcmp(data, "OPTIONS", 4) == 0) {
+ cmd = HTTP::HTTP_OPTIONS;
+ } else if (memcmp(data, "DELETE", 4) == 0) {
+ cmd = HTTP::HTTP_DELETE;
+ }
+
+ // For valid requests, the second argument, delimited by spaces
+ // is the filespec of the file being requested or transmitted.
+ if (cmd != HTTP::HTTP_NONE) {
+ boost::uint8_t *start = std::find(data, data+7, ' ') + 1;
+ boost::uint8_t *end = std::find(start + 2, data+PATH_MAX, ' ');
+ boost::uint8_t *params = std::find(start, end, '?');
+ if (params != end) {
+ _params = std::string(params+1, end);
+ _filespec = std::string(start, params);
+ log_debug("Parameters for file: \"%s\"", _params);
+ } else {
+ // This is fine as long as end is within the buffer.
+ _filespec = std::string(start, end);
+ }
+ log_debug("Requesting file: \"%s\"", _filespec);
+
+ // The third field is always the HTTP version
+ // The version is the last field and is the protocol name
+ // followed by a slash, and the version number. Note that
+ // the version is not a double, even though it has a dot
+ // in it. It's actually two separate integers.
+ _version.major = *(end+6) - '0';
+ _version.minor = *(end+8) - '0';
+ log_debug (_("Version: %d.%d"), _version.major, _version.minor);
+ }
+
+ return cmd;
+}
+
+boost::uint8_t *
+HTTPServer::processHeaderFields(amf::Buffer &buf)
+{
+ // GNASH_REPORT_FUNCTION;
+ string head(reinterpret_cast<const char *>(buf.reference()));
+
+ // The end of the header block is always followed by a blank line
+ string::size_type end = head.find("\r\n\r\n", 0);
+// head.erase(end, buf.size()-end);
+ Tok t(head, Sep("\r\n"));
+ for (Tok::iterator i = t.begin(); i != t.end(); ++i) {
+ string::size_type pos = i->find(":", 0);
+ if (pos != string::npos) {
+ string name = i->substr(0, pos);
+ string value = i->substr(pos+2, i->size());
+ std::transform(name.begin(), name.end(), name.begin(),
+ (int(*)(int)) tolower);
+ std::transform(value.begin(), value.end(), value.begin(),
+ (int(*)(int)) tolower);
+ _fields[name] = value;
+ if (name == "keep-alive") {
+ _keepalive = true;
+ if ((value != "on") && (value != "off")) {
+ _max_requests = strtol(value.c_str(), NULL, 0);
+ log_debug("Setting Max Requests for Keep-Alive to %d",
_max_requests);
+ }
+ }
+ if (name == "connection") {
+ if (value.find("keep-alive", 0) != string::npos) {
+ _keepalive = true;
+ }
+ }
+ if (name == "content-length") {
+ _filesize = strtol(value.c_str(), NULL, 0);
+ log_debug("Setting Content Length to %d", _filesize);
+ }
+ if (name == "content-type") {
+ // This is the type used by flash when sending a AMF data via
POST
+ if (value == "application/x-amf") {
+// log_debug("Got AMF data in the POST request!");
+ _filetype = DiskStream::FILETYPE_AMF;
+ }
+ // This is the type used by wget when sending a file via POST
+ if (value == "application/x-www-form-urlencoded") {
+// log_debug("Got file data in the POST request");
+ _filetype = DiskStream::FILETYPE_ENCODED;
+ }
+ log_debug("Setting Content Type to %d", _filetype);
+ }
+
+// cerr << "FIXME: " << (void *)i << " : " << dec << end << endl;
+ } else {
+ const boost::uint8_t *cmd = reinterpret_cast<const boost::uint8_t
*>(i->c_str());
+ if (extractCommand(const_cast<boost::uint8_t *>(cmd)) ==
HTTP::HTTP_NONE) {
+ break;
+#if 1
+ } else {
+ log_debug("Got a request, parsing \"%s\"", *i);
+ string::size_type start = i->find(" ");
+ string::size_type params = i->find("?");
+ string::size_type pos = i->find("HTTP/");
+ if (pos != string::npos) {
+ // The version is the last field and is the protocol name
+ // followed by a slash, and the version number. Note that
+ // the version is not a double, even though it has a dot
+ // in it. It's actually two separate integers.
+ _version.major = i->at(pos+5) - '0';
+ _version.minor = i->at(pos+7) - '0';
+ log_debug (_("Version: %d.%d"), _version.major,
_version.minor);
+ // the filespec in the request is the middle field,
deliminated
+ // by a space on each end.
+ if (params != string::npos) {
+ _params = i->substr(params+1, end);
+ _filespec = i->substr(start+1, params);
+ log_debug("Parameters for file: \"%s\"", _params);
+ } else {
+ _filespec = i->substr(start+1, pos-start-2);
+ }
+ log_debug("Requesting file: \"%s\"", _filespec);
+
+ // HTTP 1.1 enables persistant network connections
+ // by default.
+ if (_version.minor > 0) {
+ log_debug("Enabling Keep Alive by default for HTTP >
1.0");
+ _keepalive = true;
+ }
+ }
+ }
+#endif
+ }
+ }
+
+ return buf.reference() + end + 4;
+}
+
+void
+HTTPServer::dump()
+{
+// GNASH_REPORT_FUNCTION;
+}
+
+extern "C" {
+
+bool
+http_handler(Network::thread_params_t *args)
+{
+// GNASH_REPORT_FUNCTION;
+// struct thread_params thread_data;
+ string url, filespec, parameters;
+ HTTPServer *www = new HTTPServer;
+ bool result = false;
+
+// Network *net = reinterpret_cast<Network *>(args->handler);
+ bool done = false;
+// www.setHandler(net);
+
+ log_debug(_("Starting HTTP Handler for fd #%d, tid %ld"),
+ args->netfd, get_thread_id());
+
+ string docroot = args->filespec;
+
+ www->setDocRoot(docroot);
+ log_debug("Starting to wait for data in net for fd #%d", args->netfd);
+
+ // Wait for data, and when we get it, process it.
+ do {
+
+#ifdef USE_STATISTICS
+ struct timespec start;
+ clock_gettime (CLOCK_REALTIME, &start);
+#endif
+
+ // See if we have any messages waiting
+ if (www->recvMsg(args->netfd) == 0) {
+ done = true;
+ }
+
+ // Process incoming messages
+ if (!www->processClientRequest(args->netfd)) {
+// hand->die(); // tell all the threads for this connection to
die
+// hand->notifyin();
+ log_debug("Net HTTP server done for fd #%d...", args->netfd);
+// done = true;
+ }
+// www.dump();
+
+#if 0
+ string response = cache.findResponse(filestream->getFilespec());
+ if (response.empty()) {
+ cerr << "FIXME no cache hit for: " << www.getFilespec() << endl;
+// www.clearHeader();
+// amf::Buffer &ss = www.formatHeader(filestream->getFileSize(),
HTTP::LIFE_IS_GOOD);
+// www.writeNet(args->netfd, (boost::uint8_t
*)www.getHeader().c_str(), www.getHeader().size());
+// cache.addResponse(www.getFilespec(), www.getHeader());
+ } else {
+ cerr << "FIXME cache hit on: " << www.getFilespec() << endl;
+ www.writeNet(args->netfd, (boost::uint8_t *)response.c_str(),
response.size());
+ }
+#endif
+
+ // Unless the Keep-Alive flag is set, this isn't a persisant network
+ // connection.
+ if (!www->keepAlive()) {
+ log_debug("Keep-Alive is off", www->keepAlive());
+ result = false;
+ done = true;
+ } else {
+ log_debug("Keep-Alive is on", www->keepAlive());
+ result = true;
+// done = true;
+ }
+#ifdef USE_STATISTICS
+ struct timespec end;
+ clock_gettime (CLOCK_REALTIME, &end);
+ log_debug("Processing time for GET request was %f seconds",
+ (float)((end.tv_sec - start.tv_sec) + ((end.tv_nsec -
start.tv_nsec)/1e9)));
+#endif
+ } while(done != true);
+
+// hand->notify();
+
+ log_debug("http_handler all done now finally...");
+
+ return result;
+} // end of httphandler
+} // end of extern C
+
+} // end of gnash namespace
+
+
+// local Variables:
+// mode: C++
+// indent-tabs-mode: t
+// End:
=== added file 'cygnal/http_server.h'
--- a/cygnal/http_server.h 1970-01-01 00:00:00 +0000
+++ b/cygnal/http_server.h 2009-02-21 18:55:04 +0000
@@ -0,0 +1,116 @@
+//
+// Copyright (C) 2007, 2008 Free Software Foundation, Inc.
+//
+// This program 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 3 of the License, or
+// (at your option) any later version.
+//
+// This program 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 program; if not, write to the Free Software
+// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+//
+
+#ifndef _HTTP_SERVER_H_
+#define _HTTP_SERVER_H_
+
+#include <string>
+#include <map>
+#include <vector>
+#include <boost/shared_ptr.hpp>
+#include <boost/shared_array.hpp>
+#include <boost/scoped_array.hpp>
+#include <sstream>
+
+#include "amf.h"
+#include "cque.h"
+#include "rtmp.h"
+#include "http.h"
+#include "handler.h"
+#include "network.h"
+#include "buffer.h"
+#include "diskstream.h"
+
+namespace cygnal
+{
+
+class HTTPServer : public gnash::HTTP
+{
+public:
+ HTTPServer();
+ ~HTTPServer();
+
+ // These are for the protocol itself
+ http_method_e processClientRequest(int fd);
+ bool processGetRequest(int fd);
+ bool processPostRequest(int fd);
+ bool processPutRequest(int fd);
+ bool processDeleteRequest(int fd);
+ bool processConnectRequest(int fd);
+ bool processOptionsRequest(int fd);
+ bool processHeadRequest(int fd);
+ bool processTraceRequest(int fd);
+
+ // Handle the response for the request.
+ boost::shared_ptr<amf::Buffer> formatServerReply(http_status_e code);
+ amf::Buffer &formatGetReply(gnash::DiskStream::filetype_e type, size_t
size, http_status_e code);
+ amf::Buffer &formatGetReply(size_t size, http_status_e code);
+ amf::Buffer &formatGetReply(http_status_e code);
+ amf::Buffer &formatPostReply(rtmpt_cmd_e code);
+ amf::Buffer &formatErrorResponse(http_status_e err);
+
+ // These methods extract data from an RTMPT message. RTMP is an
+ // extension to HTTP that adds commands to manipulate the
+ // connection's persistance.
+ rtmpt_cmd_e extractRTMPT(boost::uint8_t *data);
+ rtmpt_cmd_e extractRTMPT(amf::Buffer &data)
+ { return extractRTMPT(data.reference()); };
+
+ // Examine the beginning of the data for an HTTP request command
+ // like GET or POST, etc...
+ http_method_e extractCommand(boost::uint8_t *data);
+ http_method_e extractCommand(amf::Buffer &data)
+ { return extractCommand(data.reference()); };
+
+ // process all the header fields in the Buffer, storing them internally
+ // in _fields. The address returned is the address where the Content data
+ // starts, and is "Content-Length" bytes long, of "Content-Type" data.
+ boost::uint8_t *processHeaderFields(amf::Buffer &buf);
+
+#if 0
+ // Parse an Echo Request message coming from the Red5 echo_test.
+ std::vector<boost::shared_ptr<amf::Element > >
parseEchoRequest(gnash::amf::Buffer &buf) { return
parseEchoRequest(buf.reference(), buf.size()); };
+ std::vector<boost::shared_ptr<amf::Element > >
parseEchoRequest(boost::uint8_t *buf, size_t size);
+
+ // format a response to the 'echo' test used for testing Gnash.
+ gnash::amf::Buffer &formatEchoResponse(const std::string &num,
amf::Element &el);
+ gnash::amf::Buffer &formatEchoResponse(const std::string &num, amf::Buffer
&data);
+ gnash::amf::Buffer &formatEchoResponse(const std::string &num, uint8_t
*data, size_t size);
+#endif
+
+ void dump();
+
+private:
+
+};
+
+// This is the thread for all incoming HTTP connections
+extern "C" {
+ bool http_handler(gnash::Network::thread_params_t *args);
+}
+
+} // end of gnash namespace
+
+// end of _HTTP_SERVER_H_
+#endif
+
+
+// local Variables:
+// mode: C++
+// indent-tabs-mode: t
+// End:
[Prev in Thread] |
Current Thread |
[Next in Thread] |
- [Gnash-commit] /srv/bzr/gnash/rtmp r10000: split the server side of HTTP support into it's own class in Cygnal, so we can support CGI bins easier.,
rob <=