http-client: content-type override, split header values

Add a "response-content-type-override" field to the resolve step
to override body parsing behavior.

Send each reponse header as a seperate message rather than
concatenated by the Nim HTTP client library.
This commit is contained in:
Emery Hemingway 2024-06-22 09:11:03 +03:00
parent f6879a906d
commit 0758f0996b
5 changed files with 31 additions and 17 deletions

View File

@ -302,7 +302,7 @@ $client-resolver ? <accepted ?client> $client [
# Pass the resolver dataspace to the client. # Pass the resolver dataspace to the client.
? <service-object <daemon http-client> ?cap> [ ? <service-object <daemon http-client> ?cap> [
$cap <resolve <http-client {}> $client-resolver> $cap <resolve <http-client { response-content-type-override: "" }> $client-resolver>
] ]
<require-service <daemon http-client>> <require-service <daemon http-client>>

View File

@ -24,7 +24,15 @@ UnixAddress = <unix @path string>.
SocketAddress = TcpAddress / UnixAddress . SocketAddress = TcpAddress / UnixAddress .
HttpClientStep = <http-client { }>. HttpClientStep = <http-client @detail HttpClientStepDetail>.
HttpClientStepDetail = {
# Body parsing happens according to a heuristic interpretation
# of Content-Type headers.
# Set this field as "application/octet-stream" to never parse
# response bodies or to "application/json" to parse all response
# bodies as JSON.
response-content-type-override: string
} .
HttpDriverStep= <http-driver { }>. HttpDriverStep= <http-driver { }>.

View File

@ -7,7 +7,7 @@
"bom-ref": "pkg:nim/syndicate_utils", "bom-ref": "pkg:nim/syndicate_utils",
"name": "syndicate_utils", "name": "syndicate_utils",
"description": "Utilites for Syndicated Actors and Synit", "description": "Utilites for Syndicated Actors and Synit",
"version": "20240621", "version": "20240622",
"authors": [ "authors": [
{ {
"name": "Emery Hemingway" "name": "Emery Hemingway"

View File

@ -28,7 +28,7 @@ proc url(req: HttpRequest): Uri =
proc toContent(body: Value; contentType: var string): string = proc toContent(body: Value; contentType: var string): string =
case contentType case contentType
of "application/json": of "application/json", "text/javascript":
var stream = newStringStream() var stream = newStringStream()
writeText(stream, body, textJson) writeText(stream, body, textJson)
return stream.data.move return stream.data.move
@ -52,9 +52,9 @@ proc toContent(body: Value; contentType: var string): string =
raise newException(ValueError, "unknown content type") raise newException(ValueError, "unknown content type")
proc spawnHttpClient*(turn: Turn; relay: Cap): Actor {.discardable.} = proc spawnHttpClient*(turn: Turn; relay: Cap): Actor {.discardable.} =
let pat = Resolve?:{ 0: HttpClientStep.matchType, 1: grab() } let pat = Resolve?:{ 0: HttpClientStep.grabWithin, 1: grab() }
result = spawnActor(turn, "http-client") do (turn: Turn): result = spawnActor(turn, "http-client") do (turn: Turn):
during(turn, relay, pat) do (observer: Cap): during(turn, relay, pat) do (detail: HttpClientStepDetail, observer: Cap):
linkActor(turn, "session") do (turn: Turn): linkActor(turn, "session") do (turn: Turn):
let ds = turn.newDataspace() let ds = turn.newDataspace()
discard publish(turn, observer, ResolvedAccepted(responderSession: ds)) discard publish(turn, observer, ResolvedAccepted(responderSession: ds))
@ -64,7 +64,7 @@ proc spawnHttpClient*(turn: Turn; relay: Cap): Actor {.discardable.} =
try: try:
var var
headers = newHttpHeaders() headers = newHttpHeaders()
contentType = "" contentType: string
for key, val in ctx.req.headers: for key, val in ctx.req.headers:
if key == Symbol"content-type" or key == Symbol"Content-Type": if key == Symbol"content-type" or key == Symbol"Content-Type":
contentType = val contentType = val
@ -74,20 +74,23 @@ proc spawnHttpClient*(turn: Turn; relay: Cap): Actor {.discardable.} =
ctx.req.method.string.toUpper, ctx.req.method.string.toUpper,
ctx.req.body.toContent(contentType), headers ctx.req.body.toContent(contentType), headers
) )
client.headers["content-type"] = contentType
var resp = HttpResponse(orKind: HttpResponseKind.status) var resp = HttpResponse(orKind: HttpResponseKind.status)
resp.status.code = stdRes.status[0 .. 2].parseInt resp.status.code = stdRes.status[0 .. 2].parseInt
resp.status.message = stdRes.status[3 .. ^1] resp.status.message = stdRes.status[3 .. ^1]
message(turn, peer, resp) message(turn, peer, resp)
resp = HttpResponse(orKind: HttpResponseKind.header) resp = HttpResponse(orKind: HttpResponseKind.header)
for key, val in stdRes.headers: for key, vals in stdRes.headers.table:
if key == "Content-Type": for val in vals.items:
resp.header.name = key.Symbol
resp.header.value = val
message(turn, peer, resp)
if detail.`response-content-type-override` != "":
contentType = detail.`response-content-type-override`
else:
for val in stdRes.headers.table.getOrDefault("content-type").items:
contentType = val contentType = val
resp.header.name = key.Symbol
resp.header.value = val
message(turn, peer, resp)
case contentType case contentType
of "application/json", "text/preserves": of "application/json", "text/preserves", "text/javascript":
message(turn, peer, message(turn, peer,
initRecord("done", stdRes.bodyStream.readAll.parsePreserves)) initRecord("done", stdRes.bodyStream.readAll.parsePreserves))
of "application/preserves": of "application/preserves":

View File

@ -39,6 +39,9 @@ type
XsltArguments* {.preservesRecord: "xslt".} = object XsltArguments* {.preservesRecord: "xslt".} = object
`field0`*: XsltArgumentsField0 `field0`*: XsltArgumentsField0
HttpClientStepDetail* {.preservesDictionary.} = object
`response-content-type-override`*: string
JsonSocketTranslatorStepField0* {.preservesDictionary.} = object JsonSocketTranslatorStepField0* {.preservesDictionary.} = object
`socket`*: SocketAddress `socket`*: SocketAddress
@ -51,10 +54,8 @@ type
FileSystemUsageArguments* {.preservesRecord: "file-system-usage".} = object FileSystemUsageArguments* {.preservesRecord: "file-system-usage".} = object
`field0`*: FileSystemUsageArgumentsField0 `field0`*: FileSystemUsageArgumentsField0
HttpClientStepField0* {.preservesDictionary.} = object
HttpClientStep* {.preservesRecord: "http-client".} = object HttpClientStep* {.preservesRecord: "http-client".} = object
`field0`*: HttpClientStepField0 `detail`*: HttpClientStepDetail
HttpDriverStepField0* {.preservesDictionary.} = object HttpDriverStepField0* {.preservesDictionary.} = object
@ -109,6 +110,7 @@ type
proc `$`*(x: JsonTranslatorArguments | SocketAddress | Base64DecoderArguments | proc `$`*(x: JsonTranslatorArguments | SocketAddress | Base64DecoderArguments |
SqliteStep | SqliteStep |
XsltArguments | XsltArguments |
HttpClientStepDetail |
JsonSocketTranslatorStep | JsonSocketTranslatorStep |
FileSystemUsageArguments | FileSystemUsageArguments |
HttpClientStep | HttpClientStep |
@ -127,6 +129,7 @@ proc `$`*(x: JsonTranslatorArguments | SocketAddress | Base64DecoderArguments |
proc encode*(x: JsonTranslatorArguments | SocketAddress | Base64DecoderArguments | proc encode*(x: JsonTranslatorArguments | SocketAddress | Base64DecoderArguments |
SqliteStep | SqliteStep |
XsltArguments | XsltArguments |
HttpClientStepDetail |
JsonSocketTranslatorStep | JsonSocketTranslatorStep |
FileSystemUsageArguments | FileSystemUsageArguments |
HttpClientStep | HttpClientStep |