-
-
Notifications
You must be signed in to change notification settings - Fork 264
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
HTTP Module: http:get(), http:post(), … #914
Comments
Other implementations: |
Postponed. |
Herr is some more information on the planned enhancements:
Function signatures: http:delete($uri[,$options])
http:get($uri[,$options])
http:head($uri[,$options])
http:options($uri[,$options])
http:post($uri[,$options])
http:put($uri[,$options])
http:send($uri,$method[,$options]) Options: map {
"username" : "user",
"password" : "pass",
"auth-method" : "Basic",
"status-only": true(),
"override-media-type": "text/plain",
"follow-redirect": false(),
"timeout": 30,
"headers": map {
"user-agent": "..." (: , ... :)
},
"body": map {
"media-type": "text/plain",
"content": "..."
}
} |
So if I understand there are two main drivers here:
What do you see the return type of these functions looking like? I was never to sure about the sequence approach. There are some arguments that a Map would be a better result. The most interesting thing for me would be how to handle multipart request/response in a way that does not prohibit lazy construction and/or streaming. Perhaps we need to also give some consideration to WebSockets, or do you see that as a separate module? |
Exactly. The design heavily borrows from the Zorba HTTP Client implementation, which I referenced in an earlier comment, and which I was happy to use in the past. The result of a simple GET request looks as follows: map {
"message": "OK",
"status": "200",
"headers": map {
"Last-Modified": "Thu, 31 May 2018 11:55:40 GMT",
"Server": "Apache",
"Content-Type": "text/html",
"Date": "Wed, 25 Jul 2018 17:09:22 GMT",
"Content-Length": "15749",
"Accept-Ranges": "bytes"
},
"body": map {
"media-type": "text/html",
"content": <html class="homepage" lang="en">...</html>
}
}
In BaseX, we lazy/streamable items can be used, which will only retrieve the actual result when requested. For example, if I am not sure how this could look like when creating the HTTP response. After all, it seems to depend on the underlying implementation anyway.
Regarding WebSockets, I would indeed propose to handle those in a separate module. As far as I know, while WebSocket are downward compatible with HTTP (in particular the handshake), they are actually a different protocol. I already implemented large parts of this proposal today; it was pretty straightforward. |
Some more notes:
Here is the full list of function signatures:
|
So a few questions:
We implement lazy streamable for binary files in a similar manner to yourselves I think. Everything there is a stream which is untouched until serialization time. I was wondering more about if there was value in making it explicit in the API, so that XQuery users could have control over this. i.e. for the body they supply a function instead of an xs:anyAtomicType or node(). I can see some use-cases where this would be important. |
The map for a multipart request could look as follows: map {
"status-only": true(),
"headers": map {
"user-agent": "BaseX"
},
"multipart": map {
"boundary": "--AaB03x", (: optional :)
"parts": (
map {
"headers" : map {
"Content Disposition": "file"
},
"body": map {
"media-type": "image/png",
"content": http:get("http://docs.basex.org/skins/vector/images/wiki.png")
}
}
)
}
} As you may see, it’s pretty close to the structure of EXPath requests. In Zorba, an array was used for the entries of the The response for multipart looks pretty much the same: Both the headers and the payload are returned as a single map. Similar to EXPath and Zorba, a You are right, the term “options” is misleading. We could give it a more generic name (such as “request”)? Or we choose a more radical approach indeed, and add another 2nd parameter to If we introduced an extra parameter for bodies in those 3 cases, we should probably return a sequence instead:
Here are the two representations: (: Request with 2 arguments :)
http:post(
'URI',
map {
'status-only': true(),
'body': map {
'media-type': 'text/plain',
'content': file:read-text('bla')
}
}
)
(: Request with 3 arguments :)
http:post(
'URI',
map {
'media-type': 'text/plain',
'content': file:read-text('bla')
},
map {
'status-only': true()
}
) For the second version, we would need to find an intuitive solution for indicating if a reponse is multipart or not. I haven’t thought about nested multipart requests. In principle, all approaches here (requests and responses) can be arbitrarily nested.
Currently, I’m not aware of any other functions that behave like this, but, yes, it could be an option. In that case, I would tend to use |
Some more thoughts on http:post($uri as xs:string, $payload as item()?) as map(*)
http:post($uri as xs:string, $payload as item()?, $options as map(*)) as map(*) The following types are accepted for the payload:
@adamretter: We could add an optional type The result is returned in a single map. If a payload is available, it is bound to the map {
"message": "OK",
"status": "200",
"headers": map {
"Last-Modified": "Thu, 1 Jan 1995 01:01:01 GMT", (: , ... :)
},
"body": map {
"media-type": "text/html",
"content": <html class="homepage" lang="en">...</html>
}
} The full list of function signatures: http:options($uri as xs:string) as xs:string*
http:options($uri as xs:string, $options as map(*)) as xs:string*
http:get($uri as xs:string) as map(*)
http:get($uri as xs:string, $options as map(*)) as map(*)
http:post($uri as xs:string, $payload as item()?) as map(*)
http:post($uri as xs:string, $payload as item()?, $options as map(*)) as map(*)
http:put($uri as xs:string, $payload as item()?) as map(*)
http:put($uri as xs:string, $payload as item()?, $options as map(*)) as map(*)
http:delete($uri as xs:string) as map(*)
http:delete($uri as xs:string, $options as map(*)) as map(*)
http:head($uri as xs:string) as map(*)
http:head($uri as xs:string, $options as map(*)) as map(*)
http:send($uri as xs:string, $method as xs:string, $payload as item()?) as map(*)
http:send($uri as xs:string, $method as xs:string, $payload as item()?, $options as map(*)) as map(*) |
Request with normal body"body": <html class="homepage" lang="en">...</html> Request with lazy body"body": function() { <html class="homepage" lang="en">expensive computation...</html> } Request with explicit media-type conversion"body": map {
"media-type": "text/html",
"content": <html class="homepage" lang="en">...</html>
} Request with lazy explicit media-type conversion"body": function() { map {
"media-type": "text/html",
"content": <html class="homepage" lang="en">expensive compuation...</html>
}} Request with multi-part body (two parts)"body": [
map {
"content": <html class="homepage" lang="en">...</html>
},
map {
"headers": map {
"Content-Disposition": 'form-data; name="uploadedfile"; filename="hello.o"',
"Content-Type": "application/x-object"
},
"content": someBase64==
}
] Request with multi-part body (one part)"body": [
map {
"headers": map {
"Content-Disposition": 'form-data; name="uploadedfile"; filename="hello.o"',
"Content-Type": "application/x-object"
},
"content": someBase64==
}
] |
And I developed sympathy for your suggestion to have explicit payload arguments… Someone else I talked to was wondering as well if a POST function without body makes sense.
I thought about using sequences, because some users still fight with the syntax of arrays (by mixing up
I like this idea a lot…
An exemplary POST request: (: OLD :)
http:post(
'http://json.io/',
map {
'media-type': 'application/json',
'content': '{ "key": "value" }'
}
)
(: NEW :)
http:post(
'http://json.io/',
'{ "key": "value" }',
map { 'headers': map { 'Content-Type': 'application/json' } }
) An exemplary GET response: map {
'headers': map {
'Content-Type': 'application/json'
},
'body': '{ "key": "value" }' (: string? map? binary? :)
) One thing I haven’t touched so far is in which format a reponse will be returned to the client. Currently, I simply adopted the existing conversion rules. |
PS: As JSON is one of the predominant data type for HTTP requests nowadays, we could even go as far as to interpret map arguments as JSON:
|
I can't think of a use-case where a user would need to specify the boundary manually. So unless anyone can claim otherwise, I think we can probably drop it.
I think we have to be a little bit careful here. We have to clearly differentiate between the HTTP Perhaps we should work next on defining implicit and explicit serialization rules?
Whilst I like the spirit of that idea. Again, I think we need to be very careful here. Whilst we could do that for non-multipart, it brings in an asymmetry with a multipart request/response where each multipart is a |
My thought here was that the content-type would only be assigned as header and passed on to the server if no explicit content-type was supplied by the user. If a user specifies the content-type in the request, (s)he still has the choice of converting the body to a binary or string before supplying it to the HTTP function. It would work similar for multipart responses: If a user supplies no Content-Type in the headers section of a multipart body, we would rely on the implicit conversion rules. To summarize this (all rules would apply to both the single main body as well as the multipart bodies):
My early stance on all this was to completely drop implicit conversions in the newly defined functions, because the EXPath solution regularly leads to confused questions on our mailing list. A bare-bones design would have been to only allow a single binary item as body argument. However, that would have required additional functions for converting multipart messages to binary data… And it would have made streaming more difficult. The convenience conversions may simplify streaming in general. I think there is an important difference between requests and responses: RequestsAs you indicated, a developer usually knows what the server requires, so we should have total control of the data that will be sent to the server. Responses
Do you have an example how this could look like? Would you like to have something like this for both requests and responses? |
|
I will write down examples in an upcoming document (see below…).
Thanks, good to remember.
Absolutely! I have just sent you an e-mail to discuss what might be the best domain (expath, exquery). Ideally, we can already use the collaborative result as final spec without too many adaptations. |
Postponed to a later version. |
New functions should be added to the HTTP Module, which shouldn’t do any magic as
http:send-request
does. Next, make functions streamable:http://www.mail-archive.com/[email protected]/msg03981.html
The text was updated successfully, but these errors were encountered: