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
|
-----------------------------------------------------------------------------
-- |
-- Module : Network.HTTP
-- Copyright : See LICENSE file
-- License : BSD
--
-- Maintainer : Ganesh Sittampalam <ganesh@earth.li>
-- 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 )
import Network.URI ( parseURI )
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 <- openStream (host auth) (fromMaybe 80 (port auth))
let norm_r = normalizeRequest defaultNormalizeRequestOptions{normDoClose=True} r
simpleHTTP_ c norm_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)
-- | @getResponseCode 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).
|