Rig out transport handling, finish identity reader
New patch will implement more transports, such as chunked and gzip.
This commit is contained in:
parent
91e0a6874b
commit
946c985a02
|
@ -34,6 +34,6 @@ export fn main() void = {
|
||||||
log::printfln("{}: {}", name, val);
|
log::printfln("{}: {}", name, val);
|
||||||
};
|
};
|
||||||
|
|
||||||
const body = resp.body as *http::reader;
|
const body = resp.body as *io::stream;
|
||||||
io::copy(os::stdout, body)!;
|
io::copy(os::stdout, body)!;
|
||||||
};
|
};
|
||||||
|
|
|
@ -9,6 +9,7 @@ use net;
|
||||||
use os;
|
use os;
|
||||||
use strconv;
|
use strconv;
|
||||||
use strings;
|
use strings;
|
||||||
|
use types;
|
||||||
|
|
||||||
// Performs an HTTP [[request]] with the given [[client]]. The request is
|
// Performs an HTTP [[request]] with the given [[client]]. The request is
|
||||||
// performed synchronously; this function blocks until the server has returned
|
// performed synchronously; this function blocks until the server has returned
|
||||||
|
@ -49,15 +50,28 @@ export fn do(client: *client, req: *request) (response | error) = {
|
||||||
read_statusline(&resp, &scan)?;
|
read_statusline(&resp, &scan)?;
|
||||||
read_header(&resp.header, &scan)?;
|
read_header(&resp.header, &scan)?;
|
||||||
|
|
||||||
|
const cl = header_get(&resp.header, "Content-Length");
|
||||||
|
const te = header_get(&resp.header, "Transfer-Encoding");
|
||||||
|
if (len(cl) > 1 || len(te) > 1) {
|
||||||
|
return protoerr;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (len(te) == 1) {
|
||||||
|
abort(); // TODO: Assign transport encoding appropriately
|
||||||
|
} else {
|
||||||
|
let length = types::SIZE_MAX;
|
||||||
|
if (len(cl) == 1) {
|
||||||
|
length = match (strconv::stoz(cl[0])) {
|
||||||
|
case let z: size =>
|
||||||
|
yield z;
|
||||||
|
case =>
|
||||||
|
return protoerr;
|
||||||
|
};
|
||||||
|
};
|
||||||
const remain = bufio::scan_buffer(&scan);
|
const remain = bufio::scan_buffer(&scan);
|
||||||
const rd = alloc(reader {
|
resp.body = new_identity_reader(conn, remain, length);
|
||||||
vtable = &reader_vtable,
|
};
|
||||||
conn = conn,
|
|
||||||
...
|
|
||||||
});
|
|
||||||
rd.buffer[..len(remain)] = remain[..];
|
|
||||||
rd.pending = len(remain);
|
|
||||||
resp.body = rd;
|
|
||||||
return resp;
|
return resp;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -11,6 +11,7 @@ export type header = [](str, str);
|
||||||
// Adds a given HTTP header, which may be added more than once. The name should
|
// Adds a given HTTP header, which may be added more than once. The name should
|
||||||
// be canonicalized by the caller.
|
// be canonicalized by the caller.
|
||||||
export fn header_add(head: *header, name: str, val: str) void = {
|
export fn header_add(head: *header, name: str, val: str) void = {
|
||||||
|
assert(len(name) > 1 && len(val) > 1);
|
||||||
append(head, (strings::dup(name), strings::dup(val)));
|
append(head, (strings::dup(name), strings::dup(val)));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -34,6 +35,18 @@ export fn header_del(head: *header, name: str) void = {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Retrieves a value, or values, from a header. The empty slice indicates the
|
||||||
|
// abscence of a header.
|
||||||
|
export fn header_get(head: *header, name: str) []str = {
|
||||||
|
for (let i = 0z; i < len(head); i += 1) {
|
||||||
|
const (key, val) = head[i];
|
||||||
|
if (key == name) {
|
||||||
|
return [val];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
return [];
|
||||||
|
};
|
||||||
|
|
||||||
// Frees state associated with an HTTP [[header]].
|
// Frees state associated with an HTTP [[header]].
|
||||||
export fn header_free(head: *header) void = {
|
export fn header_free(head: *header) void = {
|
||||||
for (let i = 0z; i < len(head); i += 1) {
|
for (let i = 0z; i < len(head); i += 1) {
|
||||||
|
|
|
@ -12,44 +12,12 @@ export type response = struct {
|
||||||
// The HTTP headers provided by the server.
|
// The HTTP headers provided by the server.
|
||||||
header: header,
|
header: header,
|
||||||
// The response body, if any.
|
// The response body, if any.
|
||||||
body: nullable *reader,
|
body: nullable *io::stream,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Frees state associated with an HTTP [[response]].
|
// Frees state associated with an HTTP [[response]].
|
||||||
export fn response_finish(resp: *response) void = {
|
export fn response_finish(resp: *response) void = {
|
||||||
header_free(&resp.header);
|
header_free(&resp.header);
|
||||||
free(resp.reason);
|
free(resp.reason);
|
||||||
};
|
free(resp.body);
|
||||||
|
|
||||||
export type reader = struct {
|
|
||||||
vtable: io::stream,
|
|
||||||
conn: io::handle,
|
|
||||||
buffer: [os::BUFSIZ]u8,
|
|
||||||
pending: size,
|
|
||||||
};
|
|
||||||
|
|
||||||
const reader_vtable = io::vtable {
|
|
||||||
reader = &reader_read,
|
|
||||||
...
|
|
||||||
};
|
|
||||||
|
|
||||||
fn reader_read(s: *io::stream, buf: []u8) (size | io::EOF | io::error) = {
|
|
||||||
let rd = s: *reader;
|
|
||||||
if (rd.pending == 0) {
|
|
||||||
match (io::read(rd.conn, rd.buffer)?) {
|
|
||||||
case let n: size =>
|
|
||||||
rd.pending = n;
|
|
||||||
case io::EOF =>
|
|
||||||
return io::EOF;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
let n = len(buf);
|
|
||||||
if (n > rd.pending) {
|
|
||||||
n = rd.pending;
|
|
||||||
};
|
|
||||||
buf[..n] = rd.buffer[..n];
|
|
||||||
rd.buffer[..len(rd.buffer) - n] = rd.buffer[n..];
|
|
||||||
rd.pending -= n;
|
|
||||||
return n;
|
|
||||||
};
|
};
|
||||||
|
|
69
net/http/transport.ha
Normal file
69
net/http/transport.ha
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
use io;
|
||||||
|
use os;
|
||||||
|
|
||||||
|
export type identity_reader = struct {
|
||||||
|
vtable: io::stream,
|
||||||
|
conn: io::handle,
|
||||||
|
buffer: [os::BUFSIZ]u8,
|
||||||
|
pending: size,
|
||||||
|
length: size,
|
||||||
|
};
|
||||||
|
|
||||||
|
const identity_reader_vtable = io::vtable {
|
||||||
|
reader = &identity_read,
|
||||||
|
...
|
||||||
|
};
|
||||||
|
|
||||||
|
// Creates a new reader that reads data until the response's Content-Length is
|
||||||
|
// reached; i.e. the null Transport-Encoding.
|
||||||
|
fn new_identity_reader(
|
||||||
|
conn: io::handle,
|
||||||
|
buffer: []u8,
|
||||||
|
content_length: size,
|
||||||
|
) *io::stream = {
|
||||||
|
let rd = alloc(identity_reader {
|
||||||
|
vtable = &identity_reader_vtable,
|
||||||
|
conn = conn,
|
||||||
|
length = content_length,
|
||||||
|
...
|
||||||
|
});
|
||||||
|
rd.buffer[..len(buffer)] = buffer[..];
|
||||||
|
rd.pending = len(buffer);
|
||||||
|
return rd;
|
||||||
|
};
|
||||||
|
|
||||||
|
fn identity_read(
|
||||||
|
s: *io::stream,
|
||||||
|
buf: []u8,
|
||||||
|
) (size | io::EOF | io::error) = {
|
||||||
|
let rd = s: *identity_reader;
|
||||||
|
assert(rd.vtable == &identity_reader_vtable);
|
||||||
|
|
||||||
|
if (rd.length <= 0) {
|
||||||
|
return io::EOF;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (rd.pending == 0) {
|
||||||
|
let nread = rd.length;
|
||||||
|
if (nread > len(rd.buffer)) {
|
||||||
|
nread = len(rd.buffer);
|
||||||
|
};
|
||||||
|
|
||||||
|
match (io::read(rd.conn, rd.buffer[..nread])?) {
|
||||||
|
case let n: size =>
|
||||||
|
rd.pending = n;
|
||||||
|
case io::EOF =>
|
||||||
|
return io::EOF;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
let n = len(buf);
|
||||||
|
if (n > rd.pending) {
|
||||||
|
n = rd.pending;
|
||||||
|
};
|
||||||
|
buf[..n] = rd.buffer[..n];
|
||||||
|
rd.buffer[..len(rd.buffer) - n] = rd.buffer[n..];
|
||||||
|
rd.pending -= n;
|
||||||
|
rd.length -= n;
|
||||||
|
return n;
|
||||||
|
};
|
Loading…
Reference in a new issue