# # # patch "cmd_netsync.cc" # from [0f8ed044a433df7a0bff0e76aa80bb3a46e718ac] # to [8afdb52702eb46694f476e910e538dca9dc2b0d6] # # patch "cmd_scgi.cc" # from [c7ae55b8a9c2d302285d1bbb01db6ef1c39b4b49] # to [89c2e17fe8de4f8e6ba7b1a6a08c91140f5fe992] # # patch "constants.hh" # from [1e47ea66a5b451a3191792383bf463e0f1076f26] # to [b1522df97c4b3fbc1b1e495bb43e025c30395162] # # patch "http_client.cc" # from [c5ace715abae7d5332b3e0e7d25ac77aae090040] # to [9701fd83cece30f2d98cef3cea8f8dd59e6ae5cb] # # patch "json_msgs.cc" # from [f14bafa35f5544386705727fcfd4c599320edcb6] # to [01a1ceced5984fcb428b110d8ca54c3707b0f148] # # patch "options_list.hh" # from [cc881707c4906d7122633711781c30b63109f668] # to [d4c1fcd779121004f5e1fd1204cadd004dc7fe50] # ============================================================ --- cmd_netsync.cc 0f8ed044a433df7a0bff0e76aa80bb3a46e718ac +++ cmd_netsync.cc 8afdb52702eb46694f476e910e538dca9dc2b0d6 @@ -530,6 +530,7 @@ CMD(gsync, "gsync", "", CMD_REF(network) database db(app); key_store keys(app); + // FIXME: allow this to connect to a server over stdio netsync_connection_info info; extract_client_connection_info(app.opts, app.lua, db, keys, args, info); ============================================================ --- cmd_scgi.cc c7ae55b8a9c2d302285d1bbb01db6ef1c39b4b49 +++ cmd_scgi.cc 89c2e17fe8de4f8e6ba7b1a6a08c91140f5fe992 @@ -76,11 +76,16 @@ using boost::lexical_cast; // This response format is not specified by the SCGI "spec". // +namespace +{ + enum connection_type { http, scgi }; +}; -struct scgi_error + +struct gserve_error { string msg; - scgi_error(string const & s): msg(s) {} + gserve_error(string const & s): msg(s) {} }; // Consume string until null or EOF. Consumes trailing null. @@ -108,9 +113,8 @@ static bool } static bool -parse_scgi(istream & in, string & data) +parse_scgi_headers(istream & in, string & data) { - if (!in.good()) return false; size_t netstring_len; @@ -166,6 +170,47 @@ parse_scgi(istream & in, string & data) return (content_length == 0); } +static bool +parse_http_headers(istream &in, string & data) +{ + if (!in.good()) return false; + + size_t content_length = 0; + + while (in.good() && in.peek() != '\r') + { + string key, val, rest; + in >> key >> val; + std::getline(in, rest); + L(FL("http: got header: %s -> %s") % key % val); + if (key == "Content-Length:" || + key == "Content-length:" || + key == "content-length:") + { + content_length = lexical_cast(val); + L(FL("http: content length: %d") % content_length); + } + } + + if (!eat(in, '\r')) return false; + if (!eat(in, '\n')) return false; + + data.clear(); + data.reserve(content_length); + L(FL("reading %d bytes") % content_length); + + while (in.good() && (content_length > 0)) + { + data += static_cast(in.get()); + content_length--; + } + + L(FL("read %d bytes, content_length now %d") % data.size() % content_length); + + return (content_length == 0); +} + + static json_io::json_value_t do_cmd(database & db, json_io::json_object_t cmd_obj) { @@ -275,22 +320,36 @@ void void -process_scgi_transaction(database & db, - std::istream & in, - std::ostream & out) +process_transaction(connection_type type, + database & db, + std::istream & in, + std::ostream & out) { string data; - + string name; try { - if (!parse_scgi(in, data)) - throw scgi_error("unable to parse SCGI request"); + switch (type) + { + case scgi: + if (!parse_scgi_headers(in, data)) + throw gserve_error("unable to parse SCGI headers"); - L(FL("read %d-byte SCGI request") % data.size()); + L(FL("read %d-byte SCGI request") % data.size()); + name = "scgi"; + break; - // std::cerr << "request" << std::endl << data << std::endl; + case http: + if (!parse_http_headers(in, data)) + throw gserve_error("unable to parse HTTP headers"); - json_io::input_source in(data, "scgi"); + L(FL("read %d-byte HTTP request") % data.size()); + name = "http"; + 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(); @@ -305,11 +364,15 @@ process_scgi_transaction(database & db, { json_io::printer out_data; res->write(out_data); + L(FL("sending JSON %d-byte response") % (out_data.buf.size() + 1)); - // std::cerr << "response" << std::endl << out_data.buf.data() << std::endl; + if (type == http) + out << "HTTP/1.1 200\r\n" + << "Connection: close\r\n"; - L(FL("sending JSON %d-byte response") % (out_data.buf.size() + 1)); - + // 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" @@ -327,7 +390,7 @@ process_scgi_transaction(database & db, std::cerr << "parse error" << std::endl; } } - catch (scgi_error & e) + catch (gserve_error & e) { std::cerr << "scgi error -- " << e.msg << std::endl; out << "Status: 400 Bad request\r\n" @@ -347,16 +410,17 @@ process_scgi_transaction(database & db, } -CMD_NO_WORKSPACE(scgi, // C - "scgi", // name +CMD_NO_WORKSPACE(gserve, // C + "gserve", // name "", // aliases CMD_REF(network), // parent N_(""), // params - N_("Serves SCGI+JSON connections"), // abstract + N_("Serves JSON connections over SCGI or HTTP"), // abstract "", // desc - options::opts::scgi_bind | options::opts::pidfile | + options::opts::bind | options::opts::bind_stdio | + options::opts::bind_http | options::opts::no_transport_auth ) { @@ -366,6 +430,14 @@ CMD_NO_WORKSPACE(scgi, // C database db(app); key_store keys(app); + connection_type type = scgi; + size_t default_port = constants::default_scgi_port; + if (app.opts.bind_http) + { + type = http; + default_port = constants::default_http_port; + } + if (app.opts.signing_key() == "") { rsa_keypair_id key; @@ -384,7 +456,7 @@ CMD_NO_WORKSPACE(scgi, // C W(F("The --no-transport-auth option is usually only used in combination with --stdio")); if (app.opts.bind_stdio) - process_scgi_transaction(db, std::cin, std::cout); + process_transaction(type, db, std::cin, std::cout); else { @@ -405,7 +477,7 @@ CMD_NO_WORKSPACE(scgi, // C Netxx::Address addr(use_ipv6); - add_address_names(addr, app.opts.bind_uris, constants::default_scgi_port); + add_address_names(addr, app.opts.bind_uris, default_port); // If we use IPv6 and the initialisation of server fails, we want // to try again with IPv4. The reason is that someone may have @@ -426,15 +498,19 @@ CMD_NO_WORKSPACE(scgi, // C Netxx::Peer peer = server.accept_connection(); if (peer) { + P(F("connection from %s:%d:%d") + % peer.get_address() % peer.get_port() % peer.get_local_port()); Netxx::Stream stream(peer.get_socketfd()); Netxx::Netbuf buf(stream); std::iostream io(&buf); - process_scgi_transaction(db, io, io); + process_transaction(type, db, io, io); + stream.close(); } else break; } } + // Possibly loop around if we get exceptions from Netxx and we're // attempting to use ipv6, or have some other reason to try again. catch (Netxx::NetworkException &) ============================================================ --- constants.hh 1e47ea66a5b451a3191792383bf463e0f1076f26 +++ constants.hh b1522df97c4b3fbc1b1e495bb43e025c30395162 @@ -108,7 +108,7 @@ namespace constants std::size_t const default_scgi_port = 3000; // standard HTTP port number - std::size_t const default_http_port = 80; + std::size_t const default_http_port = 8008; // remaining constants are related to netsync protocol ============================================================ --- http_client.cc c5ace715abae7d5332b3e0e7d25ac77aae090040 +++ http_client.cc 9701fd83cece30f2d98cef3cea8f8dd59e6ae5cb @@ -16,6 +16,7 @@ #include "json_msgs.hh" #include "netcmd.hh" #include "sanity.hh" +#include "simplestring_xform.hh" #include "lexical_cast.hh" #include "constants.hh" #include "uri.hh" @@ -84,19 +85,18 @@ http_client::transact_json(json_value_t json_io::printer out; v->write(out); - string header = (F("POST %s HTTP/1.0\r\n" + string header = (F("POST %s HTTP/1.1\r\n" "Host: %s\r\n" "Content-Length: %s\r\n" "Content-Type: application/jsonrequest\r\n" "Accept: application/jsonrequest\r\n" "Accept-Encoding: identity\r\n" - "Connection: Keep-Alive\r\n" "\r\n") % (info.client.u.path.empty() ? "/" : info.client.u.path) % info.client.u.host % lexical_cast(out.buf.size())).str(); - L(FL("http_client: sending request [[POST %s HTTP/1.0]]") + 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") % out.buf.size()); @@ -127,29 +127,40 @@ http_client::parse_http_status_line() { // We're only interested in 200-series responses string tmp; - string pat("HTTP/1.0 200"); + string pat("HTTP/1.1 200"); L(FL("http_client: reading response...")); while (io->good() && tmp.empty()) std::getline(*io, tmp); + + // 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()) + L(FL("connection is bad")); + if (io->fail()) + L(FL("connection is fail")); + if (io->eof()) + L(FL("connection is eof")); + L(FL("http_client: response: [[%s]]") % tmp); E(tmp.substr(0,pat.size()) == pat, F("HTTP status line: %s") % tmp); } void http_client::parse_http_header_line(size_t & content_length, - bool & keepalive) + bool & connection_close) { string k, v, rest; (*io) >> k >> v; L(FL("http_client: header: [[%s %s]]") % k % v); std::getline(*io, rest); - if (k == "Content-Length:" || k == "Content-length:" || - k == "content-length:") + k = lowercase(k); + v = lowercase(v); + if (k == "content-length:") content_length = lexical_cast(v); - else if (k == "Connection:" || k == "connection:") - keepalive = (v == "Keep-Alive" || v == "Keep-alive" || - v == "keep-alive"); + else if (k == "connection:" && v == "close") + connection_close = true; } @@ -165,11 +176,11 @@ http_client::parse_http_response(std::st http_client::parse_http_response(std::string & data) { size_t content_length = 0; - bool keepalive = false; + bool connection_close = false; data.clear(); parse_http_status_line(); while (io->good() && io->peek() != '\r') - parse_http_header_line(content_length, keepalive); + parse_http_header_line(content_length, connection_close); crlf(); L(FL("http_client: receiving %d-byte body") % content_length); @@ -181,11 +192,33 @@ http_client::parse_http_response(std::st } 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()) + L(FL("connection is bad")); + if (io->fail()) + L(FL("connection is fail")); + if (io->eof()) + L(FL("connection is eof")); // if we keep the connection alive, and we're limited to a single active // connection (as in the sample lighttpd.conf and required by the sqlite // database locking scheme) this will probably block all other clients. - if (!keepalive) + + // According to the scgi spec the server side will close the connection + // after processing each request. However, the connection being closed is + // the SCGI connection between the webserver and the monotone server, not + // the HTTP connection between the monotone client and the webserver, + // which may allow for connections to be kept alive. + + // something is not working right so for now close the connection after + // every request/response cycle + connection_close = true; + + if (connection_close) { L(FL("http_client: closing connection")); stream->close(); ============================================================ --- json_msgs.cc f14bafa35f5544386705727fcfd4c599320edcb6 +++ json_msgs.cc 01a1ceced5984fcb428b110d8ca54c3707b0f148 @@ -155,7 +155,7 @@ encode_msg_inquire_request(set::const_iterator i = revs.begin(); i != revs.end(); ++i) - r.add_str(i->inner()()); + r.add_str(encode_hexenc(i->inner()())); return b.v; } @@ -174,7 +174,7 @@ decode_msg_inquire_request(json_value_t std::string s; for (size_t i = 0; i < nargs; ++i) if (q[syms::revs][i].get(s)) - revs.insert(revision_id(s)); + revs.insert(revision_id(decode_hexenc(s))); return true; } } @@ -196,7 +196,7 @@ encode_msg_inquire_response(set::const_iterator i = revs.begin(); i != revs.end(); ++i) { - r.add_str(i->inner()()); + r.add_str(encode_hexenc(i->inner()())); } return b.v; } @@ -216,7 +216,7 @@ decode_msg_inquire_response(json_value_t if (r.len(nrevs)) for (size_t i = 0; i < nrevs; ++i) if (r[i].get(tmp)) - revs.insert(revision_id(tmp)); + revs.insert(revision_id(decode_hexenc(tmp))); return true; } return false; @@ -234,7 +234,7 @@ encode_msg_descendants_request(set::const_iterator i = revs.begin(); i != revs.end(); ++i) - r.add_str(i->inner()()); + r.add_str(encode_hexenc(i->inner()())); return b.v; } @@ -253,7 +253,7 @@ decode_msg_descendants_request(json_valu std::string s; for (size_t i = 0; i < nargs; ++i) if (q[syms::revs][i].get(s)) - revs.insert(revision_id(s)); + revs.insert(revision_id(decode_hexenc(s))); return true; } } @@ -273,7 +273,7 @@ encode_msg_descendants_response(vector::const_iterator i = revs.begin(); i != revs.end(); ++i) - r.add_str(i->inner()()); + r.add_str(encode_hexenc(i->inner()())); return b.v; } @@ -292,7 +292,7 @@ decode_msg_descendants_response(json_val std::string s; for (size_t i = 0; i < nargs; ++i) if (q[syms::revs][i].get(s)) - revs.push_back(revision_id(s)); + revs.push_back(revision_id(decode_hexenc(s))); return true; } } @@ -341,8 +341,8 @@ encode_cset(builder b, cset const & cs) { builder tmp = b.add_obj(); tmp[syms::patch].str(i->first.as_internal()); - tmp[syms::from].str(i->second.first.inner()()); - tmp[syms::to].str(i->second.second.inner()()); + tmp[syms::from].str(encode_hexenc(i->second.first.inner()())); + tmp[syms::to].str(encode_hexenc(i->second.second.inner()())); } for (set >::const_iterator @@ -400,8 +400,8 @@ decode_cset(query q, cset & cs) I(change[syms::from].get(from)); I(change[syms::to].get(to)); cs.deltas_applied.insert(make_pair(file_path_internal(path), - make_pair(file_id(from), - file_id(to)))); + make_pair(file_id(decode_hexenc(from)), + file_id(decode_hexenc(to))))); } else if (change[syms::clear].get(path)) { @@ -439,7 +439,7 @@ encode_rev(builder b, revision_t const & e != rev.edges.end(); ++e) { builder edge = edges.add_obj(); - edge[syms::old_revision].str(edge_old_revision(e).inner()()); + edge[syms::old_revision].str(encode_hexenc(edge_old_revision(e).inner()())); builder changes = edge[syms::changes].arr(); encode_cset(changes, edge_changes(e)); } @@ -466,7 +466,7 @@ decode_rev(query q, revision_t & rev) query changes = edge[syms::changes]; shared_ptr cs(new cset()); decode_cset(changes, *cs); - rev.edges.insert(make_pair(revision_id(old_revision), cs)); + rev.edges.insert(make_pair(revision_id(decode_hexenc(old_revision)), cs)); } rev.made_for = made_for_database; } @@ -552,7 +552,7 @@ encode_msg_get_full_rev_request(revision builder b; b[syms::type].str(syms::get_full_rev_request()); b[syms::vers].str("1"); - b[syms::id].str(rid.inner()()); + b[syms::id].str(encode_hexenc(rid.inner()())); return b.v; } @@ -566,7 +566,7 @@ decode_msg_get_full_rev_request(json_val q[syms::vers].get(vers) && vers == "1" && q[syms::id].get(id)) { - rid = revision_id(id); + rid = revision_id(decode_hexenc(id)); return true; } return false; ============================================================ --- options_list.hh cc881707c4906d7122633711781c30b63109f668 +++ options_list.hh d4c1fcd779121004f5e1fd1204cadd004dc7fe50 @@ -84,24 +84,17 @@ OPTVAR(bind_opts, bool, bind_stdio, fals OPTSET(bind_opts) OPTVAR(bind_opts, std::list, bind_uris, ) OPTVAR(bind_opts, bool, bind_stdio, false) +OPTVAR(bind_opts, bool, bind_http, false) OPTVAR(bind_opts, bool, use_transport_auth, true) OPTION(bind_opts, bind, true, "bind", - gettext_noop("address:port to listen on (default :4691)")) + gettext_noop("address:port to listen on (netsync default :4691; scgi default :3000; http default :8008)")) #ifdef option_bodies { bind_uris.push_back(utf8(arg)); bind_stdio = false; } #endif -OPTION(bind_opts, scgi_bind, true, "bind", - gettext_noop("address:port to listen on (default :3000)")) -#ifdef option_bodies -{ - bind_uris.push_back(utf8(arg)); - bind_stdio = false; -} -#endif OPTION(bind_opts, no_transport_auth, false, "no-transport-auth", gettext_noop("disable transport authentication")) #ifdef option_bodies @@ -110,12 +103,19 @@ OPTION(bind_opts, bind_stdio, false, "st } #endif OPTION(bind_opts, bind_stdio, false, "stdio", - gettext_noop("serve netsync on stdio")) + gettext_noop("serve netsync/gsync on stdio")) #ifdef option_bodies { bind_stdio = true; } #endif +OPTION(bind_opts, bind_http, false, "http", + gettext_noop("serve gsync over raw http (without scgi)")) +#ifdef option_bodies +{ + bind_http = true; +} +#endif OPTVAR(branch, branch_name, branchname, ) OPTION(branch, branch, true, "branch,b",