@@ -42,22 +42,26 @@ Each WebmachineResource defines all the callbacks (via Closures) and values requ
42
42
Note: This example uses the maplit crate to provide the `btreemap` macro and the log crate for the logging macros.
43
43
44
44
```no_run
45
+ use std::convert::Infallible;
46
+ use std::future::ready;
47
+ use std::io::Read;
48
+ use std::net::SocketAddr;
49
+ use std::sync::Arc;
50
+
45
51
use webmachine_rust::*;
46
52
use webmachine_rust::context::*;
47
53
use webmachine_rust::headers::*;
54
+
48
55
use bytes::Bytes;
49
- use serde_json::{Value, json} ;
50
- use std::io::Read ;
51
- use std::net::SocketAddr ;
52
- use std::convert::Infallible ;
53
- use std::sync::Arc ;
56
+ use futures_util::future::FutureExt ;
57
+ use hyper::{body, Request} ;
58
+ use hyper::server::conn::http1 ;
59
+ use hyper::service::service_fn ;
60
+ use hyper_util::rt::TokioIo ;
54
61
use maplit::btreemap;
62
+ use serde_json::{Value, json};
55
63
use tracing::error;
56
- use hyper_util::rt::TokioIo;
57
64
use tokio::net::TcpListener;
58
- use hyper::server::conn::http1;
59
- use hyper::service::service_fn;
60
- use hyper::{body, Request};
61
65
62
66
# fn main() {}
63
67
@@ -71,12 +75,12 @@ Note: This example uses the maplit crate to provide the `btreemap` macro and the
71
75
allowed_methods: owned_vec(&["OPTIONS", "GET", "HEAD", "POST"]),
72
76
// if the resource exists callback
73
77
resource_exists: callback(|_, _| true),
74
- // callback to render the response for the resource
75
- render_response: callback (|_, _| {
78
+ // callback to render the response for the resource, it has to be async
79
+ render_response: async_callback (|_, _| {
76
80
let json_response = json!({
77
81
"data": [1, 2, 3, 4]
78
82
});
79
- Some(Bytes::from(json_response.to_string()))
83
+ ready(Ok( Some(Bytes::from(json_response.to_string())))).boxed( )
80
84
}),
81
85
// callback to process the post for the resource
82
86
process_post: callback(|_, _| /* Handle the post here */ Ok(true) ),
@@ -114,9 +118,12 @@ For an example of a project using this crate, have a look at the [Pact Mock Serv
114
118
#![ warn( missing_docs) ]
115
119
116
120
use std:: collections:: { BTreeMap , HashMap } ;
121
+ use std:: future:: ready;
122
+ use std:: pin:: Pin ;
117
123
118
124
use bytes:: Bytes ;
119
125
use chrono:: { DateTime , FixedOffset , Utc } ;
126
+ use futures_util:: future:: FutureExt ;
120
127
use http:: { HeaderMap , Request , Response } ;
121
128
use http_body_util:: { BodyExt , Full } ;
122
129
use hyper:: body:: Incoming ;
@@ -141,6 +148,15 @@ pub fn callback<T, RT>(cb: T) -> WebmachineCallback<RT>
141
148
Box :: new ( cb)
142
149
}
143
150
151
+ /// Wrap an async callback in a structure that is safe to call between threads
152
+ pub type AsyncWebmachineCallback < T > = Pin < Box < dyn Fn ( & mut WebmachineContext , & WebmachineResource ) -> Pin < Box < dyn Future < Output =T > + Send > > + Send + Sync > > ;
153
+
154
+ /// Wrap a callback in a structure that is safe to call between threads
155
+ pub fn async_callback < T , RT > ( cb : T ) -> Pin < Box < T > >
156
+ where T : Fn ( & mut WebmachineContext , & WebmachineResource ) -> Pin < Box < dyn Future < Output =RT > + Send > > {
157
+ Box :: pin ( cb)
158
+ }
159
+
144
160
/// Convenience function to create a vector of string structs from a slice of strings
145
161
pub fn owned_vec ( strings : & [ & str ] ) -> Vec < String > {
146
162
strings. iter ( ) . map ( |s| s. to_string ( ) ) . collect ( )
@@ -152,7 +168,7 @@ pub struct WebmachineResource {
152
168
/// an opportunity to modify the response after the webmachine has executed.
153
169
pub finalise_response : Option < WebmachineCallback < ( ) > > ,
154
170
/// This is invoked to render the response for the resource
155
- pub render_response : WebmachineCallback < Option < Bytes > > ,
171
+ pub render_response : AsyncWebmachineCallback < anyhow :: Result < Option < Bytes > > > ,
156
172
/// Is the resource available? Returning false will result in a '503 Service Not Available'
157
173
/// response. Defaults to true. If the resource is only temporarily not available,
158
174
/// add a 'Retry-After' response header.
@@ -319,7 +335,7 @@ impl Default for WebmachineResource {
319
335
multiple_choices : callback ( false_fn) ,
320
336
create_path : callback ( |context, _| Ok ( context. request . request_path . clone ( ) ) ) ,
321
337
expires : callback ( none_fn) ,
322
- render_response : callback ( none_fn )
338
+ render_response : async_callback ( |_ , _| ready ( Ok ( None ) ) . boxed ( ) )
323
339
}
324
340
}
325
341
}
@@ -973,7 +989,7 @@ async fn request_from_http_request(req: Request<Incoming>) -> WebmachineRequest
973
989
}
974
990
}
975
991
976
- fn finalise_response ( context : & mut WebmachineContext , resource : & WebmachineResource ) {
992
+ async fn finalise_response ( context : & mut WebmachineContext , resource : & WebmachineResource ) {
977
993
if !context. response . has_header ( "Content-Type" ) {
978
994
let media_type = match & context. selected_media_type {
979
995
& Some ( ref media_type) => media_type. clone ( ) ,
@@ -1039,9 +1055,14 @@ fn finalise_response(context: &mut WebmachineContext, resource: &WebmachineResou
1039
1055
}
1040
1056
1041
1057
if context. response . body . is_none ( ) && context. response . status == 200 && context. request . is_get ( ) {
1042
- match ( resource. render_response ) ( context, resource) {
1043
- Some ( body) => context. response . body = Some ( body) ,
1044
- None => ( )
1058
+ match ( resource. render_response ) ( context, resource) . await {
1059
+ Ok ( Some ( body) ) => context. response . body = Some ( body) ,
1060
+ Ok ( None ) => ( ) ,
1061
+ Err ( err) => {
1062
+ error ! ( "render_response failed with an error: {}" , err) ;
1063
+ context. response . status = 500 ;
1064
+ context. response . body = Some ( Bytes :: from ( err. to_string ( ) ) ) ;
1065
+ }
1045
1066
}
1046
1067
}
1047
1068
@@ -1079,7 +1100,7 @@ impl WebmachineDispatcher {
1079
1100
/// based on the request path. If one is not found, a 404 Not Found response is returned
1080
1101
pub async fn dispatch ( & self , req : Request < Incoming > ) -> http:: Result < Response < Full < Bytes > > > {
1081
1102
let mut context = self . context_from_http_request ( req) . await ;
1082
- self . dispatch_to_resource ( & mut context) ;
1103
+ self . dispatch_to_resource ( & mut context) . await ;
1083
1104
generate_http_response ( & context)
1084
1105
}
1085
1106
@@ -1107,7 +1128,7 @@ impl WebmachineDispatcher {
1107
1128
1108
1129
/// Dispatches to the matching webmachine resource. If there is no matching resource, returns
1109
1130
/// 404 Not Found response
1110
- pub fn dispatch_to_resource ( & self , context : & mut WebmachineContext ) {
1131
+ pub async fn dispatch_to_resource ( & self , context : & mut WebmachineContext ) {
1111
1132
let matching_paths = self . match_paths ( & context. request ) ;
1112
1133
let ordered_by_length: Vec < String > = matching_paths. iter ( )
1113
1134
. cloned ( )
@@ -1117,7 +1138,7 @@ impl WebmachineDispatcher {
1117
1138
update_paths_for_resource ( & mut context. request , path) ;
1118
1139
if let Some ( resource) = self . lookup_resource ( path) {
1119
1140
execute_state_machine ( context, & resource) ;
1120
- finalise_response ( context, & resource) ;
1141
+ finalise_response ( context, & resource) . await ;
1121
1142
} else {
1122
1143
context. response . status = 404 ;
1123
1144
}
0 commit comments