@@ -83,10 +83,14 @@ class Request:
8383 Attributes:
8484 path: Request path, including optional query.
8585 headers: Request headers.
86+ method: Request method; WebSocket handshake requests use GET.
8687 """
8788
8889 path : str
8990 headers : Headers
91+ # method comes before path in the HTTP request, but it has a default,
92+ # so it must be declared after path and headers which don't have one.
93+ method : str = "GET"
9094 # body isn't useful is the context of this library.
9195
9296 _exception : Exception | None = None
@@ -109,14 +113,12 @@ def parse(
109113
110114 This is a generator-based coroutine.
111115
112- The request path isn't URL-decoded or validated in any way.
116+ The request method, path, and headers are expected to contain only ASCII
117+ characters. The request path isn't URL-decoded or validated in any way.
113118
114- The request path and headers are expected to contain only ASCII
115- characters. Other characters are represented with surrogate escapes.
116-
117- :meth:`parse` doesn't attempt to read the request body because
118- WebSocket handshake requests don't have one. If the request contains a
119- body, it may be read from the data stream after :meth:`parse` returns.
119+ :meth:`parse` doesn't read the request body because WebSocket handshake
120+ requests don't have one. If the request contains a body, it may be read
121+ from the data stream after :meth:`parse` returns.
120122
121123 Args:
122124 read_line: Generator-based coroutine that reads a LF-terminated
@@ -130,25 +132,24 @@ def parse(
130132 """
131133 # https://datatracker.ietf.org/doc/html/rfc7230#section-3.1.1
132134
133- # Parsing is simple because fixed values are expected for method and
134- # version and because path isn't checked. Since WebSocket software tends
135- # to implement HTTP/1.1 strictly, there's little need for lenient parsing.
135+ # Parsing is simple because a fixed value is expected for the version
136+ # and because path isn't checked. Since WebSocket libraries generally
137+ # implement HTTP/1.1 strictly, there's little need for lenient parsing.
136138
137139 try :
138140 request_line = yield from parse_line (read_line )
139141 except EOFError as exc :
140142 raise EOFError ("connection closed while reading HTTP request line" ) from exc
141143
142144 try :
143- method , raw_path , protocol = request_line .split (b" " , 2 )
145+ raw_method , raw_path , protocol = request_line .split (b" " , 2 )
144146 except ValueError : # not enough values to unpack (expected 3, got 1-2)
145147 raise ValueError (f"invalid HTTP request line: { d (request_line )} " ) from None
146148 if protocol != b"HTTP/1.1" :
147149 raise ValueError (
148150 f"unsupported protocol; expected HTTP/1.1: { d (request_line )} "
149151 )
150- if method != b"GET" :
151- raise ValueError (f"unsupported HTTP method; expected GET; got { d (method )} " )
152+ method = raw_method .decode ("ascii" )
152153
153154 # RFC 9110 defers the definition of URIs to RFC 3986, which allows only
154155 # a subset of ASCII. Non-ASCII IRIs must be UTF-8 then percent-encoded.
@@ -167,7 +168,7 @@ def parse(
167168 if int (headers ["Content-Length" ]) != 0 :
168169 raise ValueError ("unsupported request body" )
169170
170- return cls (path , headers )
171+ return cls (path , headers , method )
171172
172173 def serialize (self ) -> bytes :
173174 """
@@ -176,7 +177,7 @@ def serialize(self) -> bytes:
176177 """
177178 # Since the request line and headers only contain ASCII characters,
178179 # we can keep this simple.
179- request = f"GET { self .path } HTTP/1.1\r \n " .encode ()
180+ request = f"{ self . method } { self .path } HTTP/1.1\r \n " .encode ()
180181 request += self .headers .serialize ()
181182 return request
182183
0 commit comments