blob: ac3e68dd3894538e779f796c48e8713a79081502 [file] [log] [blame]
// Copyright 2019 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use anyhow::Error;
use fidl::endpoints::ServerEnd;
use fidl_fuchsia_net_oldhttp as oldhttp;
use fuchsia_async as fasync;
use fuchsia_component::server::ServiceFs;
use fuchsia_hyper as fhyper;
use fuchsia_zircon as zx;
use futures::compat::Future01CompatExt;
use futures::prelude::*;
use hyper;
fn version_to_str(version: hyper::Version) -> &'static str {
match version {
hyper::Version::HTTP_09 => "0.9",
hyper::Version::HTTP_10 => "1.0",
hyper::Version::HTTP_11 => "1.1",
hyper::Version::HTTP_2 => "2.0",
}
}
fn to_status_line(version: hyper::Version, status: hyper::StatusCode) -> String {
match status.canonical_reason() {
None => format!("HTTP/{} {}", version_to_str(version), status.as_str()),
Some(canonical_reason) => {
format!("HTTP/{} {} {}", version_to_str(version), status.as_str(), canonical_reason)
}
}
}
async fn to_body(body_opt: Option<Box<oldhttp::UrlBody>>) -> Result<hyper::Body, zx::Status> {
if let Some(body) = body_opt {
match *body {
oldhttp::UrlBody::Stream(socket) => {
let stream = fasync::Socket::from_socket(socket)?
.into_datagram_stream()
.map_err(|status| Error::from(status));
Ok(hyper::Body::wrap_stream(futures::stream::TryStreamExt::compat(stream)))
}
oldhttp::UrlBody::Buffer(buffer) => {
let mut bytes = vec![0; buffer.size as usize];
buffer.vmo.read(&mut bytes, 0)?;
Ok(hyper::Body::from(bytes))
}
}
} else {
Ok(hyper::Body::empty())
}
}
fn to_success_response<B>(request_url: String, resp: hyper::Response<B>) -> oldhttp::UrlResponse {
let headers = resp
.headers()
.iter()
.map(|(name, value)| oldhttp::HttpHeader {
name: name.to_string(),
value: String::from_utf8_lossy(value.as_bytes()).to_string(),
})
.collect();
oldhttp::UrlResponse {
status_code: resp.status().as_u16() as u32,
// TODO: Actually return the body.
body: None,
url: Some(request_url),
error: None,
status_line: Some(to_status_line(resp.version(), resp.status())),
headers: Some(headers),
// TODO: Parse the MIME type from the Content-Type header. The C++
// implementation of oldhttp doesn't parse the MIME type either.
mime_type: None,
// TODO: Parse the charset from the Content-Type header. The C++
// implementation of oldhttp doesn't parse the charset either.
charset: None,
// TODO: Compute the redirect. The C++ implementation of oldhttp doesn't
// compute the redirect either.
redirect_method: None,
redirect_url: None,
redirect_referrer: None,
}
}
// These codes are supposed to match http_error_list.h, but it's unclear how
// to relate these two error domains.
fn to_error_code(error: &hyper::Error) -> i32 {
if error.is_parse() {
-320
} else if error.is_connect() {
-15
} else if error.is_canceled() {
-3
} else if error.is_closed() {
-3
} else {
-2
}
}
fn to_error_response(error: hyper::Error) -> oldhttp::UrlResponse {
oldhttp::UrlResponse {
status_code: 0,
body: None,
url: None,
error: Some(Box::new(oldhttp::HttpError {
code: to_error_code(&error),
description: Some(error.to_string()),
})),
status_line: None,
headers: None,
mime_type: None,
charset: None,
redirect_method: None,
redirect_url: None,
redirect_referrer: None,
}
}
fn spawn_old_url_loader(server: ServerEnd<oldhttp::UrlLoaderMarker>) {
fasync::spawn(
async move {
let client = fhyper::new_client();
let c = &client;
let stream = server.into_stream()?;
stream
.err_into()
.try_for_each_concurrent(None, |message| async move {
match message {
oldhttp::UrlLoaderRequest::Start { request, responder } => {
let mut builder = hyper::Request::builder();
builder.method(request.method.as_str());
builder.uri(&request.url);
if let Some(headers) = request.headers {
for header in &headers {
builder.header(header.name.as_str(), header.value.as_str());
}
}
let req = builder.body(to_body(request.body).await?)?;
let result = c.request(req).compat().await;
responder.send(&mut match result {
Ok(resp) => to_success_response(request.url, resp),
Err(error) => to_error_response(error),
})?;
}
// TODO: Implement FollowRedirect. The C++ implementation of oldhttp doesn't
// follow redirects either.
oldhttp::UrlLoaderRequest::FollowRedirect { responder: _ } => (),
oldhttp::UrlLoaderRequest::QueryStatus { responder } => {
// TODO: We should cache the error and report it here.
responder.send(&mut oldhttp::UrlLoaderStatus {
error: None,
is_loading: false,
})?;
}
};
Ok(())
})
.await
}
.unwrap_or_else(|e: anyhow::Error| eprintln!("{:?}", e)),
);
}
fn spawn_old_server(stream: oldhttp::HttpServiceRequestStream) {
fasync::spawn(
async move {
stream
.err_into()
.try_for_each_concurrent(None, |message| async move {
let oldhttp::HttpServiceRequest::CreateUrlLoader { loader, .. } = message;
spawn_old_url_loader(loader);
Ok(())
})
.await
}
.unwrap_or_else(|e: anyhow::Error| eprintln!("{:?}", e)),
);
}
#[fasync::run_singlethreaded]
async fn main() -> Result<(), Error> {
let mut fs = ServiceFs::new();
fs.dir("svc").add_fidl_service(spawn_old_server);
fs.take_and_serve_directory_handle()?;
let () = fs.collect().await;
Ok(())
}