1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178
|
use core:io;
use parser;
/**
* A HTTP response.
*/
class Response {
// HTTP version.
Version version = Version:HTTP_1_0;
// HTTP status.
Status status = Status:OK;
// Headers to send. Keys are assumed to be all lowercase.
private Str->Str headers;
// Cookies to send. These are added automatically as headers if present.
Cookie[] cookies;
// Body of the response.
Buffer body;
// Default constructor.
init() {
init {}
}
// Create a 200 response that contains text/html.
init(Str response) {
init {
body = response.toUtf8();
}
contentType = "text/html; charset=utf-8";
}
// Create a response with a particular status and an associated message.
init(Str response, Status status) {
self(response);
status = status;
}
// Create with version and status.
init(Version version, Status status) {
init {
version = version;
status = status;
}
}
// Explicit set for content-type.
assign contentType(Str type) {
headers.put("content-type", type);
}
// Set header.
assign header(Str key, Str val) {
headers.put(key.toAsciiLowercase, val);
}
// Set header.
assign header(Buffer key, Buffer val) {
headers.put(key.toAsciiLowercase, val.fromUtf8);
}
// Get header.
Str? header(Str key) {
return headers.at(key.toAsciiLowercase);
}
// Get header, assume it exists.
Str getHeader(Str key) {
unless (r = header(key))
throw HttpError("Header ${key} does not exist.");
r;
}
// To string.
void toS(StrBuf to) {
to << "response {";
{
Indent z(to);
to << "\nversion: " << version;
to << "\nstatus: " << status;
to << "\nheaders: " << headers;
to << "\ncookies: " << cookies;
to << "\nbody: " << body;
}
to << "\n}";
}
// Serialize the request into a binary object.
// Returns 'true' if the connection should be closed afterwards.
Bool write(OStream to) {
Utf8Output text(to, windowsTextInfo(false));
text.autoFlush = false;
if (version == Version:HTTP_0_9) {
// HTTP 0.9 is special.
to.write(body);
return true;
} else if (version == Version:HTTP_1_0) {
text.write("HTTP/1.0 ");
} else if (version == Version:HTTP_1_1) {
text.write("HTTP/1.1 ");
} else {
throw HttpError("Invalid protocol version!");
}
text.write(status.v.toS);
text.write(" ");
text.write(status.toS);
text.write("\n");
for (k, v in headers) {
if (k == "content-length")
continue;
if (k == "content-encoding")
continue;
text.write(k);
text.write(": ");
text.write(v);
text.write("\n");
}
for (l in cookies) {
if (!l.cookieValid)
continue;
text.write("Set-Cookie: ");
text.write(l.toS);
text.write("\n");
}
text.write("Content-Length: ");
text.write(body.filled.toS);
text.write("\n\n");
text.flush();
to.write(body);
return false;
}
}
parseResponseHeader : parser(recursive descent, binary) {
start = SHttpResponse;
optional delimiter = SOptDelimiter;
required delimiter = SReqDelimiter;
void SOptDelimiter();
SOptDelimiter : " *";
void SReqDelimiter();
SReqDelimiter : " +";
void SNewline();
SNewline : "\r\n";
Response SHttpResponse();
SHttpResponse => Response(version, status)
: SVersion version ~ SStatus status ~ "[^\r\n]+" - SNewline - SHeaders(me);
Version SVersion();
SVersion => Version.HTTP_1_0() : "HTTP/1.0";
SVersion => Version.HTTP_1_1() : "HTTP/1.1";
Status SStatus();
SStatus => toStatus(code) : "[0-9]+" code;
void SHeaders(Response r);
SHeaders : SNewline;
SHeaders => header(r, k, v) : "[^:\r\n]+" k - ":" ~ "[^\r\n]+" v - SNewline - SHeaders(r);
}
private Status toStatus(Buffer buffer) {
Status(buffer.fromUtf8().toNat());
}
|