monotone

monotone Mtn Source Tree

Root/http_tasks.cc

1// copyright (C) 2002, 2003 graydon hoare <graydon@pobox.com>
2// all rights reserved.
3// licensed to the public under the terms of the GNU GPL (>= 2)
4// see the file COPYING for details
5
6// HTTP is a much simpler protocol, so we implement the parts of it
7// we need to speak in just this one file, rather than having a
8// separate HTTP machine abstraction.
9
10// FIXME: the layering is weak in here; we might want to stratify
11// a bit if more than a couple simple methods appear necessary to
12// talk to depots. for now it is simple.
13
14#include "constants.hh"
15#include "network.hh"
16#include "packet.hh"
17#include "sanity.hh"
18#include "transforms.hh"
19#include "ui.hh"
20
21#include <string>
22#include <iostream>
23#include <boost/lexical_cast.hpp>
24#include <boost/shared_ptr.hpp>
25#include <boost/regex.hpp>
26
27using namespace std;
28using boost::lexical_cast;
29using boost::shared_ptr;
30
31bool post_http_packets(string const & group_name,
32 string const & user,
33 string const & signature,
34 string const & packets,
35 string const & http_host,
36 string const & http_path,
37 unsigned long port,
38 bool is_proxy,
39 std::iostream & stream)
40{
41 string query =
42 string("q=post&") +
43 "group=" + group_name + "&"
44 "user=" + user + "&"
45 "sig=" + signature;
46
47 string request = string("POST ");
48
49 // absurdly, HTTP 1.1 mandates *different* forms of request line
50 // depending on whether the client thinks it's talking to an origin
51 // server or a proxy server. clever.
52
53 if (is_proxy)
54 request += "http://" + http_host + ":" + lexical_cast<string>(port) + http_path;
55 else
56 request += http_path;
57
58 request += "?" + query + " HTTP/1.1";
59
60 stream << request << "\r\n";
61 L(F("HTTP -> '%s'\n") % request);
62
63 stream << "Host: " << http_host << "\r\n";
64 L(F("HTTP -> 'Host: %s'\n") % http_host);
65
66 stream << "Content-Length: " << packets.size() << "\r\n";
67 L(F("HTTP -> 'Content-Length: %d'\n") % packets.size());
68
69 stream << "Connection: close\r\n";
70 L(F("HTTP -> 'Connection: close'\n"));
71
72 stream << "\r\n";
73 stream.flush();
74
75 stream.write(packets.data(), packets.size());
76
77 // boost::socket appeared to have an incorrect implementation
78 // of overflow; I think I have fixed it. if not, comment out
79 // the above and re-enable this. it's slow but it works.
80 //
81 // for (size_t i = 0; i < packets.size(); ++i)
82 // { stream.put(packets.at(i)); stream.flush(); }
83
84 stream.flush();
85
86 L(F("HTTP -> %d bytes\n") % packets.size());
87
88 stream.flush();
89
90 int response = 0; string http;
91 bool ok = (stream >> http >> response &&
92 response >= 200 &&
93 response < 300);
94 L(F("HTTP <- %s %d\n") % http % response);
95 if (! ok)
96 {
97 string s;
98 char c;
99 while (stream.get(c))
100s += c;
101 L(F("HTTP ERROR: '%s'\n") % s);
102 }
103 return ok;
104}
105
106struct match_seq
107{
108 unsigned long & maj;
109 unsigned long & min;
110 unsigned long & end;
111 explicit match_seq(unsigned long & maj,
112 unsigned long & min,
113 unsigned long & end) : maj(maj), min(min), end(end) {}
114 bool operator()(boost::match_results<std::string::const_iterator,
115 boost::regex::alloc_type> const & res)
116 {
117 I(res.size() == 3);
118 std::string maj_s(res[1].first, res[1].second);
119 std::string min_s(res[2].first, res[2].second);
120 maj = lexical_cast<unsigned long>(maj_s);
121 min = lexical_cast<unsigned long>(min_s);
122 end = res.position() + res.length();
123 return true;
124 }
125};
126
127
128static bool scan_for_seq(string const & str,
129 unsigned long & maj,
130 unsigned long & min,
131 unsigned long & end)
132{
133 boost::regex expr("^\\[seq ([[:digit:]]+) ([[:digit:]]+)\\]$");
134 return boost::regex_grep(match_seq(maj, min, end), str, expr,
135 boost::match_not_dot_newline) != 0;
136}
137
138static void check_received_bytes(string const & tmp)
139{
140 size_t pos = tmp.find_first_not_of(constants::legal_packet_bytes);
141 N(pos == string::npos,
142 F("Bad char from network: pos %d, char '%d'\n")
143 % pos % static_cast<int>(tmp.at(pos)));
144}
145
146static void read_chunk(std::iostream & stream,
147 string & packet)
148{
149 char buf[constants::bufsz];
150 ios_base::fmtflags flags = stream.flags();
151 stream.setf(ios_base::hex, ios_base::basefield);
152 size_t chunk_size = 0;
153 stream >> chunk_size;
154 if (chunk_size == 0)
155 return;
156
157 char c = '\0';
158 N(stream.good(), F("malformed chunk, stream closed after nonzero chunk size"));
159 while (stream.good()) { stream.get(c); if (c != ' ') break; }
160 N(c == '\r', F("malformed chunk, no leading CR (got %d)") % static_cast<int>(c));
161 N(stream.good(), F("malformed chunk, stream closed after leading CR"));
162 stream.get(c); N(c == '\n', F("malformed chunk, no leading LF (got %d)") % static_cast<int>(c));
163 N(stream.good(), F("malformed chunk, stream closed after leading LF"));
164
165 while(chunk_size > 0)
166 {
167 size_t read_size = std::min(constants::bufsz, chunk_size);
168 stream.read(buf, read_size);
169 size_t actual_read_size = stream.gcount();
170 N(actual_read_size <= read_size, F("long chunked read from server"));
171 string tmp(buf, actual_read_size);
172 check_received_bytes(tmp);
173 packet.append(tmp);
174 chunk_size -= actual_read_size;
175 }
176
177 c = '\0';
178 while (stream.good()) { stream.get(c); if (c != ' ') break; }
179 N(c == '\r', F("malformed chunk, no trailing CR (got %d)") % static_cast<int>(c));
180 N(stream.good(), F("malformed chunk, stream closed after reailing CR"));
181 stream.get(c); N(c == '\n', F("malformed chunk, no trailing LF (got %d)") % static_cast<int>(c));
182 stream.flags(flags);
183}
184
185static void read_buffer(std::iostream & stream,
186string & packet)
187{
188 char buf[constants::bufsz];
189 stream.read(buf, constants::bufsz);
190 size_t bytes = stream.gcount();
191 N(bytes <= constants::bufsz, F("long read from server"));
192 string tmp(buf, bytes);
193 check_received_bytes(tmp);
194 packet.append(tmp);
195}
196
197void fetch_http_packets(string const & group_name,
198unsigned long & maj_number,
199unsigned long & min_number,
200packet_consumer & consumer,
201string const & http_host,
202string const & http_path,
203unsigned long port,
204bool is_proxy,
205std::iostream & stream)
206{
207
208 ticker n_packets("packets");
209 ticker n_bytes("bytes");
210
211 // step 1: make the request
212 string query =
213 string("q=since&") +
214 "group=" + group_name + "&"
215 "maj=" + lexical_cast<string>(maj_number) + "&"
216 "min=" + lexical_cast<string>(min_number);
217
218
219 string request = string("GET ");
220
221 // absurdly, HTTP 1.1 mandates *different* forms of request line
222 // depending on whether the client thinks it's talking to an origin
223 // server or a proxy server. clever.
224
225 if (is_proxy)
226 request += "http://" + http_host + ":" + lexical_cast<string>(port) + http_path;
227 else
228 request += http_path;
229
230 request += "?" + query + " HTTP/1.1";
231
232 stream << request << "\r\n";
233 L(F("HTTP -> '%s'\n") % request);
234
235 stream << "Host: " << http_host << "\r\n";
236 L(F("HTTP -> 'Host: %s'\n") % http_host);
237
238 stream << "Content-Length: 0\r\n";
239 L(F("HTTP -> 'Content-Length: 0'\n"));
240
241 stream << "Connection: close\r\n";
242 L(F("HTTP -> 'Connection: close'\n"));
243
244 stream << "\r\n";
245 stream.flush();
246
247 // step 2: skip most of the headers. either we get packets or we don't;
248 // how we get them, or what the HTTP server thinks, is mostly
249 // irrelevant. unless they send chunked transport encoding, in which case
250 // we need to change our read loop slightly.
251
252 bool chunked_transport_encoding = false;
253
254 {
255 bool in_headers = true;
256 while(stream.good() && in_headers)
257 {
258size_t linesz = 0xfff;
259char line[linesz];
260memset(line, 0, linesz);
261stream.getline(line, linesz, '\n');
262size_t bytes = stream.gcount();
263N(bytes < linesz, F("long header response line from server"));
264if (bytes > 0) bytes--;
265string tmp(line, bytes);
266if (bytes == 1 || tmp.empty() || tmp == "\r")
267 in_headers = false;
268else if (tmp.find("Transfer-Encoding") != string::npos
269 && tmp.find("chunked") != string::npos)
270 {
271 L(F("reading response as chunked encoding\n"));
272 chunked_transport_encoding = true;
273 }
274else
275 L(F("HTTP <- header %d bytes: '%s'\n") % bytes % tmp);
276 }
277 }
278
279 // step 3: read any packets
280 string packet;
281 packet.reserve(constants::bufsz);
282 {
283 while(stream.good())
284 {
285// WARNING: again, we are reading from the network here.
286// please use the utmost clarity and safety in this part.
287
288if (chunked_transport_encoding)
289 read_chunk(stream, packet);
290else
291 read_buffer(stream, packet);
292
293unsigned long end = 0;
294if (scan_for_seq(packet, maj_number, min_number, end))
295 {
296 // we are at the end of a packet
297 L(F("got sequence numbers %lu, %lu\n") % maj_number % min_number);
298 istringstream pkt(packet.substr(0,end));
299 n_packets += read_packets(pkt, consumer);
300 n_bytes += end;
301 packet.erase(0, end);
302 }
303 }
304
305 if (packet.size() != 0)
306 {
307L(F("%d trailing bytes from http\n") % packet.size());
308istringstream pkt(packet);
309n_packets += read_packets(pkt, consumer);
310n_bytes += packet.size();
311 }
312 }
313 P(F("http fetch complete\n"));
314}

Archive Download this file

Branches

Tags

Quick Links:     www.monotone.ca    -     Downloads    -     Documentation    -     Wiki    -     Code Forge    -     Build Status