# # # add_file "http.cc" # content [852d451ec80ab2d935e7a369bb7125481a57a779] # # add_file "http.hh" # content [c340ecd627d001bf0b3375a8a6e202509711f58f] # # patch "Makefile.am" # from [2d6b9e005b59ce2e1ca2e03373cbcbe5678d0ae4] # to [b73161cf9e05c933322899f639e7196db257be33] # # patch "cmd_scgi.cc" # from [82c2741e27572d2ba060867213b8504c1a9f09f0] # to [c628910886e8e6a8be7a957cb3a8d0e83cf7629d] # # patch "http_client.cc" # from [aef4eb97b685e449f8d700747e81b8dcd30536c5] # to [d93bba1032bf5b94648705eeee7b58d38e0901dd] # # patch "http_client.hh" # from [305c2fb19892bc9054e057a4ecd73693a0434dd0] # to [0d36dc926a20df01f225f45e143f671c4e2520f6] # ============================================================ --- http.cc 852d451ec80ab2d935e7a369bb7125481a57a779 +++ http.cc 852d451ec80ab2d935e7a369bb7125481a57a779 @@ -0,0 +1,206 @@ +// Copyright (C) 2009 Derek Scherger +// +// This program is made available under the GNU GPL version 2.0 or +// greater. See the accompanying file COPYING for details. +// +// This program is distributed WITHOUT ANY WARRANTY; without even the +// implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +// PURPOSE. + +#include "base.hh" + +#include "http.hh" +#include "lexical_cast.hh" +#include "simplestring_xform.hh" + +using std::getline; +using std::iostream; +using std::string; + +using boost::lexical_cast; + +typedef http::header_map::const_iterator header_iterator; + +namespace http +{ + + string + connection::version() + { + return http::version; + } + + bool + connection::read(request & r) + { + bool good = + read(r.method, " ") && + read(r.uri, " ") && + read(r.version, "\r\n"); + + if (good) + L(FL("read http request: %s %s %s") % r.method % r.uri % r.version); + + return good && read_headers(r) && read_body(r); + } + + void + connection::write(request const & r) + { + L(FL("write http request: %s %s %s") % r.method % r.uri % r.version); + write(r.method, " "); + write(r.uri, " "); + write(r.version, "\r\n"); + + write_headers(r); + write_body(r); + } + + bool + connection::read(response & r) + { + bool good = + read(r.version, " ") && + read(r.status_code, " ") && + read(r.status_message, "\r\n"); + + if (good) + L(FL("read http response: %s %s %s") + % r.version % r.status_code % r.status_message); + + return good && read_headers(r) && read_body(r); + } + + void + connection::write(response const & r) + { + L(FL("write http response: %s %s %s") % r.version % r.status_code % r.status_message); + write(r.version, " "); + write(r.status_code, " "); + write(r.status_message, "\r\n"); + + write_headers(r); + write_body(r); + } + + bool + connection::read(string & value, string const & end) + { + while (io.good() && value.rfind(end) == string::npos) + value += static_cast(io.get()); + + if (value.rfind(end) == value.size() - end.size()) + { + value.resize(value.size() - end.size()); + return true; + } + else + return false; + } + + bool + connection::read(size_t & value, string const & end) + { + string tmp; + bool good = read(tmp, end); + value = lexical_cast(tmp); + return good; + } + + void + connection::write(string const & value, string const & end) + { + io << value << end; + } + + void + connection::write(size_t const value, string const & end) + { + io << value << end; + } + + bool + connection::read_headers(message & m) + { + m.headers.clear(); + while (io.good() && io.peek() != '\r') + { + string key, val; + if (!read(key, ": ")) return false; + if (!read(val, "\r\n")) return false; + + m.headers[key] = val; + + L(FL("read http header: %s: %s") % key % val); + } + + L(FL("read http header end")); + + if (!io.good()) return false; + + char cr = static_cast(io.get()); + char lf = static_cast(io.get()); + + return cr == '\r' && lf == '\n'; + + } + + bool + connection::read_body(message & m) + { + if (m.headers.find("Content-Length") == m.headers.end()) + return false; + + size_t length = lexical_cast(m.headers["Content-Length"]); + L(FL("reading http body: %d bytes") % length); + + m.body.clear(); + m.body.reserve(length); + + while (io.good() && (length > 0)) + { + m.body += static_cast(io.get()); + length--; + } + + L(FL("read %d bytes, content length now %d") + % m.body.size() % length); + + L(FL("%s") % m.body); + + return (length == 0); + } + + void + connection::write_headers(message const & m) + { + for (header_iterator i = m.headers.begin(); i != m.headers.end(); ++i) + { + L(FL("write http header: %s: %s") % i->first % i->second); + write(i->first, ": "); + write(i->second, "\r\n"); + } + + L(FL("write http header end")); + io << "\r\n"; + } + + void + connection::write_body(message const & m) + { + L(FL("writing http body: %d bytes") % m.body.size()); + L(FL("%s") % m.body); + + io << m.body; + io.flush(); + } + +}; + +// Local Variables: +// mode: C++ +// fill-column: 76 +// c-file-style: "gnu" +// indent-tabs-mode: nil +// End: +// vim: et:sw=2:sts=2:ts=2:cino=>2s,{s,\:s,+s,t0,g0,^-2,e-2,n-2,p2s,(0,=s: ============================================================ --- http.hh c340ecd627d001bf0b3375a8a6e202509711f58f +++ http.hh c340ecd627d001bf0b3375a8a6e202509711f58f @@ -0,0 +1,92 @@ +#ifndef __HTTP_HH__ +#define __HTTP_HH__ + +// Copyright (C) 2009 Derek Scherger +// +// This program is made available under the GNU GPL version 2.0 or +// greater. See the accompanying file COPYING for details. +// +// This program is distributed WITHOUT ANY WARRANTY; without even the +// implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +// PURPOSE. + +#include +#include + +namespace http +{ + static std::string const version("HTTP/1.1"); + static std::string const post("POST"); + static std::string const get("GET"); + static std::string const put("PUT"); + + typedef std::map header_map; + + struct message + { + header_map headers; + std::string body; + }; + + // the first line of an http request is: + // CR LF + // GET /path HTTP/1.1 + + struct request : public message + { + std::string method; + std::string uri; + std::string version; + }; + + // the first line of an http response is + // CR LF + // HTTP/1.1 200 OK + + struct response : public message + { + std::string version; + size_t status_code; + std::string status_message; + }; + + class connection + { + public: + connection(std::iostream & io) : io(io) {} + virtual ~connection() {}; + + virtual std::string version(); + virtual bool read(request & r); + void write(request const & r); + + bool read(response & r); + virtual void write(response const & r); + + protected: + std::iostream & io; + + bool read(std::string & value, std::string const & end); + bool read(size_t & value, std::string const & end); + + void write(std::string const & value, std::string const & end); + virtual void write(size_t const value, std::string const & end); + + bool read_headers(message & m); + bool read_body(message & m); + + void write_headers(message const & m); + void write_body(message const & m); + }; + +}; + +#endif // __HTTP_HH__ + +// Local Variables: +// mode: C++ +// fill-column: 76 +// c-file-style: "gnu" +// indent-tabs-mode: nil +// End: +// vim: et:sw=2:sts=2:ts=2:cino=>2s,{s,\:s,+s,t0,g0,^-2,e-2,n-2,p2s,(0,=s: ============================================================ --- Makefile.am 2d6b9e005b59ce2e1ca2e03373cbcbe5678d0ae4 +++ Makefile.am b73161cf9e05c933322899f639e7196db257be33 @@ -79,7 +79,7 @@ MOST_SOURCES = \ rev_height.cc rev_height.hh \ asciik.cc asciik.hh \ dates.cc dates.hh \ - http.hh http_client.cc http_client.hh \ + http.cc http.hh http_client.cc http_client.hh \ \ lru_writeback_cache.hh hybrid_map.hh \ \ ============================================================ --- cmd_scgi.cc 82c2741e27572d2ba060867213b8504c1a9f09f0 +++ cmd_scgi.cc c628910886e8e6a8be7a957cb3a8d0e83cf7629d @@ -54,7 +54,7 @@ using boost::lexical_cast; // SCGI interface is pretty straightforward // // When we accept a connection, we read a netstring out of it covering the -// header, and then a body consisting of a JSON object. +// header, and then a body with the specified content length. // // The format of the headers is: // @@ -73,183 +73,94 @@ using boost::lexical_cast; // digits giving the length of [string] in decimal. // // The response is a sequence of CRLF-separated of HTTP headers, followed by -// a bare CRLF, and a JSON object. +// a bare CRLF, and the response body. // // This response format is not specified by the SCGI "spec". // -namespace connection +namespace scgi { - enum type { http, scgi }; -}; + static const string version("SCGI/1"); + class connection : public http::connection + { + public: + connection(std::iostream & io) : http::connection(io) {} + virtual ~connection() {} -struct gserve_error -{ - string msg; - gserve_error(string const & s): msg(s) {} -}; - -// Consume string until null or EOF. Consumes trailing null. -static string -parse_str(istream & in) -{ - string s; - while (in.good()) + string version() { - char ch = static_cast(in.get()); - if (ch == '\0') - break; - s += ch; + return scgi::version; } - return s; -} -static inline bool -eat(istream & in, char c) -{ - if (!in.good()) - return false; - int i = in.get(); - return c == static_cast(i); -} - -static bool -parse_scgi_request(istream & in, http::request & request) -{ - if (!in.good()) return false; - - size_t netstring_len; - in >> netstring_len; - if (!in.good()) return false; - - L(FL("scgi: netstring length: %d") % netstring_len); - if (!eat(in, ':')) return false; - - // although requirements of the scgi spec, - // the code below will allow requests that: - // - don't have CONTENT_LENGTH as the first header - // - don't have a CONTENT_LENGTH header at all - // - don't have an SCGI header - // perhaps this is just to "be liberal in what you accept" - // but we may want to tighten it up a bit. at the very least - // this is relying on the CONTENT_LENGTH header. - - size_t content_length = 0; - while (netstring_len > 0) + bool read(string & value) { - string key = parse_str(in); - string val = parse_str(in); - - L(FL("scgi: got header: %s -> %s") % key % val); - - if (key == "CONTENT_LENGTH") + while (io.good()) { - content_length = lexical_cast(val); - request.headers["Content-Length"] = val; - L(FL("scgi: content length: %d") % content_length); + char ch = static_cast(io.get()); + if (ch == '\0') + break; + value += ch; } - else if (key == "SCGI" && val == "1") - request.version = "SCGI/1"; - else if (key == "SCGI" && val != "1") - return false; - else if (key == "REQUEST_METHOD") - { - L(FL("scgi request method: %s") % val); - request.method = val; - } - else if (key == "REQUEST_URI") - { - L(FL("scgi request uri: %s") % val); - request.uri = val; - } - else if (key == "CONTENT_TYPE") - { - L(FL("scgi request content-type: %s") % val); - request.headers["Content-Type"] = val; - } - - netstring_len -= key.size(); - netstring_len -= val.size(); - netstring_len -= 2; + return io.good(); } - if(!eat(in, ',')) return false; - - request.body.clear(); - request.body.reserve(content_length); - L(FL("reading %d bytes") % content_length); - - while (in.good() && (content_length > 0)) + bool read(http::request & r) { - request.body += static_cast(in.get()); - content_length--; - } + size_t len; + if (!http::connection::read(len, ":")) return false; + L(FL("read scgi netstring length: %d") % len); + while (len > 0) + { + string key, val; + if (!read(key)) return false; + if (!read(val)) return false; + len -= key.size(); + len -= val.size(); + len -= 2; - L(FL("read %d bytes, content_length now %d") % request.body.size() % content_length); + L(FL("read scgi header: %s: %s") % key % val); - return (content_length == 0); -} + if (key == "CONTENT_LENGTH") + r.headers["Content-Length"] = val; + else if (key == "SCGI" && val == "1") + r.version = "SCGI/1"; + else if (key == "REQUEST_METHOD") + r.method = val; + else if (key == "REQUEST_URI") + r.uri = val; + else if (key == "CONTENT_TYPE") + r.headers["Content-Type"] = val; + } -static bool -parse_http_request(istream &in, http::request & request) -{ - if (!in.good()) return false; + L(FL("read scgi request: %s %s %s") % r.method % r.uri % r.version); - size_t content_length = 0; + // this is a loose interpretation of the scgi "spec" + if (r.version != scgi::version) return false; + if (r.headers.find("Content-Length") == r.headers.end()) return false; - // first line should be - // HTTP/1.1 CR LF - // following header lines are - // : value CR LF - // final header ending line is empty - // CR LF + if (!io.good()) return false; - string rest; - in >> request.method >> request.uri >> request.version; - std::getline(in, rest); + char comma = static_cast(io.get()); - L(FL("http request method: %s") % request.method); - L(FL("http request uri: %s") % request.uri); - L(FL("http request version: %s") % request.version); - - while (in.good() && in.peek() != '\r') - { - string key, val; - in >> key; - std::getline(in, val); - key = trim_right(key, ":"); - val = trim(val, " \r\n"); - - request.headers[key] = val; - - L(FL("http: got header: %s -> %s") % key % val); - if (lowercase(key) == "content-length") - { - content_length = lexical_cast(val); - L(FL("http: content length: %d") % content_length); - } + return http::connection::read_body(r); } - if (!eat(in, '\r')) return false; - if (!eat(in, '\n')) return false; - - request.body.clear(); - request.body.reserve(content_length); - L(FL("reading %d bytes") % content_length); - - while (in.good() && (content_length > 0)) + void write(http::response const & r) { - request.body += static_cast(in.get()); - content_length--; + http::connection::write_headers(r); + http::connection::write_body(r); } - L(FL("read %d bytes, content_length now %d") - % request.body.size() % content_length); + }; +}; - return (content_length == 0); -} +struct gserve_error +{ + string msg; + gserve_error(string const & s): msg(s) {} +}; static json_io::json_value_t do_cmd(database & db, json_io::json_object_t cmd_obj) @@ -399,77 +310,50 @@ void } void -process_request(connection::type type, - database & db, - istream & in, - ostream & out) +process_request(database & db, http::connection & connection) { http::request request; http::response response; - try + if (connection.read(request)) { - switch (type) + try { - case connection::scgi: - if (!parse_scgi_request(in, request)) - throw gserve_error("unable to parse SCGI headers"); - - L(FL("read %d-byte SCGI request") % request.body.size()); - break; - - case connection::http: - if (!parse_http_request(in, request)) - throw gserve_error("unable to parse HTTP headers"); - - L(FL("read %d-byte HTTP request") % request.body.size()); - break; + if (request.method == http::post && + request.headers["Content-Type"] == "application/jsonrequest") + process_json_request(db, request, response); + else + I(false); } - - if (request.method == http::post && - request.headers["Content-Type"] == "application/jsonrequest") - process_json_request(db, request, response); - else - I(false); - - if (type == connection::http) + catch (gserve_error & e) { - out << response.version << " " - << response.status_code << " " - << response.status_message << "\r\n"; + std::cerr << "gserve error -- " << e.msg << std::endl; + response.version = connection.version(); + response.status_code = 500; + response.status_message = "Internal Server Error"; + response.headers["Status"] = "500 Internal Server Error"; } - - // FIXME: this will always write the Connection: close header - // which may not make sense for scgi connections - for (map::const_iterator i = response.headers.begin(); - i != response.headers.end(); ++i) - out << i->first << ": " << i->second << "\r\n"; - - out << "\r\n"; - out << response.body; - out.flush(); - // the old code set content-length to body.size()+1 and wrote a final "\n" here - // does that do anything useful or important? + catch (recoverable_failure & e) + { + std::cerr << "recoverable failure -- " << e.what() << std::endl; + response.version = connection.version(); + response.status_code = 500; + response.status_message = "Internal Server Error"; + response.headers["Status"] = "500 Internal Server Error"; + } } - catch (gserve_error & e) + else { - // FIXME: move these into the response and write it out in one place - std::cerr << "gserve error -- " << e.msg << std::endl; - out << "Status: 400 Bad request\r\n" - << "Content-Type: application/jsonrequest\r\n" - << "\r\n"; - out.flush(); + std::cerr << "bad request" << std::endl; + + response.version = connection.version(); + response.status_code = 400; + response.status_message = "Bad Request"; + response.headers["Status"] = "400 Bad Request"; } - catch (recoverable_failure & e) - { - // FIXME: move these into the response and write it out in one place - std::cerr << "recoverable failure -- " << e.what() << std::endl; - out << "Status: 400 Bad request\r\n" - << "Content-Type: application/jsonrequest\r\n" - << "\r\n"; - out.flush(); - } + connection.write(response); + } @@ -493,11 +377,9 @@ CMD_NO_WORKSPACE(gserve, // C database db(app); key_store keys(app); - connection::type type = connection::scgi; size_t default_port = constants::default_scgi_port; if (app.opts.bind_http) { - type = connection::http; default_port = constants::default_http_port; } @@ -518,9 +400,9 @@ CMD_NO_WORKSPACE(gserve, // C else if (!app.opts.bind_stdio) W(F("The --no-transport-auth option is usually only used in combination with --stdio")); - if (app.opts.bind_stdio) - process_request(type, db, std::cin, std::cout); - else +// if (app.opts.bind_stdio) +// process_request(type, db, std::cin, std::cout); +// else { #ifdef USE_IPV6 @@ -566,11 +448,21 @@ CMD_NO_WORKSPACE(gserve, // C Netxx::Stream stream(peer.get_socketfd()); Netxx::Netbuf buf(stream); std::iostream io(&buf); - // this could be an http or scgi transaction - // with either raw data or json data - // possibly this should loop until a Connection: close header is received - // although that's probably not right for scgi connections - process_request(type, db, io, io); + + // possibly this should loop until a Connection: close + // header is received although that's probably not + // right for scgi connections + + if (app.opts.bind_http) + { + http::connection connection(io); + process_request(db, connection); + } + else + { + scgi::connection connection(io); + process_request(db, connection); + } stream.close(); } else ============================================================ --- http_client.cc aef4eb97b685e449f8d700747e81b8dcd30536c5 +++ http_client.cc d93bba1032bf5b94648705eeee7b58d38e0901dd @@ -70,6 +70,12 @@ http_client::http_client(options & opts, open(true) {} +string +http_client::resolve(string const & relative_uri) +{ + return info.client.u.path + relative_uri; +} + void http_client::execute(http::request const & request, http::response & response) { @@ -91,50 +97,10 @@ http_client::execute(http::request const I(io); I(open); - L(FL("http_client: sending request [[POST %s HTTP/1.1]]") - % (info.client.u.path)); + // the uri in this request is relative to the server uri and needs to be adjusted + http::connection connection(*io); + connection.write(request); - (*io) << request.method << " " - << info.client.u.path << " " - << request.version << "\r\n"; - - for (map::const_iterator i = request.headers.begin(); - i != request.headers.end(); ++i) - (*io) << i->first << ": " << i->second << "\r\n"; - - (*io) << "\r\n"; - - // FIXME: this used to set a Host header - //L(FL("http_client: to [[Host: %s]]") % request.headers["Host"]); - - L(FL("http_client: sending %d-byte body") % request.body.size()); - (*io) << request.body; - - io->flush(); - L(FL("http_client: sent %d byte request") % request.body.size()); - -// std::cerr << "http request" << std::endl -// << out.buf.data() << std::endl; - - // Now read back the result - parse_http_response(response); - - L(FL("http_client: received %d byte response") % response.body.size()); - -// std::cerr << "http response" << std::endl -// << data << std::endl; -} - -void -http_client::parse_http_status_line(http::response & response) -{ - // We're only interested in 200-series responses - L(FL("http_client: reading response...")); - (*io) >> response.version >> response.status_code; - std::getline(*io, response.status_message); - response.status_message = trim(response.status_message, " \r\n"); - - // sometimes we seem to get eof when reading the response -- not sure why yet if (io->good()) L(FL("connection is good")); if (io->bad()) @@ -144,62 +110,8 @@ http_client::parse_http_status_line(http if (io->eof()) L(FL("connection is eof")); - L(FL("http_client: response: [[%s %d %s]]") - % response.version % response.status_code % response.status_message); -} + I(connection.read(response)); -void -http_client::parse_http_header_line(http::response & response) -{ - string key, val; - (*io) >> key; - std::getline(*io, val); - - key = trim_right(key, ":"); - val = trim(val, " \r\n"); - - L(FL("http_client: header: [[%s %s]]") % key % val); - - response.headers[key] = val; -} - - -void -http_client::crlf() -{ - E(io->get() == '\r', origin::network, F("expected CR in HTTP response")); - E(io->get() == '\n', origin::network, F("expected LF in HTTP response")); -} - - -void -http_client::parse_http_response(http::response & response) -{ - size_t content_length = 0; - - response.headers.clear(); - response.body.clear(); - - parse_http_status_line(response); - while (io->good() && io->peek() != '\r') - parse_http_header_line(response); - crlf(); - - content_length = lexical_cast(response.headers["Content-Length"]); - - L(FL("http_client: receiving %d-byte body") % content_length); - - response.body.reserve(content_length); - while (io->good() && content_length > 0) - { - response.body += static_cast(io->get()); - content_length--; - } - - io->flush(); - - // something is wrong and the connection is sometimes closed by the server - // even though it did not issue a Connection: close header if (io->good()) L(FL("connection is good")); if (io->bad()) @@ -231,6 +143,10 @@ http_client::parse_http_response(http::r stream.reset(); open = false; } + + E(response.status_code == 200, origin::network, + F("request failed: %s %d %s") + % response.version % response.status_code % response.status_message); } ///////////////////////////////////////////////////////////////////// @@ -247,7 +163,7 @@ json_channel::transact(json_value_t v) c http::response response; request.method = http::post; - request.uri = ""; + request.uri = client.resolve("/"); request.version = http::version; request.headers["Content-Type"] = "application/jsonrequest"; request.headers["Content-Length"] = lexical_cast(out.buf.size()); ============================================================ --- http_client.hh 305c2fb19892bc9054e057a4ecd73693a0434dd0 +++ http_client.hh 0d36dc926a20df01f225f45e143f671c4e2520f6 @@ -42,12 +42,8 @@ struct http_client http_client(options & opts, lua_hooks & lua, netsync_connection_info const & info); + std::string resolve(std::string const & relative_uri); void execute(http::request const & request, http::response & response); - - void parse_http_status_line(http::response & response); - void parse_http_header_line(http::response & response); - void parse_http_response(http::response & response); - void crlf(); }; class json_channel