refactor(users/Profpatsch/whatcd-resolver): start moving http stuff
There’s a bunch of duplication in how http client things are done, let’s move that all to a single module. Change-Id: Ic08c9bce49d562e4fa640a5bdfc15973a28a7bcb Reviewed-on: https://cl.tvl.fyi/c/depot/+/12135 Reviewed-by: Profpatsch <mail@profpatsch.de> Tested-by: BuildkiteCI
This commit is contained in:
parent
59056cf705
commit
8908fd18ca
3 changed files with 70 additions and 62 deletions
|
@ -4,27 +4,35 @@ module Http
|
|||
( doRequestJson,
|
||||
RequestOptions (..),
|
||||
mkRequestOptions,
|
||||
setRequestMethod,
|
||||
setRequestBodyLBS,
|
||||
setRequestHeader,
|
||||
getResponseStatus,
|
||||
getResponseHeader,
|
||||
getResponseBody,
|
||||
httpJson,
|
||||
Http.setRequestMethod,
|
||||
Http.setRequestBodyLBS,
|
||||
Http.setRequestHeader,
|
||||
Http.getResponseStatus,
|
||||
Http.getResponseHeader,
|
||||
Http.getResponseBody,
|
||||
)
|
||||
where
|
||||
|
||||
import AppT
|
||||
import Data.Aeson.BetterErrors qualified as Json
|
||||
import Data.CaseInsensitive (CI (original))
|
||||
import Data.Char qualified as Char
|
||||
import Data.Error.Tree
|
||||
import Data.List qualified as List
|
||||
import Data.Text qualified as Text
|
||||
import Data.Text.Punycode qualified as Punycode
|
||||
import Json qualified
|
||||
import Json.Enc qualified as Enc
|
||||
import Label
|
||||
import MyPrelude
|
||||
import Network.HTTP.Client
|
||||
import Network.HTTP.Simple
|
||||
import Network.HTTP.Client qualified as Http
|
||||
import Network.HTTP.Simple qualified as Http
|
||||
import Network.HTTP.Types.Status (Status (..))
|
||||
import Network.Wai.Parse qualified as Wai
|
||||
import Optional
|
||||
import Pretty
|
||||
import Prelude hiding (span)
|
||||
|
||||
data RequestOptions = RequestOptions
|
||||
|
@ -32,7 +40,7 @@ data RequestOptions = RequestOptions
|
|||
host :: Text,
|
||||
port :: Optional Int,
|
||||
path :: Optional [Text],
|
||||
headers :: Optional [Header],
|
||||
headers :: Optional [Http.Header],
|
||||
usePlainHttp :: Optional Bool
|
||||
}
|
||||
|
||||
|
@ -47,6 +55,47 @@ mkRequestOptions opts =
|
|||
usePlainHttp = defaults
|
||||
}
|
||||
|
||||
httpJson ::
|
||||
( MonadThrow m,
|
||||
MonadOtel m
|
||||
) =>
|
||||
(Optional (Label "contentType" ByteString)) ->
|
||||
Json.Parse ErrorTree b ->
|
||||
Http.Request ->
|
||||
m b
|
||||
httpJson opts parser req = inSpan' "HTTP Request (JSON)" $ \span -> do
|
||||
let opts' = opts.withDefault (label @"contentType" "application/json")
|
||||
Http.httpBS req
|
||||
>>= assertM
|
||||
span
|
||||
( \resp -> do
|
||||
let statusCode = resp & Http.responseStatus & (.statusCode)
|
||||
contentType =
|
||||
resp
|
||||
& Http.responseHeaders
|
||||
& List.lookup "content-type"
|
||||
<&> Wai.parseContentType
|
||||
<&> (\(ct, _mimeAttributes) -> ct)
|
||||
if
|
||||
| statusCode == 200,
|
||||
Just ct <- contentType,
|
||||
ct == opts'.contentType ->
|
||||
Right $ (resp & Http.responseBody)
|
||||
| statusCode == 200,
|
||||
Just otherType <- contentType ->
|
||||
Left [fmt|Server returned a non-json body, with content-type "{otherType}"|]
|
||||
| statusCode == 200,
|
||||
Nothing <- contentType ->
|
||||
Left [fmt|Server returned a body with unspecified content type|]
|
||||
| code <- statusCode -> Left [fmt|Server returned an non-200 error code, code {code}: {resp & showPretty}|]
|
||||
)
|
||||
>>= assertM
|
||||
span
|
||||
( \body ->
|
||||
Json.parseStrict parser body
|
||||
& first (Json.parseErrorTree "could not parse redacted response")
|
||||
)
|
||||
|
||||
doRequestJson ::
|
||||
(MonadOtel m) =>
|
||||
RequestOptions ->
|
||||
|
@ -56,16 +105,16 @@ doRequestJson opts val = inSpan' "HTTP Request (JSON)" $ \span -> do
|
|||
addAttribute span "request.xh" (requestToXhCommandLine opts val)
|
||||
resp <-
|
||||
defaultRequest {secure = not (opts & optsUsePlainHttp)}
|
||||
& setRequestHost (opts & optsHost)
|
||||
& setRequestPort (opts & optsPort)
|
||||
& Http.setRequestHost (opts & optsHost)
|
||||
& Http.setRequestPort (opts & optsPort)
|
||||
-- TODO: is this automatically escaped by the library?
|
||||
& setRequestPath (opts & optsPath)
|
||||
& setRequestHeaders (opts & optsHeaders)
|
||||
& setRequestMethod opts.method
|
||||
& setRequestBodyLBS (Enc.encToBytesUtf8Lazy val)
|
||||
& httpBS
|
||||
let code = resp & getResponseStatus & (.statusCode)
|
||||
let msg = resp & getResponseStatus & (.statusMessage) & bytesToTextUtf8Lenient
|
||||
& Http.setRequestPath (opts & optsPath)
|
||||
& Http.setRequestHeaders (opts & optsHeaders)
|
||||
& Http.setRequestMethod opts.method
|
||||
& Http.setRequestBodyLBS (Enc.encToBytesUtf8Lazy val)
|
||||
& Http.httpBS
|
||||
let code = resp & Http.getResponseStatus & (.statusCode)
|
||||
let msg = resp & Http.getResponseStatus & (.statusMessage) & bytesToTextUtf8Lenient
|
||||
addAttribute
|
||||
span
|
||||
"request.response.status"
|
||||
|
@ -87,7 +136,7 @@ optsPort opts = opts.port.withDefault (if opts & optsUsePlainHttp then 80 else 4
|
|||
optsPath :: RequestOptions -> ByteString
|
||||
optsPath opts = opts.path.withDefault [] & Text.intercalate "/" & ("/" <>) & textToBytesUtf8
|
||||
|
||||
optsHeaders :: RequestOptions -> [Header]
|
||||
optsHeaders :: RequestOptions -> [Http.Header]
|
||||
optsHeaders opts = opts.headers.withDefault []
|
||||
|
||||
-- | Create a string that can be pasted on the command line to invoke the same HTTP request via the `xh` tool (curl but nicer syntax)
|
||||
|
|
|
@ -11,16 +11,15 @@ import Data.Map.Strict qualified as Map
|
|||
import Data.Set (Set)
|
||||
import Data.Set qualified as Set
|
||||
import Html qualified
|
||||
import Http
|
||||
import IHP.HSX.QQ (hsx)
|
||||
import Json qualified
|
||||
import Label
|
||||
import MyPrelude
|
||||
import Network.HTTP.Client.Conduit qualified as Http
|
||||
import Network.HTTP.Simple qualified as Http
|
||||
import Network.HTTP.Types.URI qualified as Url
|
||||
import Network.URI (URI)
|
||||
import Optional
|
||||
import Redacted
|
||||
import Text.Blaze.Html (Html)
|
||||
import Prelude hiding (span)
|
||||
|
||||
|
|
|
@ -15,6 +15,7 @@ import Database.PostgreSQL.Simple (Binary (Binary), Only (..))
|
|||
import Database.PostgreSQL.Simple.SqlQQ (sql)
|
||||
import Database.PostgreSQL.Simple.Types (PGArray (PGArray))
|
||||
import FieldParser qualified as Field
|
||||
import Http qualified
|
||||
import Json qualified
|
||||
import Label
|
||||
import MyPrelude
|
||||
|
@ -522,47 +523,6 @@ httpTorrent span req =
|
|||
| code <- statusCode -> Left [fmt|Redacted returned an non-200 error code, code {code}: {resp & showPretty}|]
|
||||
)
|
||||
|
||||
httpJson ::
|
||||
( MonadThrow m,
|
||||
MonadOtel m
|
||||
) =>
|
||||
(Optional (Label "contentType" ByteString)) ->
|
||||
Json.Parse ErrorTree b ->
|
||||
Http.Request ->
|
||||
m b
|
||||
httpJson opts parser req = inSpan' "HTTP Request (JSON)" $ \span -> do
|
||||
let opts' = opts.withDefault (label @"contentType" "application/json")
|
||||
Http.httpBS req
|
||||
>>= assertM
|
||||
span
|
||||
( \resp -> do
|
||||
let statusCode = resp & Http.responseStatus & (.statusCode)
|
||||
contentType =
|
||||
resp
|
||||
& Http.responseHeaders
|
||||
& List.lookup "content-type"
|
||||
<&> Wai.parseContentType
|
||||
<&> (\(ct, _mimeAttributes) -> ct)
|
||||
if
|
||||
| statusCode == 200,
|
||||
Just ct <- contentType,
|
||||
ct == opts'.contentType ->
|
||||
Right $ (resp & Http.responseBody)
|
||||
| statusCode == 200,
|
||||
Just otherType <- contentType ->
|
||||
Left [fmt|Server returned a non-json body, with content-type "{otherType}"|]
|
||||
| statusCode == 200,
|
||||
Nothing <- contentType ->
|
||||
Left [fmt|Server returned a body with unspecified content type|]
|
||||
| code <- statusCode -> Left [fmt|Server returned an non-200 error code, code {code}: {resp & showPretty}|]
|
||||
)
|
||||
>>= assertM
|
||||
span
|
||||
( \body ->
|
||||
Json.parseStrict parser body
|
||||
& first (Json.parseErrorTree "could not parse redacted response")
|
||||
)
|
||||
|
||||
redactedApiRequestJson ::
|
||||
( MonadThrow m,
|
||||
HasField "action" p ByteString,
|
||||
|
@ -576,4 +536,4 @@ redactedApiRequestJson ::
|
|||
redactedApiRequestJson dat parser =
|
||||
do
|
||||
mkRedactedApiRequest dat
|
||||
>>= httpJson defaults parser
|
||||
>>= Http.httpJson defaults parser
|
||||
|
|
Loading…
Reference in a new issue