Add HTTPTransport, a Mac implementation, and an end-to-end test.

BUG=https://crbug.com/415544
R=mark@chromium.org

Review URL: https://codereview.chromium.org/692963002
This commit is contained in:
Robert Sesek 2014-10-31 12:17:32 -04:00
parent 45993a6959
commit d88711adfa
10 changed files with 802 additions and 5 deletions

23
util/net/http_headers.cc Normal file
View File

@ -0,0 +1,23 @@
// Copyright 2014 The Crashpad Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "util/net/http_headers.h"
namespace crashpad {
const char kContentType[] = "Content-Type";
const char kContentLength[] = "Content-Length";
} // namespace crashpad

34
util/net/http_headers.h Normal file
View File

@ -0,0 +1,34 @@
// Copyright 2014 The Crashpad Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef CRASHPAD_UTIL_NET_HTTP_HEADERS_H_
#define CRASHPAD_UTIL_NET_HTTP_HEADERS_H_
#include <map>
#include <string>
namespace crashpad {
//! \brief A map of HTTP header fields to their values.
using HTTPHeaders = std::map<std::string, std::string>;
//! \brief The header name `"Content-Type"`.
extern const char kContentType[];
//! \brief The header name `"Content-Length"`.
extern const char kContentLength[];
} // namespace crashpad
#endif // CRASHPAD_UTIL_NET_HTTP_HEADERS_H_

View File

@ -14,6 +14,7 @@
#include "util/net/http_multipart_builder.h" #include "util/net/http_multipart_builder.h"
#include <utility>
#include <vector> #include <vector>
#include "base/logging.h" #include "base/logging.h"
@ -152,7 +153,7 @@ scoped_ptr<HTTPBodyStream> HTTPMultipartBuilder::GetBodyStream() {
std::vector<HTTPBodyStream*> streams; std::vector<HTTPBodyStream*> streams;
for (const auto& pair : form_data_) { for (const auto& pair : form_data_) {
std::string field = GetFormDataBoundary(boundary(), pair.first); std::string field = GetFormDataBoundary(boundary_, pair.first);
field += kBoundaryCRLF; field += kBoundaryCRLF;
field += pair.second; field += pair.second;
field += kCRLF; field += kCRLF;
@ -161,7 +162,7 @@ scoped_ptr<HTTPBodyStream> HTTPMultipartBuilder::GetBodyStream() {
for (const auto& pair : file_attachments_) { for (const auto& pair : file_attachments_) {
const FileAttachment& attachment = pair.second; const FileAttachment& attachment = pair.second;
std::string header = GetFormDataBoundary(boundary(), pair.first); std::string header = GetFormDataBoundary(boundary_, pair.first);
header += base::StringPrintf("; filename=\"%s\"%s", header += base::StringPrintf("; filename=\"%s\"%s",
attachment.filename.c_str(), kCRLF); attachment.filename.c_str(), kCRLF);
header += base::StringPrintf("Content-Type: %s%s", header += base::StringPrintf("Content-Type: %s%s",
@ -173,11 +174,17 @@ scoped_ptr<HTTPBodyStream> HTTPMultipartBuilder::GetBodyStream() {
} }
streams.push_back( streams.push_back(
new StringHTTPBodyStream("--" + boundary() + "--" + kCRLF)); new StringHTTPBodyStream("--" + boundary_ + "--" + kCRLF));
return scoped_ptr<HTTPBodyStream>(new CompositeHTTPBodyStream(streams)); return scoped_ptr<HTTPBodyStream>(new CompositeHTTPBodyStream(streams));
} }
HTTPHeaders::value_type HTTPMultipartBuilder::GetContentType() const {
std::string content_type =
base::StringPrintf("multipart/form-data; boundary=%s", boundary_.c_str());
return std::make_pair(kContentType, content_type);
}
void HTTPMultipartBuilder::EraseKey(const std::string& key) { void HTTPMultipartBuilder::EraseKey(const std::string& key) {
auto data_it = form_data_.find(key); auto data_it = form_data_.find(key);
if (data_it != form_data_.end()) if (data_it != form_data_.end())

View File

@ -21,6 +21,7 @@
#include "base/basictypes.h" #include "base/basictypes.h"
#include "base/files/file_path.h" #include "base/files/file_path.h"
#include "base/memory/scoped_ptr.h" #include "base/memory/scoped_ptr.h"
#include "util/net/http_headers.h"
namespace crashpad { namespace crashpad {
@ -63,8 +64,8 @@ class HTTPMultipartBuilder {
//! \return A caller-owned HTTPBodyStream object. //! \return A caller-owned HTTPBodyStream object.
scoped_ptr<HTTPBodyStream> GetBodyStream(); scoped_ptr<HTTPBodyStream> GetBodyStream();
//! \brief Gets the boundary that will be used in GetBodyStream(). //! \brief Gets the header pair for `"Content-Type"`.
std::string boundary() const { return boundary_; } HTTPHeaders::value_type GetContentType() const;
private: private:
struct FileAttachment { struct FileAttachment {

View File

@ -0,0 +1,53 @@
// Copyright 2014 The Crashpad Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "util/net/http_transport.h"
#include "util/net/http_body.h"
namespace crashpad {
HTTPTransport::HTTPTransport()
: url_(),
method_("POST"),
headers_(),
body_stream_(),
timeout_(15.0) {
}
HTTPTransport::~HTTPTransport() {
}
void HTTPTransport::SetURL(const std::string& url) {
url_ = url;
}
void HTTPTransport::SetMethod(const std::string& method) {
method_ = method;
}
void HTTPTransport::SetHeader(const std::string& header,
const std::string& value) {
headers_[header] = value;
}
void HTTPTransport::SetBodyStream(scoped_ptr<HTTPBodyStream> stream) {
body_stream_ = stream.Pass();
}
void HTTPTransport::SetTimeout(double timeout) {
timeout_ = timeout;
}
} // namespace crashpad

102
util/net/http_transport.h Normal file
View File

@ -0,0 +1,102 @@
// Copyright 2014 The Crashpad Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef CRASHPAD_UTIL_NET_HTTP_TRANSPORT_H_
#define CRASHPAD_UTIL_NET_HTTP_TRANSPORT_H_
#include <string>
#include "base/basictypes.h"
#include "base/memory/scoped_ptr.h"
#include "util/net/http_headers.h"
namespace crashpad {
class HTTPBodyStream;
//! \brief HTTPTransport executes a HTTP request using the specified URL, HTTP
//! method, headers, and body. This class can only issue a synchronous
//! HTTP request.
//!
//! This class cannot be instantiated directly. A concrete subclass must be
//! instantiated instead, which provides an implementation to execute the
//! request that is appropriate for the host operating system.
class HTTPTransport {
public:
virtual ~HTTPTransport();
//! \brief Instantiates a concrete HTTPTransport class for the current
//! operating system.
//!
//! \return A new caller-owned HTTPTransport object.
static scoped_ptr<HTTPTransport> Create();
//! \brief Sets URL to which the request will be made.
//!
//! \param[in] url The request URL.
void SetURL(const std::string& url);
//! \brief Sets the HTTP method to execute. E.g., GET, POST, etc. The default
//! method is `"POST"`.
//!
//! \param[in] http_method The HTTP method.
void SetMethod(const std::string& http_method);
//! \brief Sets a HTTP header-value pair.
//!
//! \param[in] header The HTTP header name. Any previous value set at this
//! name will be overwritten.
//! \param[in] value The value to set for the header.
void SetHeader(const std::string& header, const std::string& value);
//! \brief Sets the stream object from which to generate the HTTP body.
//!
//! \param[in] stream A HTTPBodyStream, of which this class will take
//! ownership.
void SetBodyStream(scoped_ptr<HTTPBodyStream> stream);
//! \brief Sets the timeout for the HTTP request. The default is 15 seconds.
//!
//! \param[in] timeout The request timeout, in seconds.
void SetTimeout(double timeout);
//! \brief Performs the HTTP request with the configured parameters and waits
//! for the execution to complete.
//!
//! \return Whether or not the request was successful, defined as returning
//! a HTTP status 200 (OK) code.
virtual bool ExecuteSynchronously() = 0;
protected:
HTTPTransport();
const std::string& url() const { return url_; }
const std::string& method() const { return method_; }
const HTTPHeaders& headers() const { return headers_; }
HTTPBodyStream* body_stream() const { return body_stream_.get(); }
double timeout() const { return timeout_; }
private:
std::string url_;
std::string method_;
HTTPHeaders headers_;
scoped_ptr<HTTPBodyStream> body_stream_;
double timeout_;
DISALLOW_COPY_AND_ASSIGN(HTTPTransport);
};
} // namespace crashpad
#endif // CRASHPAD_UTIL_NET_HTTP_TRANSPORT_H_

View File

@ -0,0 +1,192 @@
// Copyright 2014 The Crashpad Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "util/net/http_transport.h"
#import <Foundation/Foundation.h>
#include "base/mac/foundation_util.h"
#import "base/mac/scoped_nsobject.h"
#include "base/strings/stringprintf.h"
#include "base/strings/sys_string_conversions.h"
#include "util/net/http_body.h"
@interface CrashpadHTTPBodyStreamTransport : NSInputStream {
@private
NSStreamStatus streamStatus_;
id<NSStreamDelegate> delegate_;
crashpad::HTTPBodyStream* bodyStream_; // weak
}
- (instancetype)initWithBodyStream:(crashpad::HTTPBodyStream*)bodyStream;
@end
@implementation CrashpadHTTPBodyStreamTransport
- (instancetype)initWithBodyStream:(crashpad::HTTPBodyStream*)bodyStream {
if ((self = [super init])) {
streamStatus_ = NSStreamStatusNotOpen;
bodyStream_ = bodyStream;
}
return self;
}
// NSInputStream:
- (BOOL)hasBytesAvailable {
// Per Apple's documentation: "May also return YES if a read must be attempted
// in order to determine the availability of bytes."
switch (streamStatus_) {
case NSStreamStatusAtEnd:
case NSStreamStatusClosed:
case NSStreamStatusError:
return NO;
default:
return YES;
}
}
- (NSInteger)read:(uint8_t*)buffer maxLength:(NSUInteger)maxLen {
streamStatus_ = NSStreamStatusReading;
NSInteger rv = bodyStream_->GetBytesBuffer(buffer, maxLen);
if (rv == 0)
streamStatus_ = NSStreamStatusAtEnd;
else if (rv < 0)
streamStatus_ = NSStreamStatusError;
else
streamStatus_ = NSStreamStatusOpen;
return rv;
}
- (BOOL)getBuffer:(uint8_t**)buffer length:(NSUInteger*)length {
return NO;
}
// NSStream:
- (void)scheduleInRunLoop:(NSRunLoop*)runLoop
forMode:(NSString*)mode {
}
- (void)removeFromRunLoop:(NSRunLoop*)runLoop
forMode:(NSString*)mode {
}
- (void)open {
streamStatus_ = NSStreamStatusOpen;
}
- (void)close {
streamStatus_ = NSStreamStatusClosed;
}
- (NSStreamStatus)streamStatus {
return streamStatus_;
}
- (id<NSStreamDelegate>)delegate {
return delegate_;
}
- (void)setDelegate:(id)delegate {
delegate_ = delegate;
}
@end
namespace crashpad {
namespace {
class HTTPTransportMac final : public HTTPTransport {
public:
HTTPTransportMac();
~HTTPTransportMac() override;
bool ExecuteSynchronously() override;
private:
DISALLOW_COPY_AND_ASSIGN(HTTPTransportMac);
};
HTTPTransportMac::HTTPTransportMac() : HTTPTransport() {
}
HTTPTransportMac::~HTTPTransportMac() {
}
bool HTTPTransportMac::ExecuteSynchronously() {
DCHECK(body_stream());
@autoreleasepool {
NSString* url_ns_string = base::SysUTF8ToNSString(url());
NSURL* url = [NSURL URLWithString:url_ns_string];
NSMutableURLRequest* request =
[NSMutableURLRequest requestWithURL:url
cachePolicy:NSURLRequestUseProtocolCachePolicy
timeoutInterval:timeout()];
[request setHTTPMethod:base::SysUTF8ToNSString(method())];
for (const auto& pair : headers()) {
[request setValue:base::SysUTF8ToNSString(pair.second)
forHTTPHeaderField:base::SysUTF8ToNSString(pair.first)];
}
base::scoped_nsobject<CrashpadHTTPBodyStreamTransport> transport(
[[CrashpadHTTPBodyStreamTransport alloc] initWithBodyStream:
body_stream()]);
[request setHTTPBodyStream:transport.get()];
NSURLResponse* response = nil;
NSError* error = nil;
[NSURLConnection sendSynchronousRequest:request
returningResponse:&response
error:&error];
if (error) {
LOG(ERROR) << [[error localizedDescription] UTF8String] << " ("
<< [[error domain] UTF8String] << " " << [error code] << ")";
return false;
}
if (!response) {
LOG(ERROR) << "no response";
return false;
}
NSHTTPURLResponse* http_response =
base::mac::ObjCCast<NSHTTPURLResponse>(response);
if (!http_response) {
LOG(ERROR) << "no http_response";
return false;
}
NSInteger http_status = [http_response statusCode];
if (http_status != 200) {
LOG(ERROR) << base::StringPrintf("HTTP status %ld",
static_cast<long>(http_status));
return false;
}
return true;
}
}
} // namespace
// static
scoped_ptr<HTTPTransport> HTTPTransport::Create() {
return scoped_ptr<HTTPTransport>(new HTTPTransportMac());
}
} // namespace crashpad

View File

@ -0,0 +1,242 @@
// Copyright 2014 The Crashpad Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "util/net/http_transport.h"
#include <stdint.h>
#include <string.h>
#include <vector>
#include "base/logging.h"
#include "base/memory/scoped_ptr.h"
#include "base/strings/stringprintf.h"
#include "build/build_config.h"
#include "gtest/gtest.h"
#include "util/file/fd_io.h"
#include "util/net/http_body.h"
#include "util/net/http_headers.h"
#include "util/net/http_multipart_builder.h"
#include "util/test/multiprocess_exec.h"
namespace crashpad {
namespace test {
namespace {
class HTTPTransportTestFixture : public MultiprocessExec {
public:
using RequestValidator =
void(*)(HTTPTransportTestFixture*, const std::string&);
HTTPTransportTestFixture(const HTTPHeaders& headers,
scoped_ptr<HTTPBodyStream> body_stream,
uint16_t http_response_code,
RequestValidator request_validator)
: MultiprocessExec(),
headers_(headers),
body_stream_(body_stream.Pass()),
response_code_(http_response_code),
request_validator_(request_validator) {
// TODO(rsesek): Use a more robust mechanism to locate testdata
// <https://code.google.com/p/crashpad/issues/detail?id=4>.
SetChildCommand("util/net/http_transport_test_server.py", nullptr);
}
const HTTPHeaders& headers() { return headers_; }
private:
void MultiprocessParent() override {
// The child will write the HTTP server port number as a packed unsigned
// short to stdout.
uint16_t port;
CheckedReadFD(ReadPipeFD(), &port, sizeof(port));
// Then the parent will tell the web server what response code to send
// for the HTTP request.
CheckedWriteFD(WritePipeFD(), &response_code_, sizeof(response_code_));
// Now execute the HTTP request.
scoped_ptr<HTTPTransport> transport(HTTPTransport::Create());
transport->SetMethod("POST");
transport->SetURL(base::StringPrintf("http://127.0.0.1:%d/upload", port));
for (const auto& pair : headers_) {
transport->SetHeader(pair.first, pair.second);
}
transport->SetBodyStream(body_stream_.Pass());
EXPECT_EQ(transport->ExecuteSynchronously(), (response_code_ == 200));
// Read until the child's stdout closes.
std::string request;
char buf[32];
ssize_t bytes_read;
while ((bytes_read = ReadFD(ReadPipeFD(), buf, sizeof(buf))) != 0) {
ASSERT_GE(bytes_read, 0);
request.append(buf, bytes_read);
}
if (request_validator_)
request_validator_(this, request);
}
HTTPHeaders headers_;
scoped_ptr<HTTPBodyStream> body_stream_;
uint16_t response_code_;
RequestValidator request_validator_;
};
const char kMultipartFormData[] = "multipart/form-data";
void GetHeaderField(const std::string& request,
const std::string& header,
std::string* value) {
size_t index = request.find(header);
ASSERT_NE(std::string::npos, index);
// Since the header is never the first line of the request, it should always
// be preceded by a CRLF.
EXPECT_EQ('\n', request[index - 1]);
EXPECT_EQ('\r', request[index - 2]);
index += header.length();
EXPECT_EQ(':', request[index++]);
// Per RFC 7230 §3.2, there can be one or more spaces or horizontal tabs.
// For testing purposes, just assume one space.
EXPECT_EQ(' ', request[index++]);
size_t header_end = request.find('\r', index);
ASSERT_NE(std::string::npos, header_end);
*value = request.substr(index, header_end - index);
}
void GetMultipartBoundary(const std::string& request,
std::string* multipart_boundary) {
std::string content_type;
GetHeaderField(request, kContentType, &content_type);
ASSERT_GE(content_type.length(), strlen(kMultipartFormData));
size_t index = strlen(kMultipartFormData);
EXPECT_EQ(kMultipartFormData, content_type.substr(0, index));
EXPECT_EQ(';', content_type[index++]);
size_t boundary_begin = content_type.find('=', index);
ASSERT_NE(std::string::npos, boundary_begin);
EXPECT_EQ('=', content_type[boundary_begin++]);
if (multipart_boundary) {
*multipart_boundary = content_type.substr(boundary_begin);
}
}
const char kBoundaryEq[] = "boundary=";
void ValidFormData(HTTPTransportTestFixture* fixture,
const std::string& request) {
std::string actual_boundary;
GetMultipartBoundary(request, &actual_boundary);
const auto& content_type = fixture->headers().find(kContentType);
ASSERT_NE(fixture->headers().end(), content_type);
size_t boundary = content_type->second.find(kBoundaryEq);
ASSERT_NE(std::string::npos, boundary);
std::string expected_boundary =
content_type->second.substr(boundary + strlen(kBoundaryEq));
EXPECT_EQ(expected_boundary, actual_boundary);
size_t body_start = request.find("\r\n\r\n");
ASSERT_NE(std::string::npos, body_start);
body_start += 4;
std::string expected = "--" + expected_boundary + "\r\n";
expected += "Content-Disposition: form-data; name=\"key1\"\r\n\r\n";
expected += "test\r\n";
ASSERT_LT(body_start + expected.length(), request.length());
EXPECT_EQ(expected, request.substr(body_start, expected.length()));
body_start += expected.length();
expected = "--" + expected_boundary + "\r\n";
expected += "Content-Disposition: form-data; name=\"key2\"\r\n\r\n";
expected += "--abcdefg123\r\n";
expected += "--" + expected_boundary + "--\r\n";
ASSERT_EQ(body_start + expected.length(), request.length());
EXPECT_EQ(expected, request.substr(body_start));
}
TEST(HTTPTransport, ValidFormData) {
HTTPMultipartBuilder builder;
builder.SetFormData("key1", "test");
builder.SetFormData("key2", "--abcdefg123");
HTTPHeaders headers;
headers.insert(builder.GetContentType());
HTTPTransportTestFixture test(headers, builder.GetBodyStream(), 200,
&ValidFormData);
test.Run();
}
const char kTextPlain[] = "text/plain";
void ErrorResponse(HTTPTransportTestFixture* fixture,
const std::string& request) {
std::string content_type;
GetHeaderField(request, kContentType, &content_type);
EXPECT_EQ(kTextPlain, content_type);
}
TEST(HTTPTransport, ErrorResponse) {
HTTPMultipartBuilder builder;
HTTPHeaders headers;
headers[kContentType] = kTextPlain;
HTTPTransportTestFixture test(headers, builder.GetBodyStream(),
404, &ErrorResponse);
test.Run();
}
const char kTextBody[] = "hello world";
void UnchunkedPlainText(HTTPTransportTestFixture* fixture,
const std::string& request) {
std::string header_value;
GetHeaderField(request, kContentType, &header_value);
EXPECT_EQ(kTextPlain, header_value);
GetHeaderField(request, kContentLength, &header_value);
const auto& content_length = fixture->headers().find(kContentLength);
ASSERT_NE(fixture->headers().end(), content_length);
EXPECT_EQ(content_length->second, header_value);
size_t body_start = request.rfind("\r\n");
ASSERT_NE(std::string::npos, body_start);
EXPECT_EQ(kTextBody, request.substr(body_start + 2));
}
TEST(HTTPTransport, UnchunkedPlainText) {
scoped_ptr<HTTPBodyStream> body_stream(new StringHTTPBodyStream(kTextBody));
HTTPHeaders headers;
headers[kContentType] = kTextPlain;
headers[kContentLength] = base::StringPrintf("%zu", strlen(kTextBody));
HTTPTransportTestFixture test(headers, body_stream.Pass(), 200,
&UnchunkedPlainText);
test.Run();
}
} // namespace
} // namespace test
} // namespace crashpad

View File

@ -0,0 +1,137 @@
#!/usr/bin/env python
# coding: utf-8
# Copyright 2014 The Crashpad Authors. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""A one-shot testing webserver.
When invoked, this server will write an integer to stdout, indiciating on which
port the server is listening. It will then read one integer from stdin,
indiciating the response code to set for a request. The server will process
one HTTP request, writing it out entirely to stdout, and will then terminate.
This server is written in Python since it provides a simple HTTP stack, and
because parsing Chunked encoding is safer and easier in a memory-safe language.
This could easily have been written in C++ instead.
"""
import BaseHTTPServer
import struct
import sys
class BufferedReadFile(object):
"""A File-like object that stores all read contents into a buffer."""
def __init__(self, real_file):
self.file = real_file
self.buffer = ""
def read(self, size=-1):
buf = self.file.read(size)
self.buffer += buf
return buf
def readline(self, size=-1):
buf = self.file.readline(size)
self.buffer += buf
return buf
def flush(self):
self.file.flush()
def close(self):
self.file.close()
class RequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
response_code = 500
def handle_one_request(self):
# Wrap the rfile in the buffering file object so that the raw header block
# can be written to stdout after it is parsed.
self.rfile = BufferedReadFile(self.rfile)
BaseHTTPServer.BaseHTTPRequestHandler.handle_one_request(self)
def do_POST(self):
writer = sys.stdout
writer.write(self.rfile.buffer)
self.rfile.buffer = ''
if self.headers.get('Transfer-Encoding', '') == 'Chunked':
body = self.handle_chunked_encoding()
else:
length = int(self.headers.get('Content-Length', -1))
body = self.rfile.read(length)
self.send_response(self.response_code)
writer.write(body)
writer.flush()
def handle_chunked_encoding(self):
"""This parses a "Transfer-Encoding: Chunked" body in accordance with
RFC 7230 §4.1. This returns the result as a string.
"""
body = ''
chunk_size = self.read_chunk_size()
while chunk_size > 0:
# Read the body.
data = self.rfile.read(chunk_size)
chunk_size -= len(data)
body += data
# Finished reading this chunk.
if chunk_size == 0:
# Read through any trailer fields.
trailer_line = self.rfile.readline()
while trailer_line.strip() != '':
trailer_line = self.rfile.readline()
# Read the chunk size.
chunk_size = self.read_chunk_size()
return body
def read_chunk_size(self):
# Read the whole line, including the \r\n.
chunk_size_and_ext_line = self.rfile.readline()
# Look for a chunk extension.
chunk_size_end = chunk_size_and_ext_line.find(';')
if chunk_size_end == -1:
# No chunk extensions; just encounter the end of line.
chunk_size_end = chunk_size_and_ext_line.find('\r')
if chunk_size_end == -1:
self.send_response(400) # Bad request.
return -1
return int(chunk_size_and_ext_line[:chunk_size_end], base=16)
def Main():
# Start the server.
server = BaseHTTPServer.HTTPServer(('127.0.0.1', 0), RequestHandler)
# Write the port as an unsigned short to the parent process.
sys.stdout.write(struct.pack('=H', server.server_address[1]))
sys.stdout.flush()
# Read the desired test response code as an unsigned short from the parent
# process.
RequestHandler.response_code = \
struct.unpack('=H', sys.stdin.read(struct.calcsize('=H')))[0]
# Handle the request.
server.handle_request()
if __name__ == '__main__':
Main()

View File

@ -71,8 +71,13 @@
'misc/uuid.h', 'misc/uuid.h',
'net/http_body.cc', 'net/http_body.cc',
'net/http_body.h', 'net/http_body.h',
'net/http_headers.cc',
'net/http_headers.h',
'net/http_multipart_builder.cc', 'net/http_multipart_builder.cc',
'net/http_multipart_builder.h', 'net/http_multipart_builder.h',
'net/http_transport.cc',
'net/http_transport.h',
'net/http_transport_mac.mm',
'numeric/checked_range.h', 'numeric/checked_range.h',
'numeric/in_range_cast.h', 'numeric/in_range_cast.h',
'numeric/int128.h', 'numeric/int128.h',
@ -220,6 +225,7 @@
'net/http_body_test_util.cc', 'net/http_body_test_util.cc',
'net/http_body_test_util.h', 'net/http_body_test_util.h',
'net/http_multipart_builder_test.cc', 'net/http_multipart_builder_test.cc',
'net/http_transport_test.cc',
'numeric/checked_range_test.cc', 'numeric/checked_range_test.cc',
'numeric/in_range_cast_test.cc', 'numeric/in_range_cast_test.cc',
'numeric/int128_test.cc', 'numeric/int128_test.cc',