-
Notifications
You must be signed in to change notification settings - Fork 59
/
Copy pathHTTP.hs
267 lines (243 loc) · 11.5 KB
/
HTTP.hs
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
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
-----------------------------------------------------------------------------
-- |
-- Module : Network.HTTP
-- Copyright : See LICENSE file
-- License : BSD
--
-- Maintainer : Ganesh Sittampalam <[email protected]>
-- Stability : experimental
-- Portability : non-portable (not tested)
--
-- The 'Network.HTTP' module provides a simple interface for sending and
-- receiving content over HTTP in Haskell. Here's how to fetch a document from
-- a URL and return it as a String:
--
-- >
-- > simpleHTTP (getRequest "http://www.haskell.org/") >>= fmap (take 100) . getResponseBody
-- > -- fetch document and return it (as a 'String'.)
--
-- Other functions let you control the submission and transfer of HTTP
-- 'Request's and 'Response's more carefully, letting you integrate the use
-- of 'Network.HTTP' functionality into your application.
--
-- The module also exports the main types of the package, 'Request' and 'Response',
-- along with 'Header' and functions for working with these.
--
-- The actual functionality is implemented by modules in the @Network.HTTP.*@
-- namespace, letting you either use the default implementation here
-- by importing @Network.HTTP@ or, for more specific uses, selectively
-- import the modules in @Network.HTTP.*@. To wit, more than one kind of
-- representation of the bulk data that flows across a HTTP connection is
-- supported. (see "Network.HTTP.HandleStream".)
--
-- /NOTE:/ The 'Request' send actions will normalize the @Request@ prior to transmission.
-- Normalization such as having the request path be in the expected form and, possibly,
-- introduce a default @Host:@ header if one isn't already present.
-- Normalization also takes the @"user:pass\@"@ portion out of the the URI,
-- if it was supplied, and converts it into @Authorization: Basic$ header.
-- If you do not
-- want the requests tampered with, but sent as-is, please import and use the
-- the "Network.HTTP.HandleStream" or "Network.HTTP.Stream" modules instead. They
-- export the same functions, but leaves construction and any normalization of
-- @Request@s to the user.
--
-- /NOTE:/ This package only supports HTTP; it does not support HTTPS.
-- Attempts to use HTTPS result in an error.
-----------------------------------------------------------------------------
module Network.HTTP
( module Network.HTTP.Base
, module Network.HTTP.Headers
{- the functionality that the implementation modules,
Network.HTTP.HandleStream and Network.HTTP.Stream,
exposes:
-}
, simpleHTTP -- :: Request -> IO (Result Response)
, simpleHTTP_ -- :: Stream s => s -> Request -> IO (Result Response)
, sendHTTP -- :: Stream s => s -> Request -> IO (Result Response)
, sendHTTP_notify -- :: Stream s => s -> Request -> IO () -> IO (Result Response)
, receiveHTTP -- :: Stream s => s -> IO (Result Request)
, respondHTTP -- :: Stream s => s -> Response -> IO ()
, module Network.TCP
, getRequest -- :: String -> Request_String
, headRequest -- :: String -> Request_String
, postRequest -- :: String -> Request_String
, postRequestWithBody -- :: String -> String -> String -> Request_String
, getResponseBody -- :: Result (Request ty) -> IO ty
, getResponseCode -- :: Result (Request ty) -> IO ResponseCode
) where
-----------------------------------------------------------------
------------------ Imports --------------------------------------
-----------------------------------------------------------------
import Network.HTTP.Headers
import Network.HTTP.Base
import qualified Network.HTTP.HandleStream as S
-- old implementation: import Network.HTTP.Stream
import Network.TCP
import Network.Stream ( Result, failMisc )
import Network.URI ( parseURI )
import Control.Exception ( IOException, try )
import Data.Maybe ( fromMaybe )
{-
Note: if you switch over/back to using Network.HTTP.Stream here, you'll
have to wrap the results from 'openStream' as Connections via 'hstreamToConnection'
prior to delegating to the Network.HTTP.Stream functions.
-}
-- | @simpleHTTP req@ transmits the 'Request' @req@ by opening a /direct/, non-persistent
-- connection to the HTTP server that @req@ is destined for, followed by transmitting
-- it and gathering up the response as a 'Result'. Prior to sending the request,
-- it is normalized (via 'normalizeRequest'). If you have to mediate the request
-- via an HTTP proxy, you will have to normalize the request yourself. Or switch to
-- using 'Network.Browser' instead.
--
-- Examples:
--
-- > simpleHTTP (getRequest "http://hackage.haskell.org/")
-- > simpleHTTP (getRequest "http://hackage.haskell.org:8012/")
simpleHTTP :: (HStream ty) => Request ty -> IO (Result (Response ty))
simpleHTTP r = do
auth <- getAuth r
failHTTPS (rqURI r)
c <- try (openStream (host auth) (fromMaybe 80 (port auth)))
case c of
Left err -> return . failMisc $ show (err :: IOException)
Right c' -> simpleHTTP_ c' r
-- | Identical to 'simpleHTTP', but acting on an already opened stream.
simpleHTTP_ :: HStream ty => HandleStream ty -> Request ty -> IO (Result (Response ty))
simpleHTTP_ s r = do
let norm_r = normalizeRequest defaultNormalizeRequestOptions{normDoClose=True} r
S.sendHTTP s norm_r
-- | @sendHTTP hStream httpRequest@ transmits @httpRequest@ (after normalization) over
-- @hStream@, but does not alter the status of the connection, nor request it to be
-- closed upon receiving the response.
sendHTTP :: HStream ty => HandleStream ty -> Request ty -> IO (Result (Response ty))
sendHTTP conn rq = do
let norm_r = normalizeRequest defaultNormalizeRequestOptions rq
S.sendHTTP conn norm_r
-- | @sendHTTP_notify hStream httpRequest action@ behaves like 'sendHTTP', but
-- lets you supply an IO @action@ to execute once the request has been successfully
-- transmitted over the connection. Useful when you want to set up tracing of
-- request transmission and its performance.
sendHTTP_notify :: HStream ty
=> HandleStream ty
-> Request ty
-> IO ()
-> IO (Result (Response ty))
sendHTTP_notify conn rq onSendComplete = do
let norm_r = normalizeRequest defaultNormalizeRequestOptions rq
S.sendHTTP_notify conn norm_r onSendComplete
-- | @receiveHTTP hStream@ reads a 'Request' from the 'HandleStream' @hStream@
receiveHTTP :: HStream ty => HandleStream ty -> IO (Result (Request ty))
receiveHTTP conn = S.receiveHTTP conn
-- | @respondHTTP hStream httpResponse@ transmits an HTTP 'Response' over
-- the 'HandleStream' @hStream@. It could be used to implement simple web
-- server interactions, performing the dual role to 'sendHTTP'.
respondHTTP :: HStream ty => HandleStream ty -> Response ty -> IO ()
respondHTTP conn rsp = S.respondHTTP conn rsp
-- | A convenience constructor for a GET 'Request'.
--
-- If the URL isn\'t syntactically valid, the function raises an error.
getRequest
:: String -- ^URL to fetch
-> Request_String -- ^The constructed request
getRequest urlString =
case parseURI urlString of
Nothing -> error ("getRequest: Not a valid URL - " ++ urlString)
Just u -> mkRequest GET u
-- | A convenience constructor for a HEAD 'Request'.
--
-- If the URL isn\'t syntactically valid, the function raises an error.
headRequest
:: String -- ^URL to fetch
-> Request_String -- ^The constructed request
headRequest urlString =
case parseURI urlString of
Nothing -> error ("headRequest: Not a valid URL - " ++ urlString)
Just u -> mkRequest HEAD u
-- | A convenience constructor for a POST 'Request'.
--
-- If the URL isn\'t syntactically valid, the function raises an error.
postRequest
:: String -- ^URL to POST to
-> Request_String -- ^The constructed request
postRequest urlString =
case parseURI urlString of
Nothing -> error ("postRequest: Not a valid URL - " ++ urlString)
Just u -> mkRequest POST u
-- | A convenience constructor for a POST 'Request'.
--
-- It constructs a request and sets the body as well as
-- the Content-Type and Content-Length headers. The contents of the body
-- are forced to calculate the value for the Content-Length header.
--
-- If the URL isn\'t syntactically valid, the function raises an error.
postRequestWithBody
:: String -- ^URL to POST to
-> String -- ^Content-Type of body
-> String -- ^The body of the request
-> Request_String -- ^The constructed request
postRequestWithBody urlString typ body =
case parseURI urlString of
Nothing -> error ("postRequestWithBody: Not a valid URL - " ++ urlString)
Just u -> setRequestBody (mkRequest POST u) (typ, body)
-- | @getResponseBody response@ takes the response of a HTTP requesting action and
-- tries to extricate the body of the 'Response' @response@. If the request action
-- returned an error, an IO exception is raised.
getResponseBody :: Result (Response ty) -> IO ty
getResponseBody (Left err) = fail (show err)
getResponseBody (Right r) = return (rspBody r)
-- | @getResponseBody response@ takes the response of a HTTP requesting action and
-- tries to extricate the status code of the 'Response' @response@. If the request action
-- returned an error, an IO exception is raised.
getResponseCode :: Result (Response ty) -> IO ResponseCode
getResponseCode (Left err) = fail (show err)
getResponseCode (Right r) = return (rspCode r)
--
-- * TODO
-- - request pipelining
-- - https upgrade (includes full TLS, i.e. SSL, implementation)
-- - use of Stream classes will pay off
-- - consider C implementation of encryption\/decryption
-- - comm timeouts
-- - MIME & entity stuff (happening in separate module)
-- - support \"*\" uri-request-string for OPTIONS request method
--
--
-- * Header notes:
--
-- [@Host@]
-- Required by HTTP\/1.1, if not supplied as part
-- of a request a default Host value is extracted
-- from the request-uri.
--
-- [@Connection@]
-- If this header is present in any request or
-- response, and it's value is "close", then
-- the current request\/response is the last
-- to be allowed on that connection.
--
-- [@Expect@]
-- Should a request contain a body, an Expect
-- header will be added to the request. The added
-- header has the value \"100-continue\". After
-- a 417 \"Expectation Failed\" response the request
-- is attempted again without this added Expect
-- header.
--
-- [@TransferEncoding,ContentLength,...@]
-- if request is inconsistent with any of these
-- header values then you may not receive any response
-- or will generate an error response (probably 4xx).
--
--
-- * Response code notes
-- Some response codes induce special behaviour:
--
-- [@1xx@] \"100 Continue\" will cause any unsent request body to be sent.
-- \"101 Upgrade\" will be returned.
-- Other 1xx responses are ignored.
--
-- [@417@] The reason for this code is \"Expectation failed\", indicating
-- that the server did not like the Expect \"100-continue\" header
-- added to a request. Receipt of 417 will induce another
-- request attempt (without Expect header), unless no Expect header
-- had been added (in which case 417 response is returned).