# # # patch "Makefile.am" # from [5698691fdc83be520243d365cbb1a1379aa92415] # to [2d6b9e005b59ce2e1ca2e03373cbcbe5678d0ae4] # # patch "cmd_scgi.cc" # from [9914ca376bc5ff1571f325a8c61af12edc47e626] # to [82c2741e27572d2ba060867213b8504c1a9f09f0] # # patch "http_client.cc" # from [de43fa61ee3e1e89cda67d78e56fc69ef91beb61] # to [aef4eb97b685e449f8d700747e81b8dcd30536c5] # # patch "http_client.hh" # from [0419c936506dca108023fd5f9528d33ba735b696] # to [305c2fb19892bc9054e057a4ecd73693a0434dd0] # ============================================================ --- Makefile.am 5698691fdc83be520243d365cbb1a1379aa92415 +++ Makefile.am 2d6b9e005b59ce2e1ca2e03373cbcbe5678d0ae4 @@ -79,7 +79,7 @@ MOST_SOURCES = \ rev_height.cc rev_height.hh \ asciik.cc asciik.hh \ dates.cc dates.hh \ - http_client.cc http_client.hh \ + http.hh http_client.cc http_client.hh \ \ lru_writeback_cache.hh hybrid_map.hh \ \ ============================================================ --- cmd_scgi.cc 9914ca376bc5ff1571f325a8c61af12edc47e626 +++ cmd_scgi.cc 82c2741e27572d2ba060867213b8504c1a9f09f0 @@ -19,6 +19,7 @@ #include "globish.hh" #include "graph.hh" #include "gsync.hh" +#include "http.hh" #include "json_io.hh" #include "json_msgs.hh" #include "keys.hh" @@ -27,6 +28,7 @@ #include "lua.hh" #include "lua_hooks.hh" #include "netcmd.hh" +#include "simplestring_xform.hh" #include "transforms.hh" #include "ui.hh" @@ -76,9 +78,9 @@ using boost::lexical_cast; // This response format is not specified by the SCGI "spec". // -namespace +namespace connection { - enum connection_type { http, scgi }; + enum type { http, scgi }; }; @@ -113,7 +115,7 @@ static bool } static bool -parse_scgi_headers(istream & in, string & data) +parse_scgi_request(istream & in, http::request & request) { if (!in.good()) return false; @@ -140,13 +142,32 @@ parse_scgi_headers(istream & in, string string val = parse_str(in); L(FL("scgi: got header: %s -> %s") % key % val); + if (key == "CONTENT_LENGTH") { content_length = lexical_cast(val); + request.headers["Content-Length"] = val; L(FL("scgi: content length: %d") % content_length); } + 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(); @@ -155,37 +176,55 @@ parse_scgi_headers(istream & in, string if(!eat(in, ',')) return false; - data.clear(); - data.reserve(content_length); + request.body.clear(); + request.body.reserve(content_length); L(FL("reading %d bytes") % content_length); while (in.good() && (content_length > 0)) { - data += static_cast(in.get()); + request.body += static_cast(in.get()); content_length--; } - L(FL("read %d bytes, content_length now %d") % data.size() % content_length); + L(FL("read %d bytes, content_length now %d") % request.body.size() % content_length); return (content_length == 0); } static bool -parse_http_headers(istream &in, string & data) +parse_http_request(istream &in, http::request & request) { if (!in.good()) return false; size_t content_length = 0; + // first line should be + // HTTP/1.1 CR LF + // following header lines are + // : value CR LF + // final header ending line is empty + // CR LF + + string rest; + in >> request.method >> request.uri >> request.version; + std::getline(in, rest); + + 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, rest; - in >> key >> val; - std::getline(in, rest); + 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 (key == "Content-Length:" || - key == "Content-length:" || - key == "content-length:") + if (lowercase(key) == "content-length") { content_length = lexical_cast(val); L(FL("http: content length: %d") % content_length); @@ -195,17 +234,18 @@ parse_http_headers(istream &in, string & if (!eat(in, '\r')) return false; if (!eat(in, '\n')) return false; - data.clear(); - data.reserve(content_length); + request.body.clear(); + request.body.reserve(content_length); L(FL("reading %d bytes") % content_length); while (in.good() && (content_length > 0)) { - data += static_cast(in.get()); + request.body += static_cast(in.get()); content_length--; } - L(FL("read %d bytes, content_length now %d") % data.size() % content_length); + L(FL("read %d bytes, content_length now %d") + % request.body.size() % content_length); return (content_length == 0); } @@ -320,79 +360,101 @@ void void -process_transaction(connection_type type, - database & db, - std::istream & in, - std::ostream & out) +process_json_request(database & db, + http::request const & request, http::response & response) { - string data; - string name; + json_io::input_source in(request.body, "json"); + json_io::tokenizer tok(in); + json_io::parser p(tok); + json_io::json_object_t obj = p.parse_object(); + + if (static_cast(obj)) + { + transaction_guard guard(db); + L(FL("read JSON object")); + + json_io::json_value_t res = do_cmd(db, obj); // process json request + + if (static_cast(res)) + { + json_io::printer out_data; + res->write(out_data); + L(FL("sending JSON %d-byte response") % (out_data.buf.size())); + + response.version = http::version; + response.status_code = 200; + response.status_message = "OK"; + response.headers["Connection"] = "close"; + response.headers["Status"] = "200 OK"; + response.headers["Content-Length"] = lexical_cast(out_data.buf.size()); + response.headers["Content-Type"] = "application/jsonrequest"; + response.body = out_data.buf; + } + } + else + { + // FIXME: do something better for reporting errors from the server + std::cerr << "parse error" << std::endl; + } +} + +void +process_request(connection::type type, + database & db, + istream & in, + ostream & out) +{ + http::request request; + http::response response; + try { switch (type) { - case scgi: - if (!parse_scgi_headers(in, data)) + case connection::scgi: + if (!parse_scgi_request(in, request)) throw gserve_error("unable to parse SCGI headers"); - L(FL("read %d-byte SCGI request") % data.size()); - name = "scgi"; + L(FL("read %d-byte SCGI request") % request.body.size()); break; - case http: - if (!parse_http_headers(in, data)) + case connection::http: + if (!parse_http_request(in, request)) throw gserve_error("unable to parse HTTP headers"); - L(FL("read %d-byte HTTP request") % data.size()); - name = "http"; + L(FL("read %d-byte HTTP request") % request.body.size()); break; - } - json_io::input_source in(data, name); - json_io::tokenizer tok(in); - json_io::parser p(tok); - json_io::json_object_t obj = p.parse_object(); + if (request.method == http::post && + request.headers["Content-Type"] == "application/jsonrequest") + process_json_request(db, request, response); + else + I(false); - if (static_cast(obj)) + if (type == connection::http) { - transaction_guard guard(db); - L(FL("read JSON object")); + out << response.version << " " + << response.status_code << " " + << response.status_message << "\r\n"; + } - json_io::json_value_t res = do_cmd(db, obj); - if (static_cast(res)) - { - json_io::printer out_data; - res->write(out_data); - L(FL("sending JSON %d-byte response") % (out_data.buf.size() + 1)); + // 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"; - if (type == http) - out << "HTTP/1.1 200\r\n" - << "Connection: close\r\n"; - - // presumably the +1 in content-length below is for the final - // trailing newline? does the client side reader account for - // this? - out << "Status: 200 OK\r\n" - << "Content-Length: " << (out_data.buf.size() + 1) << "\r\n" - << "Content-Type: application/jsonrequest\r\n" - << "\r\n"; - - out.write(out_data.buf.data(), out_data.buf.size()); - out << "\n"; - out.flush(); - return; - } - } - else - { - // FIXME: do something better for reporting errors from the server - std::cerr << "parse error" << std::endl; - } + 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 (gserve_error & e) { - std::cerr << "scgi error -- " << e.msg << std::endl; + // 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"; @@ -400,12 +462,13 @@ process_transaction(connection_type type } catch (recoverable_failure & e) { - std::cerr << "informative failure -- " << e.what() << std::endl; + // 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(); - } + } } @@ -430,11 +493,11 @@ CMD_NO_WORKSPACE(gserve, // C database db(app); key_store keys(app); - connection_type type = scgi; + connection::type type = connection::scgi; size_t default_port = constants::default_scgi_port; if (app.opts.bind_http) { - type = http; + type = connection::http; default_port = constants::default_http_port; } @@ -456,7 +519,7 @@ CMD_NO_WORKSPACE(gserve, // C W(F("The --no-transport-auth option is usually only used in combination with --stdio")); if (app.opts.bind_stdio) - process_transaction(type, db, std::cin, std::cout); + process_request(type, db, std::cin, std::cout); else { @@ -503,7 +566,11 @@ CMD_NO_WORKSPACE(gserve, // C Netxx::Stream stream(peer.get_socketfd()); Netxx::Netbuf buf(stream); std::iostream io(&buf); - process_transaction(type, db, io, io); + // 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); stream.close(); } else ============================================================ --- http_client.cc de43fa61ee3e1e89cda67d78e56fc69ef91beb61 +++ http_client.cc aef4eb97b685e449f8d700747e81b8dcd30536c5 @@ -11,6 +11,7 @@ // micro http client implementation #include "base.hh" #include "globish.hh" +#include "http.hh" #include "http_client.hh" #include "json_io.hh" #include "json_msgs.hh" @@ -27,19 +28,26 @@ #include "netxx/timeout.h" #include "netxx_pipe.hh" +#include #include #include #include +#include #include -using json_io::json_value_t; using boost::shared_ptr; using boost::lexical_cast; + +using json_io::json_value_t; + +using std::map; using std::vector; using std::set; using std::string; using std::iostream; +using std::istringstream; +using std::ostringstream; using Netxx::Netbuf; using Netxx::Timeout; @@ -63,7 +71,7 @@ void {} void -http_client::execute(string const & request, string & response) +http_client::execute(http::request const & request, http::response & response) { if (!open) { @@ -83,25 +91,27 @@ http_client::execute(string const & requ I(io); I(open); - string header = (F("POST %s HTTP/1.1\r\n" - "Host: %s\r\n" - "Content-Length: %s\r\n" - "Content-Type: application/octet-stream\r\n" - "Accept: application/octet-stream\r\n" - "Accept-Encoding: identity\r\n" - "\r\n") - % (info.client.u.path.empty() ? "/" : info.client.u.path) - % info.client.u.host - % lexical_cast(request.size())).str(); + L(FL("http_client: sending request [[POST %s HTTP/1.1]]") + % (info.client.u.path)); - L(FL("http_client: sending request [[POST %s HTTP/1.1]]") - % (info.client.u.path.empty() ? "/" : info.client.u.path)); - L(FL("http_client: to [[Host: %s]]") % info.client.u.host); - L(FL("http_client: sending %d-byte body") % request.size()); - io->write(header.data(), header.size()); - io->write(request.data(), request.size()); + (*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 body") % request.size()); + L(FL("http_client: sent %d byte request") % request.body.size()); // std::cerr << "http request" << std::endl // << out.buf.data() << std::endl; @@ -109,19 +119,20 @@ http_client::execute(string const & requ // 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_client::parse_http_status_line(http::response & response) { // We're only interested in 200-series responses - string tmp; - string pat("HTTP/1.1 200"); L(FL("http_client: reading response...")); - while (io->good() && tmp.empty()) - std::getline(*io, tmp); + (*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()) @@ -133,26 +144,23 @@ http_client::parse_http_status_line() if (io->eof()) L(FL("connection is eof")); - L(FL("http_client: response: [[%s]]") % tmp); - E(tmp.substr(0,pat.size()) == pat, origin::network, - F("HTTP status line: %s") % tmp); + L(FL("http_client: response: [[%s %d %s]]") + % response.version % response.status_code % response.status_message); } void -http_client::parse_http_header_line(size_t & content_length, - bool & connection_close) +http_client::parse_http_header_line(http::response & response) { - string k, v, rest; - (*io) >> k >> v; - L(FL("http_client: header: [[%s %s]]") % k % v); - std::getline(*io, rest); + string key, val; + (*io) >> key; + std::getline(*io, val); - k = lowercase(k); - v = lowercase(v); - if (k == "content-length:") - content_length = lexical_cast(v); - else if (k == "connection:" && v == "close") - connection_close = true; + key = trim_right(key, ":"); + val = trim(val, " \r\n"); + + L(FL("http_client: header: [[%s %s]]") % key % val); + + response.headers[key] = val; } @@ -165,21 +173,26 @@ void void -http_client::parse_http_response(std::string & data) +http_client::parse_http_response(http::response & response) { size_t content_length = 0; - bool connection_close = false; - data.clear(); - parse_http_status_line(); + + response.headers.clear(); + response.body.clear(); + + parse_http_status_line(response); while (io->good() && io->peek() != '\r') - parse_http_header_line(content_length, connection_close); + 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) { - data += static_cast(io->get()); + response.body += static_cast(io->get()); content_length--; } @@ -208,9 +221,8 @@ http_client::parse_http_response(std::st // something is not working right so for now close the connection after // every request/response cycle - connection_close = true; - if (connection_close) + if (true || response.headers["Connection"] == "close") { L(FL("http_client: closing connection")); stream->close(); @@ -221,8 +233,6 @@ http_client::parse_http_response(std::st } } - - ///////////////////////////////////////////////////////////////////// // json_channel adaptor ///////////////////////////////////////////////////////////////////// @@ -233,11 +243,23 @@ json_channel::transact(json_value_t v) c json_io::printer out; v->write(out); - string request(out.buf); - string response; + http::request request; + http::response response; + + request.method = http::post; + request.uri = ""; + request.version = http::version; + request.headers["Content-Type"] = "application/jsonrequest"; + request.headers["Content-Length"] = lexical_cast(out.buf.size()); + request.headers["Accept"] = "application/jsonrequest"; + request.headers["Accept-Encoding"] = "identity"; + // FIXME: put a real host value in here (lighttpd seems to require a host header) + request.headers["Host"] = "localhost"; + request.body = out.buf; + client.execute(request, response); - json_io::input_source in(response, "json"); + json_io::input_source in(response.body, "json"); json_io::tokenizer tok(in); json_io::parser p(tok); return p.parse_object(); ============================================================ --- http_client.hh 0419c936506dca108023fd5f9528d33ba735b696 +++ http_client.hh 305c2fb19892bc9054e057a4ecd73693a0434dd0 @@ -16,6 +16,7 @@ #include "base.hh" #include "constants.hh" #include "gsync.hh" +#include "http.hh" #include "json_io.hh" #include @@ -41,12 +42,11 @@ struct http_client http_client(options & opts, lua_hooks & lua, netsync_connection_info const & info); - void execute(std::string const & request, std::string & response); + void execute(http::request const & request, http::response & response); - void parse_http_status_line(); - void parse_http_header_line(size_t & content_length, - bool & keepalive); - void parse_http_response(std::string & data); + 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(); }; @@ -62,7 +62,7 @@ public: json_io::json_value_t transact(json_io::json_value_t v) const; virtual void inquire_about_revs(std::set const & query_set, - std::set & theirs) const; + std::set & theirs) const; virtual void get_descendants(std::set const & common_revs, std::vector & inbound_revs) const;