@@ -149,6 +149,8 @@ module Network.HTTP.Req
149149 , ReqBodyLbs (..)
150150 , ReqBodyUrlEnc (..)
151151 , FormUrlEncodedParam
152+ , ReqBodyMultipart
153+ , reqBodyMultipart
152154 , HttpBody (..)
153155 , ProvidesBody
154156 , HttpBodyAllowed
@@ -234,6 +236,7 @@ import qualified Data.Text.Encoding as T
234236import qualified Network.Connection as NC
235237import qualified Network.HTTP.Client as L
236238import qualified Network.HTTP.Client.Internal as LI
239+import qualified Network.HTTP.Client.MultipartFormData as LM
237240import qualified Network.HTTP.Client.TLS as L
238241import qualified Network.HTTP.Types as Y
239242
@@ -777,7 +780,7 @@ newtype ReqBodyJson a = ReqBodyJson a
777780
778781instance ToJSON a => HttpBody (ReqBodyJson a) where
779782 getRequestBody (ReqBodyJson a) = L.RequestBodyLBS (A.encode a)
780− getRequestContentType Proxy = pure "application/json; charset=utf-8"
783+ getRequestContentType _ = pure "application/json; charset=utf-8"
781784
782785-- | This body option streams request body from a file. It is expected that
783786-- the file size does not change during the streaming.
@@ -823,7 +826,7 @@ newtype ReqBodyUrlEnc = ReqBodyUrlEnc FormUrlEncodedParam
823826instance HttpBody ReqBodyUrlEnc where
824827 getRequestBody (ReqBodyUrlEnc (FormUrlEncodedParam params)) =
825828 (L.RequestBodyLBS . BB.toLazyByteString) (Y.renderQueryText False params)
826− getRequestContentType Proxy = pure "application/x-www-form-urlencoded"
829+ getRequestContentType _ = pure "application/x-www-form-urlencoded"
827830
828831-- | An opaque monoidal value that allows to collect URL-encoded parameters
829832-- to be wrapped in 'ReqBodyUrlEnc'.
@@ -835,6 +838,27 @@ instance QueryParam FormUrlEncodedParam where
835838 queryParam name mvalue =
836839 FormUrlEncodedParam [(name, toQueryParam <$> mvalue)]
837840
841+-- | Multipart form data. Please consult the
842+-- "Network.HTTP.Client.MultipartFormData" module for how to construct
843+-- parts, then use 'reqBodyMultipart' to create actual request body from the
844+-- parts. 'reqBodyMultipart' is the only way to get a value of type
845+-- 'ReqBodyMultipart', as its constructor is not exported on purpose.
846+
847+data ReqBodyMultipart = ReqBodyMultipart ByteString LI.RequestBody
848+
849+instance HttpBody ReqBodyMultipart where
850+ getRequestBody (ReqBodyMultipart _ body) = body
851+ getRequestContentType (ReqBodyMultipart boundary _) =
852+ pure ("multipart/form-data; boundary=" <> boundary)
853+
854+-- | Create 'ReqBodyMultipart' request body from a collection of 'LM.Part's.
855+
856+reqBodyMultipart :: MonadIO m => [LM.Part] -> m ReqBodyMultipart
857+reqBodyMultipart parts = liftIO $ do
858+ boundary <- LM.webkitBoundary
859+ body <- LM.renderParts boundary parts
860+ return (ReqBodyMultipart boundary body)
861+
838862-- | A type class for things that can be interpreted as HTTP
839863-- 'L.RequestBody'.
840864
@@ -850,8 +874,8 @@ class HttpBody body where
850874 -- header that should be used with particular body option. By default it
851875 -- returns 'Nothing' and so @Content-Type@ is not set.
852876
853− getRequestContentType :: Proxy body -> Maybe ByteString
854− getRequestContentType Proxy = Nothing
877+ getRequestContentType :: body -> Maybe ByteString
878+ getRequestContentType = const Nothing
855879
856880-- | The type function recognizes 'NoReqBody' as having 'NoBody', while any
857881-- other body option 'CanHaveBody'. This forces user to use 'NoReqBody' with
@@ -883,7 +907,7 @@ instance HttpBody body => RequestComponent (Womb "body" body) where
883907 x { L.requestBody = getRequestBody body
884908 , L.requestHeaders =
885909 let old = L.requestHeaders x in
886− case getRequestContentType (Proxy :: Proxy body) of
910+ case getRequestContentType body of
887911 Nothing -> old
888912 Just contentType ->
889913 (Y.hContentType, contentType) : old }