Elixir: HTTPResponseStream to consume streaming API - http

I want to write a client which can consume streaming APIs. Essentially, have a getter that returns an HTTPResponseStream instead of HTTPResponse. I couldn't find one in HTTPotion, so I figured I'd give it a try instead. But I have no idea how to go about it, and would really appreciate some help!

You can do async requests with HTTPotion like so:
%HTTPotion.AsyncResponse{ id: id } = HTTPotion.get "http://example.com", [], [stream_to: self]
This will send messages of three different types to the current process (which is defined above via self):
# First, the response headers
%HTTPotion.AsyncHeaders{ id: ^id, status_code: 200, headers: _headers }
# Then, one or more chunks
%HTTPotion.AsyncChunk{ id: ^id, chunk: _chunk }
# And finally, an end message
%HTTPotion.AsyncEnd{ id: ^id }
The id can be used to handle the responses from multiple ongoing requests.

Related

How to make a gRPC firestore listen request in Rust?

Using gRPC bindings from https://github.com/gkkachi/firestore-grpc I was able to puzzle together something that is seemingly working but does not receive any content:
Creating the request:
let req = ListenRequest {
database: format!("projects/{}/databases/(default)", project_id),
labels: HashMap::new(),
target_change: Some(TargetChange::AddTarget(Target {
// "Rust" in hex: https://github.com/googleapis/python-firestore/issues/51
target_id: 0x52757374,
once: false,
target_type: Some(TargetType::Documents(DocumentsTarget {
documents: vec![users_collection],
})),
resume_type: None,
})),
};
Sending it:
let mut req = Request::new(stream::iter(vec![req]));
let metadata = req.metadata_mut();
metadata.insert(
"google-cloud-resource-prefix",
MetadataValue::from_str(&db).unwrap(),
);
println!("sending request");
let res = get_client(&token).await?.listen(req).await?;
let mut res = res.into_inner();
while let Some(msg) = res.next().await {
println!("getting response");
dbg!(msg);
}
(full code in this repo).
The request can be made but the stream does not contain any actual content. The only hint I get from the debug logs is
[2021-10-27T14:54:39Z DEBUG h2::codec::framed_write] send frame=GoAway { error_code: NO_ERROR, last_stream_id: StreamId(0) }
[2021-10-27T14:54:39Z DEBUG h2::proto::connection] Connection::poll; connection error error=GoAway(b"", NO_ERROR, Library)
Any idea what is missing?
The crucial thing I was missing as pointed out in the rust users forum was that the request stream was immediately ending which caused the connection to close. The send frame=GoAway was actually send by the client (facepalm).
To keep the connection open and receive responses we can keep the input stream pending: Request::new(stream::iter(vec![req]).chain(stream::pending())). There will be a better way to set things up and keep control over subsequent input requests but this is enough to fix the example.

vert.x request x-www-form-urlencoded array

Good day.
I want to make a web service for processing an HTTP request. The request has:
Content-Type: x-www-form-urlencoded and body: Name=Vasya&Email=main#mail.ru&Phone=49494994&payment[sys]=none&payment[systranid]=0&payment[orderid]=1133704863&payment[products][0][name]=Мороженко&payment[products][0][quantity]=1&payment[products][0][amount]=4566&payment[products][0][price]=4566&payment[products][0][sku]=4345345345&payment[products][1][name]=Стаканчик&payment[products][1][quantity]=1&payment[products][1][amount]=100&payment[products][1][price]=100&payment[products][1][sku]=59595959&payment[amount]=4666&formid=form218781270&formname=Cart
I can't get payment parameters (payment{}).
I use: routingContext.request().getParam("Phone") but for payment this approach does not work.
Please help with advice
Form data is plain flat key-value.
I created a little example server code based on your description:
suspend fun main() {
val vertx = Vertx.vertx()
val router = Router.router(vertx)
router.route()
.handler(BodyHandler.create())
router.post("/test")
.handler { ctx ->
ctx.request().formAttributes().forEach {
println("${it.key}: ${it.value}")
}
ctx.response().end()
}
vertx.createHttpServer()
.requestHandler(router)
.listenAwait(8080)
}
Running it with your body, this is the key value pairs in the map:
Name: Vasya
Email: main#mail.ru
Phone: 49494994
payment[sys]: none
payment[systranid]: 0
payment[orderid]: 1133704863
payment[products][0][name]: Мороженко
payment[products][0][quantity]: 1
payment[products][0][amount]: 4566
payment[products][0][price]: 4566
payment[products][0][sku]: 4345345345
payment[products][1][name]: Стаканчик
payment[products][1][quantity]: 1
payment[products][1][amount]: 100
payment[products][1][price]: 100
payment[products][1][sku]: 59595959
payment[amount]: 4666
formid: form218781270
formname: Cart
As you can see, there is no Payment key.
I suggest that you use JSON (application/json) for your server and client and not form-data, it will be simpler and better suited for your use-case.
If not, you can try to iterate the attributes and extract what you need from the form data.

How to write functioning Webmock stubs with nested data?

I'm trying to write rspec tests. Some of them have to stub calls to external service.
Some of these calls send nested data, and this data never seems to be processed by Webmock correctly.
describe 'calc' do
before do
stub_request(:any, url).with(body: hash_including(operation: 'calc'))
end
it 'works' do
request_data = { sale_type: 'money', cover_type: 'money',
region: 'rf', period: 9,
adults: 600, children: 750, mice: 500 }
# This thing makes a HTTP request:
MiteService.new(login_params).calc(request_data)
expected_body = { operation: 'calc', product: 'mite3',
sessionID: session, data: request_data }
expect(WebMock).to have_requested(:post, url).with(body: expected_body)
end
end
This test is expected to pass. The http call made by service seems to be correct, but Webmock is unable to read any nested data (which is in data part of body in this case).
1) MiteService API calls calc works
Failure/Error: expect(WebMock).to have_requested(:post, url).with(body: expected_body)
The request POST http://example.com/api with body {"data"=>{"sale_type"=>"money", "cover_type"=>"money", "region"=>"rf", "period"=>9, "adults"=>600, "children"=>750, "mice"=>500}, "operation"=>"calc", "product"=>"mite3", "sessionID"=>"123"} was expected to execute 1 time but it executed 0 times
The following requests were made:
<...>
POST http://example.com/api with body 'operation=calc&product=mite3&sessionID=123&data=%7B%3Asale_type%3D%3E%22money%22%2C+%3Acover_type%3D%3E%22money%22%2C+%3Aregion%3D%3E%22rf%22%2C+%3Aperiod%3D%3E9%2C+%3Aadults%3D%3E600%2C+%3Achildren%3D%3E750%2C+%3Amice%3D%3E500%7D' with headers {'Accept'=>'*/*', 'Content-Type'=>'application/x-www-form-urlencoded', 'Date'=>'Tue, 25 Dec 2018 08:20:32 GMT', 'User-Agent'=>'HTTPClient/1.0 (2.8.3, ruby 2.4.1 (2017-03-22))'} was made 1 time
Made it work for this example by converting fields into json in example spec.
expect(WebMock).to have_requested(:post, url).with(body: expected_body.to_json)

Python Request Session JIRA REST post http 405

Using python requests session I can connect to JIRA and retrieve issue information ...
session = requests.Session()
headers = {"Authorization": "Basic %s" % bas64_val}
session.post(jira_rest_url, headers=headers)
jira = session.get(jira_srch_issue_url + select_fields)
# select_fields = the fields I want from the issue
Now I'm trying to post a payload via the JIRA API, using a fixed issue url e.g. "https://my_jira_server.com:1234/rest/api/latest/issue/KEY-9876"
Which should be a case of the following, given: https://developer.atlassian.com/jiradev/jira-apis/about-the-jira-rest-apis/jira-rest-api-tutorials/jira-rest-api-example-edit-issues
payload = { "update": {
"fixVersions": [ {"set": "release-2.139.0"} ]
}}
posted = session.post(jira_task_url, data=payload)
# returns <Response [405]>
# jira_task_url = https://my_jira_server.com:1234/rest/api/latest/issue/KEY-9876
But this doesn't appear to work! Looking into the http 405 response, suggests that my payload is not properly formatted! Which notably, is the not easiest thing to diagnose.
What am I doing wrong here? Any help on this would be much appreciated.
Please note, I am not looking to use the python jira module, I am using requests.session to manage several sessions for different systems i.e. JIRA, TeamCity, etc..
Found the solution! I had two problems:
1) The actual syntax structure should have been:
fix_version = { "update": { "fixVersions": [ {"set" : [{ "name" : "release-2.139.0" }]}]
2) To ensure the payload is actually presented as JSON, use json.dumps() which takes an object and produces a string (see here) AND set 'content-type' to 'application/json':
payload = json.dumps(fix_version)
app_json = { 'content-type': 'application/json' }
session.put(https://.../rest/api/latest/issue/KEY-9876, headers=app_json, data=payload)
Rather than trying to define the JSON manually!

Meteor http calls limitations

Currently, I use the built-in meteor http method (see http://docs.meteor.com/#http) for issuing http calls, on both my client and my server.
However, I'm experiencing two issues:
is it possible to cancel a request?
is it possible to have multiple query parameters which share the same key?
Are these just Meteor limitations, or are there ways to get both to work using Meteor?
I know I could you jquery on the clientside, and there must be a server-side solution which supports both as wel, but I'd prefer sticking with meteor code here.
"is it possible to cancel a request?"
HTTP.call() does not appear to return an object on which we could call something like a stop() method. Perhaps a solution would be to prevent execution of your callback based on a Session variable?
HTTP.call("GET", url, function(error, result) {
if (!Session.get("stopHTTP")) {
// Callback code here
}
});
Then when you reach a point where you want to cancel the request, do this:
Session.set("stopHTTP", true);
On the server, instead of Session perhaps you could use an environment variable?
Note that the HTTP.call() options object does accept a timeout key, so if you're just worried about the request never timing out, you can set this to whatever millisecond integer you want.
"is it possible to have multiple query parameters which share the same key?"
Yes, this appears to be possible. Here's a simple test I used:
Meteor code:
HTTP.call("GET", "http://localhost:1337", {
query: "id=foo&id=bar"
}, function(error, result) {
// ...
});
Separate Node.js server: (just the basic example on the Node.js homepage, with a console.log line to output the request URL with query string)
var http = require('http');
http.createServer(function(req, res) {
console.log(req.url); // Here I log the request URL, with the query string
res.writeHead(200, {
'Content-Type': 'text/plain'
});
res.end('Hello World\n');
}).listen(1337, '127.0.0.1');
console.log('Server running at http://127.0.0.1:1337/');
When the Meteor server is run, the Node.js server logged:
/?id=foo&id=bar
Of course, this is only for GET URL query parameters. If you need to do this for POST params, perhaps you could store the separate values as a serialized array string with EJSON.stringify?

Resources