Skip to content
This repository was archived by the owner on Feb 14, 2025. It is now read-only.

Commit 30d379a

Browse files
committed
wip
1 parent ed6400f commit 30d379a

File tree

4 files changed

+550
-201
lines changed

4 files changed

+550
-201
lines changed

gojo/net/http/header.mojo

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -152,11 +152,11 @@ struct Header(CollectionElement):
152152

153153
fn set(inout self, inout key: String, value: String) raises:
154154
"""Sets the header entries associated with key to the
155-
single element value. It replaces any existing values
156-
associated with key. The key is case insensitive; it is
157-
canonicalized by [canonical_mime_header_key].
158-
To use non-canonical keys, assign to the map directly.
159-
"""
155+
single element value. It replaces any existing values
156+
associated with key. The key is case insensitive; it is
157+
canonicalized by [canonical_mime_header_key].
158+
To use non-canonical keys, assign to the map directly.
159+
"""
160160
var mime_header = MIMEHeader(self.value)
161161
mime_header.set(key, value)
162162

gojo/net/http/response.mojo

Lines changed: 262 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,262 @@
1+
from memory._arc import Arc
2+
from collections.optional import Optional
3+
import ...io
4+
import ...bufio
5+
from ...builtins import Result, WrappedError
6+
from ...fmt import sprintf
7+
from .header import Header
8+
from .request import Request
9+
from .status import status_text
10+
from .transfer import TransferWriter
11+
12+
13+
fn remove_prefix(input_string: String, prefix: String) -> String:
14+
if input_string.startswith(prefix):
15+
return input_string[len(prefix) :]
16+
return input_string
17+
18+
19+
struct Response[RWC: io.ReadWriteCloser](Movable):
20+
"""Response represents the response from an HTTP request.
21+
22+
The [Client] and [Transport] return Responses from servers once
23+
the response headers have been received. The response body
24+
is streamed on demand as the body field is read."""
25+
26+
var status: String # e.g. "200 OK"
27+
var status_code: Int # e.g. 200
28+
var proto: String # e.g. "HTTP/1.0"
29+
var proto_major: Int # e.g. 1
30+
var proto_minor: Int # e.g. 0
31+
32+
# Header maps header keys to values. If the response had multiple
33+
# headers with the same key, they may be concatenated, with comma
34+
# delimiters. (RFC 7230, section 3.2.2 requires that multiple headers
35+
# be semantically equivalent to a comma-delimited sequence.) When
36+
# Header values are duplicated by other fields in this struct (e.g.,
37+
# content_length, TransferEncoding, Trailer), the field values are
38+
# authoritative.
39+
40+
# Keys in the map are canonicalized (see CanonicalHeaderKey).
41+
var header: Header
42+
43+
# body represents the response body.
44+
45+
# The response body is streamed on demand as the body field
46+
# is read. If the network connection fails or the server
47+
# terminates the response, body.Read calls return an error.
48+
49+
# The http Client and Transport guarantee that body is always
50+
# non-nil, even on responses without a body or responses with
51+
# a zero-length body. It is the caller's responsibility to
52+
# close body. The default HTTP client's Transport may not
53+
# reuse HTTP/1.x "keep-alive" TCP connections if the body is
54+
# not read to completion and closed.
55+
56+
# The body is automatically dechunked if the server replied
57+
# with a "chunked" Transfer-Encoding.
58+
var body: RWC
59+
60+
# content_length records the length of the associated content. The
61+
# value -1 indicates that the length is unknown. Unless Request.Method
62+
# is "HEAD", values >= 0 indicate that the given number of bytes may
63+
# be read from body.
64+
var content_length: Int64
65+
66+
# Close records whether the header directed that the connection be
67+
# closed after reading body. The value is advice for clients: neither
68+
# ReadResponse nor Response.Write ever closes a connection.
69+
var close: Bool
70+
71+
# Request is the request that was sent to obtain this Response.
72+
# Request's body is nil (having already been consumed).
73+
# This is only populated for Client requests.
74+
var request: Arc[Request]
75+
76+
fn __init__(inout self, status: String, proto: String, proto_major: Int, proto_minor: Int, header: Header, body: RWC, content_length: Int64, close: Bool, request: Arc[Request]):
77+
self.status = status
78+
self.proto = proto
79+
self.proto_major = proto_major
80+
self.proto_minor = proto_minor
81+
self.header = header
82+
self.body = body ^
83+
self.content_length = content_length
84+
self.close = close
85+
self.request = request
86+
87+
fn __moveinit__(inout self, owned other: Response[RWC]):
88+
self.status = other.status ^
89+
self.status_code = other.status_code
90+
self.proto = other.proto ^
91+
self.proto_major = other.proto_major
92+
self.proto_minor = other.proto_minor
93+
self.header = other.header ^
94+
self.body = other.body ^
95+
self.content_length = other.content_length
96+
self.close = other.close
97+
self.request = other.request ^
98+
99+
fn proto_at_least(self, major: Int, minor: Int) -> Bool:
100+
"""Reports whether the HTTP protocol used
101+
in the response is at least major.minor.
102+
103+
Args:
104+
major: Major version.
105+
minor: Minor version.
106+
"""
107+
return self.proto_major > major or
108+
self.proto_major == major and self.proto_minor >= minor
109+
110+
fn write[W: io.Writer](inout self, inout writer: W) raises -> Optional[WrappedError]:
111+
"""Writes r to w in the HTTP/1.x server response format,
112+
including the status line, headers, body, and optional trailer.
113+
114+
This method consults the following fields of the response r:
115+
116+
status_code
117+
proto_major
118+
proto_minor
119+
Request.Method
120+
TransferEncoding
121+
Trailer
122+
body
123+
content_length
124+
Header, values for non-canonical keys will have unpredictable behavior
125+
The Response body is closed after it is sent.
126+
"""
127+
# Status line
128+
var text = self.status
129+
if text == "":
130+
text = status_text(self.status_code)
131+
if text == "":
132+
text = "status code " + str(self.status_code)
133+
134+
else:
135+
# Just to reduce stutter, if user set self.Status to "200 OK" and status_code to 200.
136+
# Not important.
137+
text = remove_prefix(text, str(self.status_code) + " ")
138+
139+
var first_line = sprintf(String("HTTP/%d.%d %d %s\r\n"), self.proto_major, self.proto_minor, self.status_code, text)
140+
var result = writer.write(first_line.as_bytes())
141+
if result.error:
142+
return result.error
143+
144+
if self.content_length == 0:
145+
# Is it actually 0 length? Or just unknown?
146+
var buf = List[Int8](capacity=1)
147+
result = self.body.read(buf)
148+
if result.error:
149+
var err = result.unwrap_error()
150+
if str(err) != io.EOF:
151+
return err
152+
153+
if result.value == 0:
154+
# Reset it to a known zero reader, in case underlying one
155+
# is unhappy being read repeatedly.
156+
# self.body = NoBody
157+
pass
158+
else:
159+
self.content_length = -1
160+
161+
# If we're sending a non-chunked HTTP/1.1 response without a
162+
# content-length, the only way to do that is the old HTTP/1.0
163+
# way, by noting the EOF with a connection close, so we need
164+
# to set Close.
165+
if self.content_length == -1 and not self.close and self.proto_at_least(1, 1):
166+
self.close = True
167+
168+
# Process body,content_length,Close,Trailer
169+
var tw = TransferWriter(r1)
170+
if err != nil:
171+
return err
172+
173+
err = tw.writeHeader(w, nil)
174+
if err != nil:
175+
return err
176+
177+
# Rest of header
178+
err = self.header.WriteSubset(w, respExcludeHeader)
179+
if err != nil:
180+
return err
181+
182+
# contentLengthAlreadySent may have been already sent for
183+
# POST/PUT requests, even if zero length. See Issue 8180.
184+
# contentLengthAlreadySent = tw.shouldSendContentLength()
185+
if self.content_length == 0 and !chunked(self.TransferEncoding) and !contentLengthAlreadySent and bodyAllowedForStatus(self.status_code):
186+
if _, err = io.WriteString(w, "Content-Length: 0\r\n"); err != nil:
187+
return err
188+
189+
# End-of-header
190+
if _, err = io.WriteString(w, "\r\n"); err != nil:
191+
return err
192+
193+
# Write body and trailer
194+
err = tw.writeBody(w)
195+
if err != nil:
196+
return err
197+
198+
Success
199+
return nil
200+
201+
fn closeBody():
202+
if self.body != nil:
203+
self.body.Close()
204+
205+
206+
207+
fn read_response[R: io.Reader](r: Arc[bufio.Reader[R]], req: Arc[Request]) -> Arc[Response]:
208+
"""Reads and returns an HTTP response from self.
209+
The req parameter optionally specifies the [Request] that corresponds
210+
to this [Response]. If nil, a GET request is assumed.
211+
Clients must call resp.body.Close when finished reading resp.body.
212+
After that call, clients can inspect resp.Trailer to find key/value
213+
pairs included in the response trailer."""
214+
tp = textproto.NewReader(r)
215+
resp = &Response{
216+
Request: req,
217+
218+
Parse the first line of the response.
219+
line, err = tp.ReadLine()
220+
if err != nil:
221+
if err == io.EOF:
222+
err = io.ErrUnexpectedEOF
223+
224+
return nil, err
225+
226+
proto, status, ok = strings.Cut(line, " ")
227+
if not ok:
228+
return nil, badStringError("malformed HTTP response", line)
229+
230+
resp.Proto = proto
231+
resp.Status = strings.TrimLeft(status, " ")
232+
233+
statusCode, _, _ = strings.Cut(resp.Status, " ")
234+
if len(statusCode) != 3:
235+
return nil, badStringError("malformed HTTP status code", statusCode)
236+
237+
resp.status_code, err = strconv.Atoi(statusCode)
238+
if err != nil or resp.status_code < 0:
239+
return nil, badStringError("malformed HTTP status code", statusCode)
240+
241+
if resp.proto_major, resp.proto_minor, ok = ParseHTTPVersion(resp.Proto); !ok:
242+
return nil, badStringError("malformed HTTP version", resp.Proto)
243+
244+
245+
Parse the response headers.
246+
mimeHeader, err = tp.ReadMIMEHeader()
247+
if err != nil:
248+
if err == io.EOF:
249+
err = io.ErrUnexpectedEOF
250+
251+
return nil, err
252+
253+
resp.Header = Header(mimeHeader)
254+
255+
fixPragmaCacheControl(resp.Header)
256+
257+
err = readTransfer(resp, r)
258+
if err != nil:
259+
return nil, err
260+
261+
262+
return resp, nil

0 commit comments

Comments
 (0)