Skip to content

Commit 8e7b590

Browse files
authored
Merge pull request #929 from jkczyz/2021-05-json-rpc-error
Parse RPC errors as JSON content
2 parents f074343 + b2f16ad commit 8e7b590

File tree

2 files changed

+57
-25
lines changed

2 files changed

+57
-25
lines changed

lightning-block-sync/src/http.rs

Lines changed: 41 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use chunked_transfer;
55
use serde_json;
66

77
use std::convert::TryFrom;
8+
use std::fmt;
89
#[cfg(not(feature = "tokio"))]
910
use std::io::Write;
1011
use std::net::ToSocketAddrs;
@@ -348,21 +349,33 @@ impl HttpClient {
348349

349350
if !status.is_ok() {
350351
// TODO: Handle 3xx redirection responses.
351-
let error_details = match String::from_utf8(contents) {
352-
// Check that the string is all-ASCII with no control characters before returning
353-
// it.
354-
Ok(s) if s.as_bytes().iter().all(|c| c.is_ascii() && !c.is_ascii_control()) => s,
355-
_ => "binary".to_string()
352+
let error = HttpError {
353+
status_code: status.code.to_string(),
354+
contents,
356355
};
357-
let error_msg = format!("Errored with status: {} and contents: {}",
358-
status.code, error_details);
359-
return Err(std::io::Error::new(std::io::ErrorKind::Other, error_msg));
356+
return Err(std::io::Error::new(std::io::ErrorKind::Other, error));
360357
}
361358

362359
Ok(contents)
363360
}
364361
}
365362

363+
/// HTTP error consisting of a status code and body contents.
364+
#[derive(Debug)]
365+
pub(crate) struct HttpError {
366+
pub(crate) status_code: String,
367+
pub(crate) contents: Vec<u8>,
368+
}
369+
370+
impl std::error::Error for HttpError {}
371+
372+
impl fmt::Display for HttpError {
373+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
374+
let contents = String::from_utf8_lossy(&self.contents);
375+
write!(f, "status_code: {}, contents: {}", self.status_code, contents)
376+
}
377+
}
378+
366379
/// HTTP response status code as defined by [RFC 7231].
367380
///
368381
/// [RFC 7231]: https://tools.ietf.org/html/rfc7231#section-6
@@ -538,16 +551,16 @@ pub(crate) mod client_tests {
538551
}
539552

540553
impl HttpServer {
541-
pub fn responding_with_ok<T: ToString>(body: MessageBody<T>) -> Self {
554+
fn responding_with_body<T: ToString>(status: &str, body: MessageBody<T>) -> Self {
542555
let response = match body {
543-
MessageBody::Empty => "HTTP/1.1 200 OK\r\n\r\n".to_string(),
556+
MessageBody::Empty => format!("{}\r\n\r\n", status),
544557
MessageBody::Content(body) => {
545558
let body = body.to_string();
546559
format!(
547-
"HTTP/1.1 200 OK\r\n\
560+
"{}\r\n\
548561
Content-Length: {}\r\n\
549562
\r\n\
550-
{}", body.len(), body)
563+
{}", status, body.len(), body)
551564
},
552565
MessageBody::ChunkedContent(body) => {
553566
let mut chuncked_body = Vec::new();
@@ -557,18 +570,26 @@ pub(crate) mod client_tests {
557570
encoder.write_all(body.to_string().as_bytes()).unwrap();
558571
}
559572
format!(
560-
"HTTP/1.1 200 OK\r\n\
573+
"{}\r\n\
561574
Transfer-Encoding: chunked\r\n\
562575
\r\n\
563-
{}", String::from_utf8(chuncked_body).unwrap())
576+
{}", status, String::from_utf8(chuncked_body).unwrap())
564577
},
565578
};
566579
HttpServer::responding_with(response)
567580
}
568581

582+
pub fn responding_with_ok<T: ToString>(body: MessageBody<T>) -> Self {
583+
HttpServer::responding_with_body("HTTP/1.1 200 OK", body)
584+
}
585+
569586
pub fn responding_with_not_found() -> Self {
570-
let response = "HTTP/1.1 404 Not Found\r\n\r\n".to_string();
571-
HttpServer::responding_with(response)
587+
HttpServer::responding_with_body::<String>("HTTP/1.1 404 Not Found", MessageBody::Empty)
588+
}
589+
590+
pub fn responding_with_server_error<T: ToString>(content: T) -> Self {
591+
let body = MessageBody::Content(content);
592+
HttpServer::responding_with_body("HTTP/1.1 500 Internal Server Error", body)
572593
}
573594

574595
fn responding_with(response: String) -> Self {
@@ -732,16 +753,15 @@ pub(crate) mod client_tests {
732753

733754
#[tokio::test]
734755
async fn read_error() {
735-
let response = String::from(
736-
"HTTP/1.1 500 Internal Server Error\r\n\
737-
Content-Length: 10\r\n\r\ntest error\r\n");
738-
let server = HttpServer::responding_with(response);
756+
let server = HttpServer::responding_with_server_error("foo");
739757

740758
let mut client = HttpClient::connect(&server.endpoint()).unwrap();
741759
match client.get::<JsonResponse>("/foo", "foo.com").await {
742760
Err(e) => {
743-
assert_eq!(e.get_ref().unwrap().to_string(), "Errored with status: 500 and contents: test error");
744761
assert_eq!(e.kind(), std::io::ErrorKind::Other);
762+
let http_error = e.into_inner().unwrap().downcast::<HttpError>().unwrap();
763+
assert_eq!(http_error.status_code, "500");
764+
assert_eq!(http_error.contents, "foo".as_bytes());
745765
},
746766
Ok(_) => panic!("Expected error"),
747767
}

lightning-block-sync/src/rpc.rs

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
//! endpoint.
33
44
use crate::{BlockHeaderData, BlockSource, AsyncBlockSourceResult};
5-
use crate::http::{HttpClient, HttpEndpoint, JsonResponse};
5+
use crate::http::{HttpClient, HttpEndpoint, HttpError, JsonResponse};
66

77
use bitcoin::blockdata::block::Block;
88
use bitcoin::hash_types::BlockHash;
@@ -47,8 +47,20 @@ impl RpcClient {
4747
"id": &self.id.fetch_add(1, Ordering::AcqRel).to_string()
4848
});
4949

50-
let mut response = self.client.post::<JsonResponse>(&uri, &host, &self.basic_auth, content)
51-
.await?.0;
50+
let mut response = match self.client.post::<JsonResponse>(&uri, &host, &self.basic_auth, content).await {
51+
Ok(JsonResponse(response)) => response,
52+
Err(e) if e.kind() == std::io::ErrorKind::Other => {
53+
match e.get_ref().unwrap().downcast_ref::<HttpError>() {
54+
Some(http_error) => match JsonResponse::try_from(http_error.contents.clone()) {
55+
Ok(JsonResponse(response)) => response,
56+
Err(_) => Err(e)?,
57+
},
58+
None => Err(e)?,
59+
}
60+
},
61+
Err(e) => Err(e)?,
62+
};
63+
5264
if !response.is_object() {
5365
return Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "expected JSON object"));
5466
}
@@ -143,7 +155,7 @@ mod tests {
143155
let response = serde_json::json!({
144156
"error": { "code": -8, "message": "invalid parameter" },
145157
});
146-
let server = HttpServer::responding_with_ok(MessageBody::Content(response));
158+
let server = HttpServer::responding_with_server_error(response);
147159
let mut client = RpcClient::new(CREDENTIALS, server.endpoint()).unwrap();
148160

149161
let invalid_block_hash = serde_json::json!("foo");

0 commit comments

Comments
 (0)