Error establishing connection to Binance via Tungstenite Websocket API - rust

I am trying to establish a connection to the binance websocket server with the code:
use tungstenite::{connect, Message};
use url::Url;
fn main() {
let (mut socket, response) =
connect(Url::parse("wss://stream.binance.com:9443/ws/BNBBTC#aggTrade").unwrap()).expect("Can't connect");
println!("Connected to the server");
println!("Response HTTP code: {}", response.status());
println!("Response contains the following headers:");
for (ref header, _value) in response.headers() {
println!("* {}", header);
}
}
with Cargo.toml containing:
[dependencies]
tungstenite = "0.17.3"
url = "2.3.1"
I am getting the error:
thread 'main' panicked at 'Can't connect: Http(Response { status: 400, version: HTTP/1.1, headers: {"server": "awselb/2.0", "date": "Sat, 22 Oct 2022 20:23:40 GMT", "content-type": "text/html", "content-length": "220", "connection": "close"}, body: None })', src/main.rs:6:90
stack backtrace:
0: rust_begin_unwind
at /rustc/a55dd71d5fb0ec5a6a3a9e8c27b2127ba491ce52/library/std/src/panicking.rs:584:5
1: core::panicking::panic_fmt
at /rustc/a55dd71d5fb0ec5a6a3a9e8c27b2127ba491ce52/library/core/src/panicking.rs:142:14
2: core::result::unwrap_failed
at /rustc/a55dd71d5fb0ec5a6a3a9e8c27b2127ba491ce52/library/core/src/result.rs:1814:5
3: core::result::Result<T,E>::expect
at /rustc/a55dd71d5fb0ec5a6a3a9e8c27b2127ba491ce52/library/core/src/result.rs:1064:23
4: untitled12::main
at ./src/main.rs:6:9
5: core::ops::function::FnOnce::call_once
at /rustc/a55dd71d5fb0ec5a6a3a9e8c27b2127ba491ce52/library/core/src/ops/function.rs:248:5
note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.
What is the reason behind this error? What am I missing? It seems like a bad request error, however my request is supposed to be well-formed according to the binance websocket API

By default, tungstenite does not have TLS enabled, so I had to enable in Cargo.toml:
tungstenite = { version = "0.17.3", features = ["native-tls"] }

Related

DocuSign, Node SDK, JWT auth, Envelope fetch, "The custom error module does not recognize this error."

[FINAL UPDATE]
Fixed per this thread: https://github.com/docusign/docusign-esign-node-client/issues/295
Apparently related to security additions made to DocuSign's API.
I haven't encountered this issue before today. I gave a demo to a client just last week and the same code was working fine. Today I get the following:
{
"status": 444,
"response": {
"req": {
"method": "GET",
"url": "https://demo.docusign.net/restapi...",
"data": {},
"headers": {
"user-agent": "node-superagent/3.8.2",
"x-docusign-sdk": "Node",
"authorization": "Bearer ABC...",
"content-type": "application/json",
"accept": "application/json"
}
},
"header": {
"content-type": "text/html",
"x-docusign-tracetoken": "2eeb8caa-8865-4898-bef9-d3611bfaa3f7",
"x-docusign-node": "DA2DFE5",
"date": "Fri, 17 Jun 2022 01:02:02 GMT",
"content-length": "54",
"connection": "close",
"strict-transport-security": "max-age=31536000; includeSubDomains"
},
"status": 444,
"text": "The custom error module does not recognize this error."
}
}
Through the node SDK I fetch a token using requestJWTUserToken, and set the apiclient's auth header, then I new-up an EnvelopesAPI instance. When I make a call to getEnvelope() or listStatusChanges(), then I get the error above. None of this code has changed in months, and I'm using the same integration key, account, private key, everything. I've demo'd it all a few times now - no issues.
An interesting observation: the error above gives me a URL and token. The token is valid, and if I make a request to the URL (the envelopes endpoint) via Postman using said token, then the request succeeds. So my calls through the SDK seem to be failing for some reason.
I can't seem to crack this one, and now I can't get around it given a couple demo systems that worked just last week.
I'm using the docusign-esign 5.17 module - upgraded from 5.15 in an attempt to fix the issue. No luck.
Where is this error coming from?
[Update 1]
I'm running my node app that is making requests through the DocuSign Node SDK against a proxy so I can see what the failing request actually look like:
They fail the same way.
HTTP/1.1 444
Content-Type: text/html
X-DocuSign-TraceToken: 338534c6-c8c3-4b01-9b66-35d697cd0053
X-DocuSign-Node: DA1DFE4
Date: Fri, 17 Jun 2022 03:55:07 GMT
Content-Length: 54
Vary: Accept-Encoding
Connection: close
Strict-Transport-Security: max-age=31536000; includeSubDomains
The custom error module does not recognize this error.
I'm using Proxyman to catch the request, and like Chrome or Firefox it will let you copy a request as a cURL command. If I copy the failing request as cURL, then run in at the terminal, it succeeds.
[MacBookPro0020]~/source/docusign/jwt-smoke-test:0 (master)
$ curl 'https://demo.docusign.net/restapi/v2.1/accounts/a0a4c81f-.../envelopes?envelope_ids=e750526f-...&envelope_ids=a38b794b...&envelope_ids=a5d8c586-...' \
-H 'Host: demo.docusign.net' \
-H 'User-Agent: node-superagent/3.8.2' \
-H 'X-DocuSign-SDK: Node' \
-H 'Node-Ver: v14.18.3' \
-H 'Authorization: Bearer ABCD...' \
-H 'Accept: application/json' \
-H 'Connection: close' \
-H 'Content-Type: application/json' \
--proxy http://localhost:9090
{"resultSetSize":"1","startPosition":"0","endPosition":"0","totalSetSize":"1","nextUri":"","previousUri":"","envelopes":[{"status":"created","documentsUri":"/envelopes/d97565c8...purgeState":"unpurged","envelopeIdStamping":"true","autoNavigation":"true","isSignatureProviderEnvelope":"false","allowComments":"true","anySigner":null,"envelopeLocation":"current_site"}]}
I'm using a JWT auth token, so again, I'm getting a valid token. Calls through the SDK consistently fail, but cURL'ing and manually requesting through Postman both succeed.
I'm at a loss.
Additional details: I see this same issue on MacOS and Windows (i.e., node app hosting docusign-esign). I'm using auth code grant to send envelopes and query envelope statuses and that works fine. I've used JWT Grant without issue up until this week (just demo'd automated functionality last week and it worked.) I haven't made any code changes to my DocuSign functionality, nor have my colleagues, at least according to the repo's history.
I don't recall ever encountering the error above before. I'd love to know why cURL'ing the same request succeeds. I'd rather not ditch the SDK and roll my own requests, but it wouldn't be difficult.
[Update 2]
Here's a simple repro - it's a quick and dirty copy of the QuickStart demo project for the node SDK. I'm using only docusign-esign.
Exact. same. issue.
Again, I can take that token and drop it into cURL or postman and the request will succeed. There's not a lot of code here. The token is valid.
async function main() {
// Data used
// dsConfig.dsClientId
// dsConfig.impersonatedUserGuid
// dsConfig.privateKey
// dsConfig.dsOauthServer
let dsConfig = dsConfig_customer; // defined globally
const jwtLifeSec = 10 * 60, // requested lifetime for the JWT is 10 min
dsApi = new docusign.ApiClient();
dsApi.setOAuthBasePath(dsConfig.dsOauthServer.replace('https://', '')); // it should be domain only.
const results = await dsApi.requestJWTUserToken(dsConfig.dsClientId,
dsConfig.impersonatedUserGuid, 'signature impersonation', dsConfig.privateKey,
jwtLifeSec);
console.log( results.body.access_token );
const userInfo = await dsApi.getUserInfo(results.body.access_token);
dsApi.setBasePath(userInfo.accounts[0].baseUri + '/restapi');
dsApi.addDefaultHeader( 'Authorization', 'Bearer ' + results.body.access_token );
const envelopesAPI = new docusign.EnvelopesApi(dsApi);
const res = await envelopesAPI.getEnvelope( dsConfig.accountID, 'e1917111-2900-48e8-9054-799169379c8a', null );
console.log(res);
return {
accessToken: results.body.access_token,
tokenExpirationTimestamp: expiresAt,
userInfo,
account: userInfo.accounts[0]
};
}
main().then(result => console.log(result)).catch(err=>console.error(err));
...
header: {
'content-type': 'text/html',
'x-docusign-tracetoken': '685b6226-a0d3-4547-94c7-df0216d884a3',
'x-docusign-node': 'DA2DFE188',
date: 'Fri, 17 Jun 2022 05:20:12 GMT',
'content-length': '54',
vary: 'Accept-Encoding',
connection: 'close',
'strict-transport-security': 'max-age=31536000; includeSubDomains'
},
statusCode: 444,
status: 444,
statusType: 4,
info: false,
ok: false,
redirect: false,
clientError: true,
serverError: false,
error: Error: cannot GET /restapi/v2.1/accounts/49754554-ABCD-.../envelopes/e1917111-2900-48e8-9054-799169379c8a (444)
...
I finally manage to resolve the issue by downgrading my Docusign SDK NodeJS version from 5.15.0 to 5.7.0 in my package.json file.
An issue relative to this can be find there, I really hope this issue will be resolved anytime soon.
Edit :
The Docusign support actually took action on this (the github's issue from above is now closed), it might be interesting to test again with the latest version of Docusign SDK NodeJS (or the one you were using previously).

Axios/HTTPS certificate expired error only on Windows

I am currently working on a VSCode extension that internally makes some API calls to a HTTPS protected endpoint. On Linux and Mac OS the extension works as expected but on Windows machines axios, the internal HTTP client used to make the API calls, is rejecting those requests due to the certificates being expired. When I access the API endpoint though via Firefox, Chrome and even Edge the certificates seems find.
I have upgraded Node to 16.14.0 and also to 17.6.0 but the problem still remains. As the API is only accessible through our VPN, with my VPN activated of course, I used testssl.sh to verify that the whole trust-chain is still valid:
Testing protocols via sockets except NPN+ALPN
SSLv2 not offered (OK)
SSLv3 not offered (OK)
TLS 1 not offered
TLS 1.1 not offered
TLS 1.2 offered (OK)
TLS 1.3 offered (OK): final
NPN/SPDY not offered
ALPN/HTTP2 h2, http/1.1 (offered)
Testing cipher categories
NULL ciphers (no encryption) not offered (OK)
Anonymous NULL Ciphers (no authentication) not offered (OK)
Export ciphers (w/o ADH+NULL) not offered (OK)
LOW: 64 Bit + DES, RC[2,4] (w/o export) not offered (OK)
Triple DES Ciphers / IDEA not offered
Obsolete CBC ciphers (AES, ARIA etc.) offered
Strong encryption (AEAD ciphers) offered (OK)
Testing robust (perfect) forward secrecy, (P)FS -- omitting Null Authentication/Encryption, 3DES, RC4
PFS is offered (OK) TLS_AES_256_GCM_SHA384 TLS_CHACHA20_POLY1305_SHA256 ECDHE-RSA-AES256-GCM-SHA384 ECDHE-RSA-AES256-SHA384 ECDHE-RSA-AES256-SHA
DHE-RSA-AES256-GCM-SHA384 DHE-RSA-AES256-SHA256 DHE-RSA-AES256-SHA TLS_AES_128_GCM_SHA256 ECDHE-RSA-AES128-GCM-SHA256 ECDHE-RSA-AES128-SHA256
ECDHE-RSA-AES128-SHA DHE-RSA-AES128-GCM-SHA256 DHE-RSA-AES128-SHA256 DHE-RSA-AES128-SHA
Elliptic curves offered: prime256v1 secp384r1 secp521r1 X25519 X448
DH group offered: HAProxy (2048 bits)
Testing server preferences
Has server cipher order? yes (OK) -- TLS 1.3 and below
Negotiated protocol TLSv1.3
Negotiated cipher TLS_AES_256_GCM_SHA384, 253 bit ECDH (X25519)
Cipher order
TLSv1.2: ECDHE-RSA-AES128-GCM-SHA256 ECDHE-RSA-AES256-GCM-SHA384 DHE-RSA-AES128-GCM-SHA256 DHE-RSA-AES256-GCM-SHA384 ECDHE-RSA-AES128-SHA256 ECDHE-RSA-AES128-SHA
ECDHE-RSA-AES256-SHA384 ECDHE-RSA-AES256-SHA DHE-RSA-AES128-SHA256 DHE-RSA-AES128-SHA DHE-RSA-AES256-SHA256 DHE-RSA-AES256-SHA
TLSv1.3: TLS_AES_256_GCM_SHA384 TLS_CHACHA20_POLY1305_SHA256 TLS_AES_128_GCM_SHA256
...
Certificate Validity (UTC) 65 >= 30 days (2022-02-01 08:18 --> 2022-05-02 08:18)
...
The other certificates in the trustchain are also valid and not expired at all.
Axios is used within an own HttpClient class that looks like this:
export class HttpClient {
private readonly instance: AxiosInstance;
constructor() {
this.instance = axios.create();
}
async asyncGet<T>(url: string): Promise<Response<T>> {
return this.instance.get<T>(url)
.then(axiosResponse => {
...
})
.catch(error => {
console.error(error);
throw error;
});
}
}
I also tried the https
const options: https:RequestOptions = {
hostname: '...',
port: 443,
path: '/...',
method: 'GET'
};
const request = https.request(url, options, (res: IncomingMessage) => {
console.log(`statusCode: ${res.statusCode ? res.statusCode : 'undefined'}`);
res.on('data', d => {
process.stdout.write(d as string);
}
});
request.on('error', error => {
console.error(error);
};
request.end();
analog to the samples given in the NodeJS documentation and tls
const socket = tls.connect(443, hostname, undefined, () => {
console.log('Connected to ' + hostname);
console.log('TLS.connect', socket.authorized ? 'authorized' : 'unauthorized');
console.log('Cipher: ' + JSON.stringify(socket.getCipher()));
console.log('Protocol: ' + JSON.stringify(socket.getProtocol()));
const peerCert: tls.DetailedPeerCertificate = socket.getPeerCertificate(true);
console.log(`Peer-Cert ${JSON.stringify(peerCert.subject)} - valid from: ${peerCert ? peerCert.valid_from : 'invalid'} valid till: ${peerCert ? peerCert.valid_to : 'invalid'}`);
const issuerCert = peerCert.issuerCertificate;
console.log(`issuer-Cert ${JSON.stringify(issuerCert.subject)} - valid from: ${issuerCert ? issuerCert.valid_from : 'invalid'} valid till: ${issuerCert ? issuerCert.valid_to : 'invalid'}`);
const rootCert = issuerCert.issuerCertificate;
console.log(`root-Cert ${JSON.stringify(rootCert.subject)} - valid from: ${rootCert ? rootCert.valid_from : 'invalid'} valid till: ${rootCert ? rootCert.valid_to : 'invalid'}`);
});
as proposed in this answer.
Both axios and https return an error like this:
{
"message": "certificate has expired",
"name": "Error",
"stack": "Error: certificate has expired\n\tat TLSSocket.onConnectSecure (_tls_wrap.js:1497:34)\n\tat TLSSocket.emit (events.js:315:20)\n\tat TLSSocket._finishInit (_tls_wrap.js:932:8)\n\tat TLSWrap.onhandshakedone (_tls_wrap.js:706:12)\n\tat TLSWrap.callbackTrampoline (internal/async_hooks.js:131:14)",
"config": {
"transitional": {
"silentJSONParsing": true,
"forcedJSONParsing": true,
"clarifyTimeoutError": false
},
"transformRequest": [
"null"
],
"transformResponse": [
"null"
],
"timeout": 0,
"xsrfCookieName": "XSRF-TOKEN",
"xsrfHeaderName": "X-XSRF-TOKEN",
"maxContentLength": -1,
"maxBodyLength": -1,
"headers": {
"Accept": "application/json, text/plain, */*",
"user-Agent": "axios/0.26.0"
},
"method": "get",
"url": "https://..."
},
"code": "CERT_HAS_EXPIRED",
"status": null
}
with a more human-readable stacktrace:
Error: certificate has expired
at TLSSocket.onConnectSecure (_tls_wrap.js:1497:34)
at TLSSocket.emit (events.js:315:20)
at TLSSocket._finishInit (_tls_wrap.js:932:8)
at TLSWrap.onhandshakedone (_tls_wrap.js:706:12)
at TLSWrap.callbackTrampoline (internal/async_hooks.js:131:14)
while for tls I get the following output:
Connected to ...
TLS.connect authorized
Cipher: {"name":"TLS_CHACHA20_POLY1305_SHA256","standardName":"TLS_CHACHA20_POLY1305_SHA256","version":"TLSv1/SSLv3"}
Protocol: "TLSv1.3"
Peer-Cert {"CN": "*...."} - valid from: Feb 1 08:18:23 2022 GMT valid till: May 2 08:18:22 2022 GMT
issuer-Cert {"C":"US","O":"Let's Encrypt","CN":"R3"} - valid from Sep 4 00:00:00 2020 GMT valid till: Sep 15 16:00:00 2025 GMT
root-Cert {"C":"US","O":"Internet Security Research Group","CN":"ISRG Root X1"} - valid from: Jan 20 19:14:03 2021 GMT valid till: Sep 30 18:14:03 2024 GMT
So tls seems to be able to connect to the API server and perform the SSL/TLS handshake, but https and axios somehow fail.
I also stumbled upon this question here, which seems to be related, but as I am already on the latest NodeJS release (as well as any dependency used in the extension is on the most recent version) and this error only occurs on Windows (mostly 10, unsure if and how many users actually use Windows 11) machines I think the question deserves its own spot here on SO.
In order to rule out a lack of common supported ciphers between the Windows and Node.js based tls implementation and the nginx managed server side, I also checked the available ciphers in Node via node -p crypto.constants.defaultCoreCipherList which returns a list like this:
TLS_AES_256_GCM_SHA384
TLS_CHACHA20_POLY1305_SHA256
TLS_AES_128_GCM_SHA256
ECDHE-RSA-AES128-GCM-SHA256
ECDHE-ECDSA-AES128-GCM-SHA256
ECDHE-RSA-AES256-GCM-SHA384
ECDHE-ECDSA-AES256-GCM-SHA384
DHE-RSA-AES128-GCM-SHA256
ECDHE-RSA-AES128-SHA256
DHE-RSA-AES128-SHA256
ECDHE-RSA-AES256-SHA384
DHE-RSA-AES256-SHA384
ECDHE-RSA-AES256-SHA256
DHE-RSA-AES256-SHA256
HIGH
!aNULL
!eNULL
!EXPORT
!DES
!RC4
!MD5
!PSK
!SRP
!CAMELLIA
which indicates that enough ciphers would overlap between client and server.
Why do I still get a certificate expired error on Windows machines with axios/https
when Linux and MacOS work just fine with these settings and tls is able to connect to the remote API sucessfully even on Windows machines?

Azure ML - Model not registering, encountering WebServiceException

I've successfully registered a model with the following exact same code snippet before:
#register model
from azureml.core.model import Model
register_model = Model.register(model_path = "./models",
model_name = "cr_tools",
description = "Tools relating to the Customer Relations classifier.",
workspace = ws)
register_model
But now it's not working for a different model (different ./models directory), and I'm encountering the following error:
ServiceException: ServiceException:
Code: 504
Message: Operation returned an invalid status code 'Gateway Time-out'
Details:
Headers: {
"Date": "Tue, 04 Jan 2022 22:12:54 GMT",
"Content-Type": "text/html",
"Content-Length": "160",
"Connection": "keep-alive",
"Strict-Transport-Security": "max-age=15724800; includeSubDomains; preload",
"X-Content-Type-Options": "nosniff",
"x-request-time": "60.019"
}
InnerException: 504 Server Error: Gateway Time-out for url: https://eastus.experiments.azureml.net/artifact/v2.0/subscriptions/c450f3d1-583c-495f-b5d3-0b38b99e70c0/resourceGroups/ba-p-zeaus-group020-rg/providers/Microsoft.MachineLearningServices/workspaces/p-group020-aml-ws-001/artifacts/batch/metadata/LocalUpload/220104T215629-7c0d42b6
Not able to find exact reason for your error. But generally ServiceException: ServiceException: Code: 504 error occurs when did not receive a timely response from the upstream server specified by the URI.
However, You can refer this github repo for model registration and deployment.

Implementing http partial requests with actix

I've made a server which provides larger videos via HTTP-Get requests. The following code snipped is used on the server side:
use actix_web::{web, App, HttpResponse, HttpServer, Responder, HttpRequest};
use std::str::FromStr;
//Some random data
lazy_static::lazy_static! {
static ref DATA : Vec<u8> = std::fs::read("data/movie1/bigfile.mp4").unwrap();
}
fn get_all_data() -> &'static[u8] {
&DATA
}
fn get_part_data(start: u64, end: u64) -> &'static[u8] {
&DATA[start as usize.. end as usize]
}
fn data_len() -> u64 {
DATA.len() as u64
}
//Extracts the begin and end from the range header string
fn parse_range(s: &str) -> (u64, Option<u64>) {
assert_eq!(&s[0..6], "bytes=");
let split = (&s[6..]).split("-").map(|s| s.to_string()).collect::<Vec<String>>();
assert_eq!(split.len(), 2);
(u64::from_str(&split[0]).unwrap(), match u64::from_str(&split[1]) {
Err(_) => None,
Ok(a) => Some(a)
})
}
async fn body_request(request: HttpRequest) -> impl Responder {
println!("Request received: {:#?}", request);
let range_requested = request.headers().contains_key("Range");
if range_requested {
println!("Range requested...");
let range_str = request.headers().get("Range").unwrap().to_str().unwrap();
let range = parse_range(range_str);
let start = range.0;
let end = match range.1 {
None => data_len(),
Some(s) => s
};
println!("Original Range: {:#?}, Interpreted range: {} - {}", range_str, start, end);
let response = HttpResponse::PartialContent()
.header("Content-Range", format!("{} {}-{}/{}", "bytes", start, end, data_len()))
.header("Content-Type", "video/mp4")
.body(get_part_data(start, end));
println!("Response: {:#?}", response);
response
} else {
println!("Whole document requested");
HttpResponse::Ok()
.header("Content-Type", "video/mp4")
.body(get_all_data())
}
}
#[actix_rt::test]
async fn part_test() -> std::io::Result<()> {
HttpServer::new(|| {
App::new()
.route("/", web::get().to(body_request))
})
.bind(("0.0.0.0", 8080))?
.run()
.await
}
This implementation should be able to resume cancelled downloads. However, the part request is not working with the wget utility, I have already read the Mozilla entry on how to respond to partial requests, but I cannot figure out why wget is retrying/not working.
I did the following:
Start the server with cargo test part_test -- --nocapture
Start wget with wget -c -v localhost:8080 and interrupt it with ctr-c
Restart wget with the same command (the -c option should resume the operation)
I expected the wget to just resume it normally, however it is not satisfied with the server's partial content for some reason. I would like to know why and how I can fix that.
wget output:
user#student-net-etx-0499 rusttest % wget -c -v localhost:8080
--2021-12-22 15:25:43-- http://localhost:8080/
Resolving localhost (localhost)... 127.0.0.1, ::1
Connecting to localhost (localhost)|127.0.0.1|:8080... connected.
HTTP request sent, awaiting response... 200 OK
Length: 300017432 (286M) [video/mp4]
Saving to: ‘index.html’
index.html 44%[============================> ] 128,02M 157MB/s
^C
user#student-net-etx-0499 rusttest % wget -c -v localhost:8080
--2021-12-22 15:25:47-- http://localhost:8080/
Resolving localhost (localhost)... 127.0.0.1, ::1
Connecting to localhost (localhost)|127.0.0.1|:8080... connected.
HTTP request sent, awaiting response... 206 Partial Content
Retrying.
--2021-12-22 15:25:48-- (try: 2) http://localhost:8080/
Connecting to localhost (localhost)|127.0.0.1|:8080... connected.
HTTP request sent, awaiting response... 206 Partial Content
Retrying.
^C
Server output:
Request received:
HttpRequest HTTP/1.1 GET:/
headers:
"accept": "*/*"
"accept-encoding": "identity"
"host": "localhost:8080"
"user-agent": "Wget/1.21.2"
"connection": "Keep-Alive"
Whole document requested
Request received:
HttpRequest HTTP/1.1 GET:/
headers:
"accept": "*/*"
"range": "bytes=158907840-"
"accept-encoding": "identity"
"host": "localhost:8080"
"user-agent": "Wget/1.21.2"
"connection": "Keep-Alive"
Range requested...
Original Range: "bytes=158907840-", Interpreted range: 158907840 - 300017432
Response:
Response HTTP/1.1 206 Partial Content
headers:
"content-type": "video/mp4"
"content-range": "bytes 158907840-300017432/300017432"
body: Sized(141109592)
Request received:
HttpRequest HTTP/1.1 GET:/
headers:
"accept": "*/*"
"range": "bytes=158907840-"
"accept-encoding": "identity"
"host": "localhost:8080"
"user-agent": "Wget/1.21.2"
"connection": "Keep-Alive"
Range requested...
Original Range: "bytes=158907840-", Interpreted range: 158907840 - 300017432
Response:
Response HTTP/1.1 206 Partial Content
headers:
"content-type": "video/mp4"
"content-range": "bytes 158907840-300017432/300017432"
body: Sized(141109592)
#cargo.toml
[package]
name = "rusttest"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
actix-web="*"
actix-rt="1.1.1"
futures="*"
serde_json = "*"
serde = { version = "*", features = ["derive"] }
qr2term = "0.2.2"
async-std= { version= "*", features = ["unstable"]}
async_flag="*"
lazy_static="*"

How to return an error to caller from an actix-web handler with client?

I created a server with actix_web that will connect through GET to another service using actix client and return body on success or error on error. I have been able to return the body but have no clue about how to return the error.
This is my handler:
fn echo_client(client: web::Data<Client>) -> impl Future<Item = HttpResponse, Error = Error> {
client
.get("127.0.0.1:9596/echo/javier") // <- Create request builder
.header("User-Agent", "Actix-web")
//.finish().unwrap()
.send() // <- Send http request
.map_err(|_| ())
//.map_err(Error::from)
.and_then(|response| {
response
.body()
.and_then(|body| {
println!("{:?}", body);
Ok(HttpResponse::Ok().body(body))
})
.map_err(|error| Err(error.error_response()))
})
}
There are three things that may fail:
Failing connection.
Non 200-status code.
Abrupt stop in body stream.
To handle 1, do not map_err to ():
.map_err(|err| match err {
SendRequestError::Connect(error) => {
ErrorBadGateway(format!("Unable to connect to httpbin: {}", error))
}
error => ErrorInternalServerError(error),
})
SendRequestError lists the errors that can occur when doing client requests.
To handle 2, make sure you use the status code from the client response:
.and_then(|response| Ok(HttpResponse::build(response.status()).streaming(response))))
actix-web handles 3 I believe.
Complete example, handling headers too:
use actix_web::client::{Client, SendRequestError};
use actix_web::error::{ErrorBadGateway, ErrorInternalServerError};
use actix_web::{web, App, Error, HttpResponse, HttpServer};
use futures::future::Future;
fn main() {
HttpServer::new(|| App::new().data(Client::new()).route("/", web::to(handler)))
.bind("127.0.0.1:8000")
.expect("Cannot bind to port 8000")
.run()
.expect("Unable to run server");
}
fn handler(client: web::Data<Client>) -> Box<Future<Item = HttpResponse, Error = Error>> {
Box::new(
client
.get("https://httpbin.org/get")
.no_decompress()
.send()
.map_err(|err| match err {
SendRequestError::Connect(error) => {
ErrorBadGateway(format!("Unable to connect to httpbin: {}", error))
}
error => ErrorInternalServerError(error),
})
.and_then(|response| {
let mut result = HttpResponse::build(response.status());
let headers = response
.headers()
.iter()
.filter(|(h, _)| *h != "connection" && *h != "content-length");
for (header_name, header_value) in headers {
result.header(header_name.clone(), header_value.clone());
}
Ok(result.streaming(response))
}),
)
}
Which actually failed:
$ curl -v localhost:8000
* Rebuilt URL to: localhost:8000/
* Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 8000 (#0)
> GET / HTTP/1.1
> Host: localhost:8000
> User-Agent: curl/7.54.0
> Accept: */*
>
< HTTP/1.1 502 Bad Gateway
< content-length: 50
< content-type: text/plain
< date: Sun, 07 Jul 2019 21:01:39 GMT
<
* Connection #0 to host localhost left intact
Unable to connect to httpbin: SSL is not supported
Add ssl as a feature in Cargo.toml to fix the connection error:
actix-web = { version = "1.0", features=["ssl"] }
Then try the request again:
$ curl -v localhost:8000
* Rebuilt URL to: localhost:8000/
* Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 8000 (#0)
> GET / HTTP/1.1
> Host: localhost:8000
> User-Agent: curl/7.54.0
> Accept: */*
>
< HTTP/1.1 200 OK
< transfer-encoding: chunked
< x-frame-options: DENY
< date: Sun, 07 Jul 2019 21:07:18 GMT
< content-type: application/json
< access-control-allow-origin: *
< access-control-allow-credentials: true
< server: nginx
< x-content-type-options: nosniff
< x-xss-protection: 1; mode=block
< referrer-policy: no-referrer-when-downgrade
<
{
"args": {},
"headers": {
"Date": "Sun, 07 Jul 2019 21:07:18 GMT",
"Host": "httpbin.org"
},
"origin": "212.251.175.90, 212.251.175.90",
"url": "https://httpbin.org/get"
}

Resources