4141# # import asynchttpserver, asyncdispatch
4242# # import strutils, strformat
4343# #
44+ # # const stream = true # for test purposes switch from true to false
45+ # #
4446# # proc htmlpage(contentLength, bodyLength: int): string =
4547# # return &"""
4648# # <!Doctype html>
4749# # <html lang="en">
48- # # <head>
49- # # <meta charset="utf-8"/>
50- # # </head>
50+ # # <head><meta charset="utf-8"/></head>
5151# # <body>
5252# # <form action="/" method="post" enctype="multipart/form-data">
5353# # File: <input type="file" name="testfile" accept="text/*"><br />
6565# # bodyLength = 0
6666# # if req.reqMethod == HttpPost:
6767# # contentLength = req.headers["Content-length"].parseInt
68- # # if contentLength < 8*1024: # the default chunkSize
69- # # # read the request body at once
70- # # let body = await req.bodyStream.readAll();
71- # # bodyLength = body.len
68+ # # if stream:
69+ # # # Read 8*1024 bytes at a time
70+ # # # optional chunkSize parameter. The default is 8*1024
71+ # # for length, data in req.bodyStream(8*1024):
72+ # # let content = await data
73+ # # if length == content.len:
74+ # # bodyLength += content.len
75+ # # else:
76+ # # # Handle exception
77+ # # await req.respond(Http400,
78+ # # "Bad Request. Data read has a different length than the expected.")
79+ # # return
7280# # else:
73- # # # read 8*1024 bytes at a time
74- # # while (let data = await req.bodyStream.read(); data[0]):
75- # # bodyLength += data[1].len
81+ # # bodyLength += req.body.len
7682# # await req.respond(Http200, htmlpage(contentLength, bodyLength))
7783# #
78- # # let server = newAsyncHttpServer(maxBody = 10485760) # 10 MB
84+ # # let server = newAsyncHttpServer(maxBody = 10485760, stream = stream ) # 10 MB
7985# # waitFor server.serve(Port(8080), cb)
8086
8187import tables, asyncnet, asyncdispatch, parseutils, uri, strutils
9399 maxLine = 8 * 1024
94100
95101when (NimMajor , NimMinor ) >= (1 , 1 ):
96- const
97- chunkSize = 8 * 1024 # # This seems perfectly reasonable for default chunkSize.
98-
99102 type
100103 Request * = object
101104 client* : AsyncSocket # TODO : Separate this into a Response object?
@@ -104,8 +107,16 @@ when (NimMajor, NimMinor) >= (1, 1):
104107 protocol* : tuple [orig: string , major, minor: int ]
105108 url* : Uri
106109 hostname* : string # # The hostname of the client that made the request.
107- body* : string # For future removal
108- bodyStream* : FutureStream [string ]
110+ body* : string
111+ contentLength* : int
112+
113+ type
114+ AsyncHttpServer * = ref object
115+ socket: AsyncSocket
116+ reuseAddr: bool
117+ reusePort: bool
118+ maxBody: int # # The maximum content-length that will be read for the body.
119+ stream: bool # # By default (stream = false), the body of the request is read immediately
109120else :
110121 type
111122 Request * = object
@@ -117,20 +128,30 @@ else:
117128 hostname* : string # # The hostname of the client that made the request.
118129 body* : string
119130
120- type
121- AsyncHttpServer * = ref object
122- socket: AsyncSocket
123- reuseAddr: bool
124- reusePort: bool
125- maxBody: int # # The maximum content-length that will be read for the body.
131+ type
132+ AsyncHttpServer * = ref object
133+ socket: AsyncSocket
134+ reuseAddr: bool
135+ reusePort: bool
136+ maxBody: int # # The maximum content-length that will be read for the body.
126137
127- proc newAsyncHttpServer * (reuseAddr = true , reusePort = false ,
138+ when (NimMajor , NimMinor ) >= (1 , 1 ):
139+ proc newAsyncHttpServer * (reuseAddr = true , reusePort = false ,
140+ maxBody = 8388608 , stream = false ): AsyncHttpServer =
141+ # # Creates a new ``AsyncHttpServer`` instance.
142+ new result
143+ result .reuseAddr = reuseAddr
144+ result .reusePort = reusePort
145+ result .maxBody = maxBody
146+ result .stream = stream
147+ else :
148+ proc newAsyncHttpServer * (reuseAddr = true , reusePort = false ,
128149 maxBody = 8388608 ): AsyncHttpServer =
129- # # Creates a new ``AsyncHttpServer`` instance.
130- new result
131- result .reuseAddr = reuseAddr
132- result .reusePort = reusePort
133- result .maxBody = maxBody
150+ # # Creates a new ``AsyncHttpServer`` instance.
151+ new result
152+ result .reuseAddr = reuseAddr
153+ result .reusePort = reusePort
154+ result .maxBody = maxBody
134155
135156proc addHeaders (msg: var string , headers: HttpHeaders ) =
136157 for k, v in headers:
@@ -193,13 +214,30 @@ proc parseProtocol(protocol: string): tuple[orig: string, major, minor: int] =
193214proc sendStatus (client: AsyncSocket , status: string ): Future [void ] =
194215 client.send (" HTTP/1.1 " & status & " \c\L\c\L " )
195216
217+ when (NimMajor , NimMinor ) >= (1 , 1 ):
218+ iterator bodyStream * (
219+ request: Request ,
220+ chunkSize: int = 8 * 1024 ): (int , Future [string ]) =
221+ # # The chunkSize parameter is optional and default value is 8*1024 bytes.
222+ # #
223+ # # This iterator return a tuple with the length of the data that was read
224+ # # and a future.
225+ var remainder = request.contentLength
226+ while remainder > 0 :
227+ let readSize = min (remainder, chunkSize)
228+ let data = request.client.recv (readSize)
229+ if data.failed:
230+ raise newException (ValueError , " Error reading POST data from client." )
231+ yield (readSize, data)
232+ remainder -= readSize
233+
196234proc processRequest (
197235 server: AsyncHttpServer ,
198236 req: FutureVar [Request ],
199237 client: AsyncSocket ,
200238 address: string ,
201239 lineFut: FutureVar [string ],
202- callback: proc (request: Request ): Future [void ] {.closure , gcsafe .},
240+ callback: proc (request: Request ): Future [void ] {.closure , gcsafe .}
203241): Future [bool ] {.async .} =
204242
205243 # Alias `request` to `req.mget()` so we don't have to write `mget` everywhere.
@@ -213,13 +251,9 @@ proc processRequest(
213251 request.hostname.shallowCopy (address)
214252 assert client != nil
215253 request.client = client
254+ request.body = " "
216255 when (NimMajor , NimMinor ) >= (1 , 1 ):
217- request.bodyStream = newFutureStream [string ]()
218- # To uncomment in the future after compatibility issues
219- # with third parties are solved
220- # else:
221- # request.body = ""
222- request.body = " " # Temporary fix for future removal
256+ request.contentLength = 0
223257
224258 # We should skip at least one empty line before the request
225259 # https://tools.ietf.org/html/rfc7230#section-3.5
@@ -316,16 +350,12 @@ proc processRequest(
316350 return false
317351
318352 when (NimMajor , NimMinor ) >= (1 , 1 ):
319- var remainder = contentLength
320- while remainder > 0 :
321- let readSize = min (remainder, chunkSize)
322- let data = await client.recv (read_size)
323- if data.len != read_size:
353+ request.contentLength = contentLength
354+ if not server.stream:
355+ request.body = await client.recv (contentLength)
356+ if request.body.len != contentLength:
324357 await request.respond (Http400 , " Bad Request. Content-Length does not match actual." )
325358 return true
326- await request.bodyStream.write (data)
327- remainder -= data.len
328- request.bodyStream.complete ()
329359 else :
330360 request.body = await client.recv (contentLength)
331361 if request.body.len != contentLength:
0 commit comments