diff --git a/gojo/net/http/header.mojo b/gojo/net/http/header.mojo index 811f91a..a8c18f6 100644 --- a/gojo/net/http/header.mojo +++ b/gojo/net/http/header.mojo @@ -3,54 +3,36 @@ from ...builtins import ascii from ...io import io -@value -struct StringKey(KeyElement): - var s: String - - fn __init__(inout self, owned s: String): - self.s = s ^ - - fn __init__(inout self, s: StringLiteral): - self.s = String(s) - - fn __hash__(self) -> Int: - return hash(self.s) - - fn __eq__(self, other: Self) -> Bool: - return self.s == other.s - - fn __ne__(self, other: Self) -> Bool: - return self.s != other.s - - fn __str__(self) -> String: - return self.s - - fn valid_header_field_byte(char: Int8) raises -> Bool: """Mask is a 128-bit bitmap with 1s for allowed bytes, so that the byte c can be tested with a shift and an and. If c >= 128, then 1<>64)) != 0 + var mask = 0 | (1 << (10) - 1) << atol("0") | (1 << (26) - 1) << atol("a") | (1 << (26) - 1) << atol( + "A" + ) | 1 << atol("!") | 1 << atol("#") | 1 << atol("$") | 1 << atol("%") | 1 << atol("&") | 1 << atol("'") | 1 << atol( + "*" + ) | 1 << atol( + "+" + ) | 1 << atol( + "-" + ) | 1 << atol( + "." + ) | 1 << atol( + "^" + ) | 1 << atol( + "_" + ) | 1 << atol( + "`" + ) | 1 << atol( + "|" + ) | 1 << atol( + "~" + ) + return ( + (UInt64(1) << char.cast[DType.uint64]()) & (mask & (1 << 64 - 1)) + | (UInt64(1) << (char.cast[DType.uint64]() - 64)) & (mask >> 64) + ) != 0 fn canonical_mime_header_key(key: String) raises -> String: @@ -64,21 +46,21 @@ fn canonical_mime_header_key(key: String) raises -> String: returned without modifications.""" var upper = True var i = 0 - var header_key = String(key) # operate on copy + var header_key = String(key) # operate on copy while i < len(header_key): var char = header_key[i] if not valid_header_field_byte(ord(char)): return header_key - if (upper) and (ord('a') <= ord(char)) and (ord(char) <= ord('z')): + if (upper) and (ord("a") <= ord(char)) and (ord(char) <= ord("z")): header_key = canonical_mime_header_key(header_key) return header_key - if (not upper) and (ord('A') <= ord(char)) and (ord(char) <= ord('Z')): + if (not upper) and (ord("A") <= ord(char)) and (ord(char) <= ord("Z")): header_key = canonical_mime_header_key(header_key) return header_key - upper = (char == '-') + upper = char == "-" i += 1 return header_key @@ -88,28 +70,29 @@ fn canonical_mime_header_key(key: String) raises -> String: struct MIMEHeader(CollectionElement): """A MIMEHeader represents a MIME-style header mapping keys to sets of values.""" - var value: Dict[StringKey, List[String]] + + var value: Dict[String, List[String]] fn add(inout self, key: String, value: String) raises: """Adds the key, value pair to the header. - It appends to any existing values associated with key.""" + It appends to any existing values associated with key.""" var mime_key = canonical_mime_header_key(key) self.value[mime_key].append(value) fn set(inout self, key: String, value: String) raises: """Sets the header entries associated with key to - the single element value. It replaces any existing - values associated with key.""" + the single element value. It replaces any existing + values associated with key.""" var keys = List[String]() keys.append(value) self.value[canonical_mime_header_key(key)] = keys fn get(self, key: String) raises -> String: """Gets the first value associated with the given key. - It is case insensitive; [canonical_mime_header_key] is used - to canonicalize the provided key. - If there are no values associated with the key, Get returns "". - To use non-canonical keys, access the map directly.""" + It is case insensitive; [canonical_mime_header_key] is used + to canonicalize the provided key. + If there are no values associated with the key, Get returns "". + To use non-canonical keys, access the map directly.""" if len(self.value) == 0: return "" @@ -121,10 +104,10 @@ struct MIMEHeader(CollectionElement): fn values(self, key: String) raises -> Optional[List[String]]: """Returns all values associated with the given key. - It is case insensitive; [canonical_mime_header_key] is - used to canonicalize the provided key. To use non-canonical - keys, access the map directly. - The returned slice is not a copy.""" + It is case insensitive; [canonical_mime_header_key] is + used to canonicalize the provided key. To use non-canonical + keys, access the map directly. + The returned slice is not a copy.""" if len(self.value) == 0: return None @@ -137,10 +120,10 @@ struct MIMEHeader(CollectionElement): @value struct Header(CollectionElement): - var value: Dict[StringKey, List[String]] + var value: Dict[String, List[String]] - fn __init__(inout self, value: Dict[StringKey, List[String]] = Dict[StringKey, List[String]]()): - self.value = Dict[StringKey, List[String]]() + fn __init__(inout self, value: Dict[String, List[String]] = Dict[String, List[String]]()): + self.value = value fn add(inout self, key: String, value: String) raises: """Add adds the key, value pair to the header. @@ -163,20 +146,20 @@ struct Header(CollectionElement): fn get(self, key: String) raises -> String: """Gets the first value associated with the given key. If - there are no values associated with the key, Get returns "". - It is case insensitive; [canonical_mime_header_key] is - used to canonicalize the provided key. Get assumes that all - keys are stored in canonical form. To use non-canonical keys, - access the map directly.""" + there are no values associated with the key, Get returns "". + It is case insensitive; [canonical_mime_header_key] is + used to canonicalize the provided key. Get assumes that all + keys are stored in canonical form. To use non-canonical keys, + access the map directly.""" var mime_header = MIMEHeader(self.value) return mime_header.get(key) fn values(self, key: String) raises -> Optional[List[String]]: """Values returns all values associated with the given key. - It is case insensitive; [canonical_mime_header_key] is - used to canonicalize the provided key. To use non-canonical - keys, access the map directly. - The returned slice is not a copy.""" + It is case insensitive; [canonical_mime_header_key] is + used to canonicalize the provided key. To use non-canonical + keys, access the map directly. + The returned slice is not a copy.""" var mime_header = MIMEHeader(self.value) return mime_header.values(key) @@ -214,7 +197,7 @@ struct Header(CollectionElement): var mime_header = MIMEHeader(self.value) mime_header.delete_values(key) - fn write[W: io.StringWriter](self, inout w: W): + fn write[W: io.Writer](self, inout w: W): """Write writes a header in wire format. Args: @@ -222,7 +205,7 @@ struct Header(CollectionElement): """ return self._write(w) - fn _write[W: io.StringWriter](self, inout w: W): + fn _write[W: io.Writer](self, inout w: W): """Write writes a header in wire format. Args: @@ -230,13 +213,11 @@ struct Header(CollectionElement): """ return self.write_subset(w) - # # Clone returns a copy of h or nil if h is nil. # fn Clone() -> Header: # if h == nil: # return nil - # # Find total number of values. # nv := 0 # for _, vv := range h: @@ -257,7 +238,6 @@ struct Header(CollectionElement): # return h2 - # # sortedKeyValues returns h's keys sorted in the returned kvs # # slice. The headerSorter used to sort is also returned, for possible # # return to headerSorterCache. @@ -271,21 +251,18 @@ struct Header(CollectionElement): # if !exclude[k]: # kvs = append(kvs, keyValues{k, vv) - # hs.kvs = kvs # sort.Sort(hs) # return kvs, hs - # # WriteSubset writes a header in wire format. # # If exclude is not nil, keys where exclude[key] == true are not written. # # Keys are not canonicalized before checking the exclude map. # fn WriteSubset(w io.Writer, exclude map[String]bool): # return h.writeSubset(w, exclude, nil) - # TODO: Implement the key sorter stuff - fn write_subset[W: io.StringWriter](self, inout w: W): + fn write_subset[W: io.Writer](self, inout w: W): for item in self.value.items(): for value in item[].value: _ = w.write_string(String(item[].key) + ": " + value[] + "\r\n") @@ -312,16 +289,13 @@ struct Header(CollectionElement): # headerSorterPool.Put(sorter) # return err - # if trace != nil and trace.WroteHeaderField != nil: # formattedVals = append(formattedVals, v) - # if trace != nil and trace.WroteHeaderField != nil: # trace.WroteHeaderField(kv.key, formattedVals) # formattedVals = nil - # headerSorterPool.Put(sorter) @@ -348,7 +322,7 @@ fn has_token(value: String, token: String) -> Bool: return True var index = 0 - while index <= len(value)-len(token): + while index <= len(value) - len(token): # Check that first character is good. # The token is ASCII, so checking only a single byte # is sufficient. We skip this potential starting @@ -360,7 +334,7 @@ fn has_token(value: String, token: String) -> Bool: continue # Check that start pos is on a valid token boundary. - if index > 0 and not is_token_boundary(ord(value[index-1])): + if index > 0 and not is_token_boundary(ord(value[index - 1])): continue # Check that end pos is on a valid token boundary. @@ -368,7 +342,7 @@ fn has_token(value: String, token: String) -> Bool: if end_pos != len(value) and not is_token_boundary(ord(value[end_pos])): continue - if ascii.equal_fold(value[index:index+len(token)], token): + if ascii.equal_fold(value[index : index + len(token)], token): return True index += 1 @@ -377,7 +351,7 @@ fn has_token(value: String, token: String) -> Bool: fn is_token_boundary(b: Int8) -> Bool: - return b == ord(' ') or b == ord(',') or b == ord('\t') + return b == ord(" ") or b == ord(",") or b == ord("\t") # # var timeFormats = List[String]{ diff --git a/gojo/net/http/response.mojo b/gojo/net/http/response.mojo index 4ea83e0..440a104 100644 --- a/gojo/net/http/response.mojo +++ b/gojo/net/http/response.mojo @@ -1,8 +1,6 @@ -from memory._arc import Arc from collections.optional import Optional import ...io import ...bufio -from ...builtins import Result, WrappedError from ...fmt import sprintf from .header import Header from .request import Request @@ -23,11 +21,11 @@ struct Response[RWC: io.ReadWriteCloser](Movable): the response headers have been received. The response body is streamed on demand as the body field is read.""" - var status: String # e.g. "200 OK" - var status_code: Int # e.g. 200 - var proto: String # e.g. "HTTP/1.0" - var proto_major: Int # e.g. 1 - var proto_minor: Int # e.g. 0 + var status: String # e.g. "200 OK" + var status_code: Int # e.g. 200 + var proto: String # e.g. "HTTP/1.0" + var proto_major: Int # e.g. 1 + var proto_minor: Int # e.g. 0 # Header maps header keys to values. If the response had multiple # headers with the same key, they may be concatenated, with comma @@ -73,28 +71,39 @@ struct Response[RWC: io.ReadWriteCloser](Movable): # This is only populated for Client requests. var request: Arc[Request] - 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]): + fn __init__( + inout self, + status: String, + proto: String, + proto_major: Int, + proto_minor: Int, + header: Header, + owned body: RWC, + content_length: Int64, + close: Bool, + request: Arc[Request], + ): self.status = status self.proto = proto self.proto_major = proto_major self.proto_minor = proto_minor self.header = header - self.body = body ^ + self.body = body^ self.content_length = content_length self.close = close self.request = request fn __moveinit__(inout self, owned other: Response[RWC]): - self.status = other.status ^ + self.status = other.status^ self.status_code = other.status_code - self.proto = other.proto ^ + self.proto = other.proto^ self.proto_major = other.proto_major self.proto_minor = other.proto_minor - self.header = other.header ^ - self.body = other.body ^ + self.header = other.header^ + self.body = other.body^ self.content_length = other.content_length self.close = other.close - self.request = other.request ^ + self.request = other.request^ fn proto_at_least(self, major: Int, minor: Int) -> Bool: """Reports whether the HTTP protocol used @@ -104,159 +113,158 @@ struct Response[RWC: io.ReadWriteCloser](Movable): major: Major version. minor: Minor version. """ - return self.proto_major > major or - self.proto_major == major and self.proto_minor >= minor - - fn write[W: io.Writer](inout self, inout writer: W) raises -> Optional[WrappedError]: - """Writes r to w in the HTTP/1.x server response format, - including the status line, headers, body, and optional trailer. - - This method consults the following fields of the response r: - - status_code - proto_major - proto_minor - Request.Method - TransferEncoding - Trailer - body - content_length - Header, values for non-canonical keys will have unpredictable behavior - The Response body is closed after it is sent. - """ - # Status line - var text = self.status - if text == "": - text = status_text(self.status_code) - if text == "": - text = "status code " + str(self.status_code) - - else: - # Just to reduce stutter, if user set self.Status to "200 OK" and status_code to 200. - # Not important. - text = remove_prefix(text, str(self.status_code) + " ") - - var first_line = sprintf(String("HTTP/%d.%d %d %s\r\n"), self.proto_major, self.proto_minor, self.status_code, text) - var result = writer.write(first_line.as_bytes()) - if result.error: - return result.error - - if self.content_length == 0: - # Is it actually 0 length? Or just unknown? - var buf = List[Int8](capacity=1) - result = self.body.read(buf) - if result.error: - var err = result.unwrap_error() - if str(err) != io.EOF: - return err - - if result.value == 0: - # Reset it to a known zero reader, in case underlying one - # is unhappy being read repeatedly. - # self.body = NoBody - pass - else: - self.content_length = -1 - - # If we're sending a non-chunked HTTP/1.1 response without a - # content-length, the only way to do that is the old HTTP/1.0 - # way, by noting the EOF with a connection close, so we need - # to set Close. - if self.content_length == -1 and not self.close and self.proto_at_least(1, 1): - self.close = True - - # Process body,content_length,Close,Trailer - var tw = TransferWriter(r1) - if err != nil: - return err - - err = tw.writeHeader(w, nil) - if err != nil: - return err - - # Rest of header - err = self.header.WriteSubset(w, respExcludeHeader) - if err != nil: - return err - - # contentLengthAlreadySent may have been already sent for - # POST/PUT requests, even if zero length. See Issue 8180. - # contentLengthAlreadySent = tw.shouldSendContentLength() - if self.content_length == 0 and !chunked(self.TransferEncoding) and !contentLengthAlreadySent and bodyAllowedForStatus(self.status_code): - if _, err = io.WriteString(w, "Content-Length: 0\r\n"); err != nil: - return err - - # End-of-header - if _, err = io.WriteString(w, "\r\n"); err != nil: - return err - - # Write body and trailer - err = tw.writeBody(w) - if err != nil: - return err - - Success - return nil - - fn closeBody(): - if self.body != nil: - self.body.Close() - - - -fn read_response[R: io.Reader](r: Arc[bufio.Reader[R]], req: Arc[Request]) -> Arc[Response]: - """Reads and returns an HTTP response from self. - The req parameter optionally specifies the [Request] that corresponds - to this [Response]. If nil, a GET request is assumed. - Clients must call resp.body.Close when finished reading resp.body. - After that call, clients can inspect resp.Trailer to find key/value - pairs included in the response trailer.""" - tp = textproto.NewReader(r) - resp = &Response{ - Request: req, - - Parse the first line of the response. - line, err = tp.ReadLine() - if err != nil: - if err == io.EOF: - err = io.ErrUnexpectedEOF - - return nil, err - - proto, status, ok = strings.Cut(line, " ") - if not ok: - return nil, badStringError("malformed HTTP response", line) - - resp.Proto = proto - resp.Status = strings.TrimLeft(status, " ") - - statusCode, _, _ = strings.Cut(resp.Status, " ") - if len(statusCode) != 3: - return nil, badStringError("malformed HTTP status code", statusCode) - - resp.status_code, err = strconv.Atoi(statusCode) - if err != nil or resp.status_code < 0: - return nil, badStringError("malformed HTTP status code", statusCode) - - if resp.proto_major, resp.proto_minor, ok = ParseHTTPVersion(resp.Proto); !ok: - return nil, badStringError("malformed HTTP version", resp.Proto) - - - Parse the response headers. - mimeHeader, err = tp.ReadMIMEHeader() - if err != nil: - if err == io.EOF: - err = io.ErrUnexpectedEOF - - return nil, err + return self.proto_major > major or self.proto_major == major and self.proto_minor >= minor + + +# fn write[W: io.Writer](inout self, inout writer: W) raises -> Error: +# """Writes r to w in the HTTP/1.x server response format, +# including the status line, headers, body, and optional trailer. + +# This method consults the following fields of the response r: + +# status_code +# proto_major +# proto_minor +# Request.Method +# TransferEncoding +# Trailer +# body +# content_length +# Header, values for non-canonical keys will have unpredictable behavior +# The Response body is closed after it is sent. +# """ +# # Status line +# var text = self.status +# if text == "": +# text = status_text(self.status_code) +# if text == "": +# text = "status code " + str(self.status_code) +# else: +# # Just to reduce stutter, if user set self.Status to "200 OK" and status_code to 200. +# # Not important. +# text = remove_prefix(text, str(self.status_code) + " ") + +# var first_line = sprintf(String("HTTP/%d.%d %d %s\r\n"), self.proto_major, self.proto_minor, self.status_code, text) +# var bytes_written: Int +# var err: Error +# bytes_written, err = writer.write(first_line.as_bytes()) +# if err: +# return err + +# if self.content_length == 0: +# # Is it actually 0 length? Or just unknown? +# var buf = List[UInt8](capacity=1) +# var bytes_read: Int +# bytes_read, err = self.body.read(buf) +# if err: +# if str(err) != io.EOF: +# return err + +# if bytes_read == 0: +# # Reset it to a known zero reader, in case underlying one +# # is unhappy being read repeatedly. +# # self.body = NoBody +# pass +# else: +# self.content_length = -1 + +# # If we're sending a non-chunked HTTP/1.1 response without a +# # content-length, the only way to do that is the old HTTP/1.0 +# # way, by noting the EOF with a connection close, so we need +# # to set Close. +# if self.content_length == -1 and not self.close and self.proto_at_least(1, 1): +# self.close = True + +# # Process body,content_length,Close,Trailer +# var tw = TransferWriter(self) +# if err != nil: +# return err + +# err = tw.writeHeader(w, nil) +# if err != nil: +# return err + +# # Rest of header +# err = self.header.WriteSubset(w, respExcludeHeader) +# if err != nil: +# return err + +# # contentLengthAlreadySent may have been already sent for +# # POST/PUT requests, even if zero length. See Issue 8180. +# # contentLengthAlreadySent = tw.shouldSendContentLength() +# if self.content_length == 0 and !chunked(self.TransferEncoding) and !contentLengthAlreadySent and bodyAllowedForStatus(self.status_code): +# if _, err = io.WriteString(w, "Content-Length: 0\r\n"); err != nil: +# return err + +# # End-of-header +# if _, err = io.WriteString(w, "\r\n"); err != nil: +# return err + +# # Write body and trailer +# err = tw.writeBody(w) +# if err != nil: +# return err + +# Success +# return nil + +# fn close_body(inout self) -> Error: +# return self.body.close() + + +# fn read_response[R: io.Reader](r: Arc[bufio.Reader[R]], req: Arc[Request]) -> Arc[Response]: +# """Reads and returns an HTTP response from self. +# The req parameter optionally specifies the [Request] that corresponds +# to this [Response]. If nil, a GET request is assumed. +# Clients must call resp.body.Close when finished reading resp.body. +# After that call, clients can inspect resp.Trailer to find key/value +# pairs included in the response trailer.""" +# tp = textproto.NewReader(r) +# resp = &Response{ +# Request: req, + +# Parse the first line of the response. +# line, err = tp.ReadLine() +# if err != nil: +# if err == io.EOF: +# err = io.ErrUnexpectedEOF + +# return nil, err + +# proto, status, ok = strings.Cut(line, " ") +# if not ok: +# return nil, badStringError("malformed HTTP response", line) + +# resp.Proto = proto +# resp.Status = strings.TrimLeft(status, " ") + +# statusCode, _, _ = strings.Cut(resp.Status, " ") +# if len(statusCode) != 3: +# return nil, badStringError("malformed HTTP status code", statusCode) + +# resp.status_code, err = strconv.Atoi(statusCode) +# if err != nil or resp.status_code < 0: +# return nil, badStringError("malformed HTTP status code", statusCode) + +# if resp.proto_major, resp.proto_minor, ok = ParseHTTPVersion(resp.Proto); !ok: +# return nil, badStringError("malformed HTTP version", resp.Proto) + + +# Parse the response headers. +# mimeHeader, err = tp.ReadMIMEHeader() +# if err != nil: +# if err == io.EOF: +# err = io.ErrUnexpectedEOF + +# return nil, err - resp.Header = Header(mimeHeader) +# resp.Header = Header(mimeHeader) - fixPragmaCacheControl(resp.Header) +# fixPragmaCacheControl(resp.Header) - err = readTransfer(resp, r) - if err != nil: - return nil, err +# err = readTransfer(resp, r) +# if err != nil: +# return nil, err - return resp, nil +# return resp, nil diff --git a/gojo/net/http/transfer.mojo b/gojo/net/http/transfer.mojo index 9601b31..73da407 100644 --- a/gojo/net/http/transfer.mojo +++ b/gojo/net/http/transfer.mojo @@ -1,41 +1,42 @@ from collections.optional import Optional -from memory._arc import Arc import ...io -from ...builtins import Result, WrappedError from .header import Header, has_token from .response import Response -fn new_response_transfer_write[RWC: io.ReadWriteCloser](response: Arc[Response[RWC]]) raises -> Arc[TransferWriter[io.Reader, io.Closer]]: - return TransferWriter[io.Reader, io.Closer](TransferWriter[io.Reader, io.Closer]( - True, "", nil, nil, False) - ) +fn new_response_transfer_write[ + RWC: io.ReadWriteCloser +](response: Arc[Response[RWC]]) raises -> Arc[TransferWriter[io.Reader, io.Closer]]: + return TransferWriter[io.Reader, io.Closer](TransferWriter[io.Reader, io.Closer](True, "", nil, nil, False)) struct TransferWriter[R: io.Reader, C: io.Closer](): """Inspects the fields of a user-supplied Request or Response, sanitizes them without changing the user object and provides methods for writing the respective header, body and trailer in wire format.""" + var method: String var body: R var body_closer: C var response_to_head: Bool - var content_length: Int64 #-1 means unknown, 0 means exactly none + var content_length: Int64 # -1 means unknown, 0 means exactly none var close: Bool # var transfer_encoding: List[String] var header: Header # var trailer: Header var is_response: Bool - var body_read_error: Optional[WrappedError] # any non-EOF error from reading Body + var body_read_error: Error # any non-EOF error from reading Body - var flush_headers: Bool # flush headers to network before body + var flush_headers: Bool # flush headers to network before body # var byte_read_ch chan readResult non-nil if probeRequestBody called - fn __init__(inout self, is_response: Bool, method: String, body: R, body_closer: C, response_to_head: Bool): + fn __init__( + inout self, is_response: Bool, method: String, owned body: R, owned body_closer: C, response_to_head: Bool + ): self.is_response = is_response self.method = method - self.body = body ^ - self.body_closer = body_closer ^ + self.body = body^ + self.body_closer = body_closer^ self.response_to_head = response_to_head self.header = Header() # self.trailer = Header() @@ -43,115 +44,116 @@ struct TransferWriter[R: io.Reader, C: io.Closer](): self.content_length = -1 self.close = False self.flush_headers = False - self.body_read_error = None + self.body_read_error = Error() # self.flush_headers = false fn __moveinit__(inout self, owned other: TransferWriter[R, C]): - self.method = other.method ^ - self.body = other.body ^ - self.body_closer = other.body_closer ^ + self.method = other.method^ + self.body = other.body^ + self.body_closer = other.body_closer^ self.response_to_head = other.response_to_head self.content_length = other.content_length self.close = other.close - self.header = other.header ^ + self.header = other.header^ # self.trailer = other.trailer # self.transfer_encoding = other.transfer_encoding self.is_response = other.is_response - self.body_read_error = other.body_read_error ^ + self.body_read_error = other.body_read_error^ self.flush_headers = other.flush_headers # self.byte_read_ch = other.byte_read_ch - fn write_header[W: io.Writer](inout self, inout writer: W) -> Optional[WrappedError]: + fn write_header[W: io.Writer](inout self, inout writer: W) -> Error: + var err = Error() if self.close: var contains_token = False try: contains_token = has_token(self.header.get("Connection"), "close") except e: - return WrappedError(e) + return e if not contains_token: - var result = io.write_string[W](writer, "Connection: close\r\n") - if result.error: - return result.error + _, err = writer.write(String("Connection: close\r\n").as_bytes()) + if err: + return err # Write Content-Length and/or Transfer-Encoding whose values are a # function of the sanitized field triple (Body, ContentLength, # TransferEncoding) # Just using true instead of should_send_content_length field # if self.should_send_content_length(): + @parameter if True: - var result = io.write_string[W](writer, "Content-Length: ") - if result.error: - return result.error - - result = io.write_string[W](writer, self.content_length) - if result.error: - return result.error - - return None - - fn write_body[W: io.Writer](inout self, writer: W) -> Optional[WrappedError]: - var ncopy int64 - closed := false - defer fn(): - if closed or self.BodyCloser == nil: - return - - if closeErr := self.BodyCloser.Close(); closeErr != nil and err == nil: - err = closeErr - - () - - # Write body. We "unwrap" the body first if it was wrapped in a - # nopCloser or readTrackingBody. This is to ensure that we can take advantage of - # OS-level optimizations in the event that the body is an - # *os.File. - if self.Body != nil: - var body = self.unwrapBody() - if chunked(self.TransferEncoding): - if bw, ok := w.(*bufio.Writer); ok and not self.IsResponse: - w = &internal.FlushAfterChunkWriter{Writer: bw - - cw := internal.NewChunkedWriter(w) - _, err = self.doBodyCopy(cw, body) - if err == nil: - err = cw.Close() - - else if self.ContentLength == -1: - dst := w - if self.Method == "CONNECT": - dst = bufioFlushWriter{dst - - ncopy, err = self.doBodyCopy(dst, body) - else: - ncopy, err = self.doBodyCopy(w, io.LimitReader(body, self.ContentLength)) - if err != nil: - return err - - var nextra int64 - nextra, err = self.doBodyCopy(io.Discard, body) - ncopy += nextra - - if err != nil: + _, err = writer.write(String("Content-Length: ").as_bytes()) + if err: return err - if self.BodyCloser != nil: - closed = true - if err := self.BodyCloser.Close(); err != nil: + _, err = writer.write(str(self.content_length).as_bytes()) + if err: return err - if not self.ResponseToHEAD and self.ContentLength != -1 and self.ContentLength != ncopy: - return fmt.Errorf("http: ContentLength=%d with Body length %d", - self.ContentLength, ncopy) - - if chunked(self.TransferEncoding): - # Write Trailer header - if self.Trailer != nil: - if err := self.Trailer.Write(w); err != nil: - return err - - - # Last chunk, empty trailer - _, err = io.WriteString(w, "\r\n") - return err + + # fn write_body[W: io.Writer](inout self, writer: W) -> Error: + # var ncopy: Int64 + # var closed = False + # defer fn(): + # if closed or self.BodyCloser == nil: + # return + + # if closeErr := self.BodyCloser.Close(); closeErr != nil and err == nil: + # err = closeErr + + # () + + # # Write body. We "unwrap" the body first if it was wrapped in a + # # nopCloser or readTrackingBody. This is to ensure that we can take advantage of + # # OS-level optimizations in the event that the body is an + # # *os.File. + # if self.Body != nil: + # var body = self.unwrapBody() + # if chunked(self.TransferEncoding): + # if bw, ok := w.(*bufio.Writer); ok and not self.IsResponse: + # w = &internal.FlushAfterChunkWriter{Writer: bw + + # cw := internal.NewChunkedWriter(w) + # _, err = self.doBodyCopy(cw, body) + # if err == nil: + # err = cw.Close() + + # else if self.ContentLength == -1: + # dst := w + # if self.Method == "CONNECT": + # dst = bufioFlushWriter{dst + + # ncopy, err = self.doBodyCopy(dst, body) + # else: + # ncopy, err = self.doBodyCopy(w, io.LimitReader(body, self.ContentLength)) + # if err != nil: + # return err + + # var nextra int64 + # nextra, err = self.doBodyCopy(io.Discard, body) + # ncopy += nextra + + # if err != nil: + # return err + + # if self.BodyCloser != nil: + # closed = true + # if err := self.BodyCloser.Close(); err != nil: + # return err + + # if not self.ResponseToHEAD and self.ContentLength != -1 and self.ContentLength != ncopy: + # return fmt.Errorf("http: ContentLength=%d with Body length %d", + # self.ContentLength, ncopy) + + # if chunked(self.TransferEncoding): + # # Write Trailer header + # if self.Trailer != nil: + # if err := self.Trailer.Write(w); err != nil: + # return err + + # # Last chunk, empty trailer + # _, err = io.WriteString(w, "\r\n") + + # return err diff --git a/gojo/syscall/__init__.mojo b/gojo/syscall/__init__.mojo index 219df7d..0171fd3 100644 --- a/gojo/syscall/__init__.mojo +++ b/gojo/syscall/__init__.mojo @@ -22,7 +22,6 @@ from .net import ( getaddrinfo, getaddrinfo_unix, gai_strerror, - c_charptr_to_string, shutdown, inet_ntoa, bind,