Uses http-types complex header support

pull/76/head
Tpt 4 years ago
parent fc977d56f4
commit 8f8bb95669
  1. 73
      server/src/main.rs
  2. 181
      wikibase/src/main.rs

@ -15,6 +15,7 @@ use async_std::io::Read;
use async_std::net::{TcpListener, TcpStream}; use async_std::net::{TcpListener, TcpStream};
use async_std::prelude::*; use async_std::prelude::*;
use async_std::task::{block_on, spawn}; use async_std::task::{block_on, spawn};
use http_types::content::{Accept, ContentType};
use http_types::{ use http_types::{
bail_status, headers, Error, Method, Mime, Request, Response, Result, StatusCode, bail_status, headers, Error, Method, Mime, Request, Response, Result, StatusCode,
}; };
@ -61,16 +62,16 @@ pub async fn main() -> Result<()> {
} }
async fn handle_request(request: Request, store: Store) -> Result<Response> { async fn handle_request(request: Request, store: Store) -> Result<Response> {
let mut response = match (request.url().path(), request.method()) { Ok(match (request.url().path(), request.method()) {
("/", Method::Get) => { ("/", Method::Get) => {
let mut response = Response::new(StatusCode::Ok); let mut response = Response::new(StatusCode::Ok);
response.append_header(headers::CONTENT_TYPE, "text/html"); ContentType::new("text/html").apply(&mut response);
response.set_body(HTML_ROOT_PAGE); response.set_body(HTML_ROOT_PAGE);
response response
} }
("/logo.svg", Method::Get) => { ("/logo.svg", Method::Get) => {
let mut response = Response::new(StatusCode::Ok); let mut response = Response::new(StatusCode::Ok);
response.append_header(headers::CONTENT_TYPE, "image/svg+xml"); ContentType::new("image/svg+xml").apply(&mut response);
response.set_body(LOGO); response.set_body(LOGO);
response response
} }
@ -161,7 +162,7 @@ async fn handle_request(request: Request, store: Store) -> Result<Response> {
format.media_type() format.media_type()
}; };
let mut response = Response::from(body); let mut response = Response::from(body);
response.insert_header(headers::CONTENT_TYPE, format); ContentType::new(format).apply(&mut response);
response response
} }
(path, Method::Put) if path.starts_with("/store") => { (path, Method::Put) if path.starts_with("/store") => {
@ -337,9 +338,7 @@ async fn handle_request(request: Request, store: Store) -> Result<Response> {
request.method(), request.method(),
request.url().path() request.url().path()
), ),
}; })
response.append_header(headers::SERVER, SERVER);
Ok(response)
} }
fn base_url(request: &Request) -> Result<Url> { fn base_url(request: &Request) -> Result<Url> {
@ -418,7 +417,7 @@ fn evaluate_sparql_query(
let mut body = Vec::default(); let mut body = Vec::default();
results.write_graph(&mut body, format)?; results.write_graph(&mut body, format)?;
let mut response = Response::from(body); let mut response = Response::from(body);
response.insert_header(headers::CONTENT_TYPE, format.media_type()); ContentType::new(format.media_type()).apply(&mut response);
Ok(response) Ok(response)
} else { } else {
let format = content_negotiation( let format = content_negotiation(
@ -434,7 +433,7 @@ fn evaluate_sparql_query(
let mut body = Vec::default(); let mut body = Vec::default();
results.write(&mut body, format)?; results.write(&mut body, format)?;
let mut response = Response::from(body); let mut response = Response::from(body);
response.insert_header(headers::CONTENT_TYPE, format.media_type()); ContentType::new(format.media_type()).apply(&mut response);
Ok(response) Ok(response)
} }
} }
@ -556,7 +555,7 @@ async fn http_server<
handle: F, handle: F,
) -> Result<()> { ) -> Result<()> {
async_h1::accept(stream, |request| async { async_h1::accept(stream, |request| async {
Ok(match handle(request).await { let mut response = match handle(request).await {
Ok(result) => result, Ok(result) => result,
Err(error) => { Err(error) => {
if error.status().is_server_error() { if error.status().is_server_error() {
@ -566,7 +565,9 @@ async fn http_server<
response.set_body(error.to_string()); response.set_body(error.to_string());
response response
} }
}) };
response.append_header(headers::SERVER, SERVER);
Ok(response)
}) })
.await .await
} }
@ -613,43 +614,21 @@ fn content_negotiation<F>(
supported: &[&str], supported: &[&str],
parse: impl Fn(&str) -> Option<F>, parse: impl Fn(&str) -> Option<F>,
) -> Result<F> { ) -> Result<F> {
let header = request if let Some(mut accept) = Accept::from_headers(request)? {
.header(headers::ACCEPT) let supported: Vec<Mime> = supported
.map(|h| h.last().as_str().trim()) .iter()
.unwrap_or(""); .map(|h| Mime::from_str(h).unwrap())
let supported: Vec<Mime> = supported .collect();
.iter() parse(accept.negotiate(&supported)?.value().as_str())
.map(|h| Mime::from_str(h).unwrap()) } else {
.collect(); parse(supported.first().ok_or_else(|| {
Error::from_str(
let mut result = supported.first().unwrap(); StatusCode::InternalServerError,
let mut result_score = 0f32; "No default MIME type provided",
)
if !header.is_empty() { })?)
for possible in header.split(',') {
let possible = Mime::from_str(possible.trim())?;
let score = if let Some(q) = possible.param("q") {
f32::from_str(&q.to_string())?
} else {
1.
};
if score <= result_score {
continue;
}
for candidate in &supported {
if (possible.basetype() == candidate.basetype() || possible.basetype() == "*")
&& (possible.subtype() == candidate.subtype() || possible.subtype() == "*")
{
result = candidate;
result_score = score;
break;
}
}
}
} }
.ok_or_else(|| Error::from_str(StatusCode::InternalServerError, "Unknown mime type"))
parse(result.essence())
.ok_or_else(|| Error::from_str(StatusCode::InternalServerError, "Unknown mime type"))
} }
fn bad_request(e: impl Into<Error>) -> Error { fn bad_request(e: impl Into<Error>) -> Error {

@ -15,8 +15,12 @@ use async_std::future::Future;
use async_std::net::{TcpListener, TcpStream}; use async_std::net::{TcpListener, TcpStream};
use async_std::prelude::*; use async_std::prelude::*;
use async_std::task::spawn; use async_std::task::spawn;
use http_types::{headers, Body, Error, Method, Mime, Request, Response, Result, StatusCode}; use http_types::content::{Accept, ContentType};
use http_types::{
bail_status, headers, Error, Method, Mime, Request, Response, Result, StatusCode,
};
use oxigraph::io::GraphFormat; use oxigraph::io::GraphFormat;
use oxigraph::model::{GraphName, NamedNode, NamedOrBlankNode};
use oxigraph::sparql::{Query, QueryResults, QueryResultsFormat}; use oxigraph::sparql::{Query, QueryResults, QueryResultsFormat};
use oxigraph::RocksDbStore; use oxigraph::RocksDbStore;
use std::str::FromStr; use std::str::FromStr;
@ -102,14 +106,9 @@ pub async fn main() -> Result<()> {
} }
async fn handle_request(request: Request, store: RocksDbStore) -> Result<Response> { async fn handle_request(request: Request, store: RocksDbStore) -> Result<Response> {
let mut response = match (request.url().path(), request.method()) { Ok(match (request.url().path(), request.method()) {
("/query", Method::Get) => { ("/query", Method::Get) => {
evaluate_urlencoded_sparql_query( configure_and_evaluate_sparql_query(store, url_query(&request), None, request)?
store,
request.url().query().unwrap_or("").as_bytes().to_vec(),
request,
)
.await?
} }
("/query", Method::Post) => { ("/query", Method::Post) => {
if let Some(content_type) = request.content_type() { if let Some(content_type) = request.content_type() {
@ -121,7 +120,12 @@ async fn handle_request(request: Request, store: RocksDbStore) -> Result<Respons
.take(MAX_SPARQL_BODY_SIZE) .take(MAX_SPARQL_BODY_SIZE)
.read_to_string(&mut buffer) .read_to_string(&mut buffer)
.await?; .await?;
evaluate_sparql_query(store, buffer, request).await? configure_and_evaluate_sparql_query(
store,
url_query(&request),
Some(buffer),
request,
)?
} else if content_type.essence() == "application/x-www-form-urlencoded" { } else if content_type.essence() == "application/x-www-form-urlencoded" {
let mut buffer = Vec::new(); let mut buffer = Vec::new();
let mut request = request; let mut request = request;
@ -130,59 +134,83 @@ async fn handle_request(request: Request, store: RocksDbStore) -> Result<Respons
.take(MAX_SPARQL_BODY_SIZE) .take(MAX_SPARQL_BODY_SIZE)
.read_to_end(&mut buffer) .read_to_end(&mut buffer)
.await?; .await?;
evaluate_urlencoded_sparql_query(store, buffer, request).await? configure_and_evaluate_sparql_query(store, buffer, None, request)?
} else { } else {
simple_response( bail_status!(415, "Not supported Content-Type given: {}", content_type)
StatusCode::UnsupportedMediaType,
format!("No supported Content-Type given: {}", content_type),
)
} }
} else { } else {
simple_response(StatusCode::BadRequest, "No Content-Type given") bail_status!(400, "No Content-Type given");
} }
} }
_ => Response::new(StatusCode::NotFound), _ => bail_status!(
}; 404,
response.append_header("Server", SERVER); "{} {} is not supported by this server",
Ok(response) request.method(),
request.url().path()
),
})
} }
fn simple_response(status: StatusCode, body: impl Into<Body>) -> Response { fn url_query(request: &Request) -> Vec<u8> {
let mut response = Response::new(status); request.url().query().unwrap_or("").as_bytes().to_vec()
response.set_body(body);
response
} }
async fn evaluate_urlencoded_sparql_query( fn configure_and_evaluate_sparql_query(
store: RocksDbStore, store: RocksDbStore,
encoded: Vec<u8>, encoded: Vec<u8>,
mut query: Option<String>,
request: Request, request: Request,
) -> Result<Response> { ) -> Result<Response> {
if let Some((_, query)) = form_urlencoded::parse(&encoded).find(|(k, _)| k == "query") { let mut default_graph_uris = Vec::new();
evaluate_sparql_query(store, query.to_string(), request).await let mut named_graph_uris = Vec::new();
for (k, v) in form_urlencoded::parse(&encoded) {
match k.as_ref() {
"query" => {
if query.is_some() {
bail_status!(400, "Multiple query parameters provided")
}
query = Some(v.into_owned())
}
"default-graph-uri" => default_graph_uris.push(v.into_owned()),
"named-graph-uri" => named_graph_uris.push(v.into_owned()),
_ => bail_status!(400, "Unexpected parameter: {}", k),
}
}
if let Some(query) = query {
evaluate_sparql_query(store, query, default_graph_uris, named_graph_uris, request)
} else { } else {
Ok(simple_response( bail_status!(400, "You should set the 'query' parameter")
StatusCode::BadRequest,
"You should set the 'query' parameter",
))
} }
} }
async fn evaluate_sparql_query( fn evaluate_sparql_query(
store: RocksDbStore, store: RocksDbStore,
query: String, query: String,
default_graph_uris: Vec<String>,
named_graph_uris: Vec<String>,
request: Request, request: Request,
) -> Result<Response> { ) -> Result<Response> {
//TODO: stream let mut query = Query::parse(&query, None).map_err(bad_request)?;
let mut query = Query::parse(&query, None).map_err(|e| { let default_graph_uris = default_graph_uris
let mut e = Error::from(e); .into_iter()
e.set_status(StatusCode::BadRequest); .map(|e| Ok(NamedNode::new(e)?.into()))
e .collect::<Result<Vec<GraphName>>>()
})?; .map_err(bad_request)?;
if query.dataset().is_default_dataset() { let named_graph_uris = named_graph_uris
query.dataset_mut().set_default_graph_as_union(); .into_iter()
.map(|e| Ok(NamedNode::new(e)?.into()))
.collect::<Result<Vec<NamedOrBlankNode>>>()
.map_err(bad_request)?;
if !default_graph_uris.is_empty() || !named_graph_uris.is_empty() {
query.dataset_mut().set_default_graph(default_graph_uris);
query
.dataset_mut()
.set_available_named_graphs(named_graph_uris);
} }
let results = store.query(query)?; let results = store.query(query)?;
//TODO: stream
if let QueryResults::Graph(_) = results { if let QueryResults::Graph(_) = results {
let format = content_negotiation( let format = content_negotiation(
request, request,
@ -196,7 +224,7 @@ async fn evaluate_sparql_query(
let mut body = Vec::default(); let mut body = Vec::default();
results.write_graph(&mut body, format)?; results.write_graph(&mut body, format)?;
let mut response = Response::from(body); let mut response = Response::from(body);
response.insert_header(headers::CONTENT_TYPE, format.media_type()); ContentType::new(format.media_type()).apply(&mut response);
Ok(response) Ok(response)
} else { } else {
let format = content_negotiation( let format = content_negotiation(
@ -212,7 +240,7 @@ async fn evaluate_sparql_query(
let mut body = Vec::default(); let mut body = Vec::default();
results.write(&mut body, format)?; results.write(&mut body, format)?;
let mut response = Response::from(body); let mut response = Response::from(body);
response.insert_header(headers::CONTENT_TYPE, format.media_type()); ContentType::new(format.media_type()).apply(&mut response);
Ok(response) Ok(response)
} }
} }
@ -229,10 +257,19 @@ async fn http_server<
handle: F, handle: F,
) -> Result<()> { ) -> Result<()> {
async_h1::accept(stream, |request| async { async_h1::accept(stream, |request| async {
Ok(match handle(request).await { let mut response = match handle(request).await {
Ok(result) => result, Ok(result) => result,
Err(error) => simple_response(error.status(), error.to_string()), Err(error) => {
}) if error.status().is_server_error() {
eprintln!("{}", error);
}
let mut response = Response::new(error.status());
response.set_body(error.to_string());
response
}
};
response.append_header(headers::SERVER, SERVER);
Ok(response)
}) })
.await .await
} }
@ -251,46 +288,30 @@ async fn http_server<
Ok(()) Ok(())
} }
fn bad_request(e: impl Into<Error>) -> Error {
let mut e = e.into();
e.set_status(StatusCode::BadRequest);
e
}
fn content_negotiation<F>( fn content_negotiation<F>(
request: Request, request: Request,
supported: &[&str], supported: &[&str],
parse: impl Fn(&str) -> Option<F>, parse: impl Fn(&str) -> Option<F>,
) -> Result<F> { ) -> Result<F> {
let header = request if let Some(mut accept) = Accept::from_headers(request)? {
.header(headers::ACCEPT) let supported: Vec<Mime> = supported
.map(|h| h.last().as_str().trim()) .iter()
.unwrap_or(""); .map(|h| Mime::from_str(h).unwrap())
let supported: Vec<Mime> = supported .collect();
.iter() parse(accept.negotiate(&supported)?.value().as_str())
.map(|h| Mime::from_str(h).unwrap()) } else {
.collect(); parse(supported.first().ok_or_else(|| {
Error::from_str(
let mut result = supported.first().unwrap(); StatusCode::InternalServerError,
let mut result_score = 0f32; "No default MIME type provided",
)
if !header.is_empty() { })?)
for possible in header.split(',') {
let possible = Mime::from_str(possible.trim())?;
let score = if let Some(q) = possible.param("q") {
f32::from_str(&q.to_string())?
} else {
1.
};
if score <= result_score {
continue;
}
for candidate in &supported {
if (possible.basetype() == candidate.basetype() || possible.basetype() == "*")
&& (possible.subtype() == candidate.subtype() || possible.subtype() == "*")
{
result = candidate;
result_score = score;
break;
}
}
}
} }
.ok_or_else(|| Error::from_str(StatusCode::InternalServerError, "Unknown mime type"))
parse(result.essence())
.ok_or_else(|| Error::from_str(StatusCode::InternalServerError, "Unknown mime type"))
} }

Loading…
Cancel
Save