11#![ deny( warnings) ]
22
33use {
4- anyhow:: { Error , Result } ,
4+ anyhow:: { anyhow, Error , Result } ,
5+ bytes:: Bytes ,
56 http:: { header:: HeaderName , request, HeaderValue } ,
67 once_cell:: unsync:: OnceCell ,
7- pyo3:: { exceptions:: PyAssertionError , types:: PyModule , PyErr , PyObject , PyResult , Python } ,
8+ pyo3:: {
9+ exceptions:: PyAssertionError ,
10+ types:: { PyBytes , PyModule } ,
11+ Py , PyErr , PyObject , PyResult , Python ,
12+ } ,
813 spin_sdk:: {
14+ config,
915 http:: { Request , Response } ,
10- outbound_http,
16+ outbound_http, redis ,
1117 } ,
1218 std:: { ops:: Deref , str} ,
1319} ;
@@ -16,6 +22,14 @@ thread_local! {
1622 static HANDLE_REQUEST : OnceCell <PyObject > = OnceCell :: new( ) ;
1723}
1824
25+ fn bytes ( py : Python < ' _ > , src : & [ u8 ] ) -> PyResult < Py < PyBytes > > {
26+ Ok ( PyBytes :: new_with ( py, src. len ( ) , |dst| {
27+ dst. copy_from_slice ( src) ;
28+ Ok ( ( ) )
29+ } ) ?
30+ . into ( ) )
31+ }
32+
1933#[ derive( Clone ) ]
2034#[ pyo3:: pyclass]
2135#[ pyo3( name = "Request" ) ]
@@ -26,9 +40,8 @@ struct HttpRequest {
2640 uri : String ,
2741 #[ pyo3( get, set) ]
2842 headers : Vec < ( String , String ) > ,
29- // todo: this should be a byte slice, but make sure it gets converted to/from Python correctly
3043 #[ pyo3( get, set) ]
31- body : Option < String > ,
44+ body : Option < Py < PyBytes > > ,
3245}
3346
3447#[ pyo3:: pymethods]
@@ -38,7 +51,7 @@ impl HttpRequest {
3851 method : String ,
3952 uri : String ,
4053 headers : Vec < ( String , String ) > ,
41- body : Option < String > ,
54+ body : Option < Py < PyBytes > > ,
4255 ) -> Self {
4356 Self {
4457 method,
@@ -57,15 +70,14 @@ struct HttpResponse {
5770 status : u16 ,
5871 #[ pyo3( get, set) ]
5972 headers : Vec < ( String , String ) > ,
60- // todo: this should be a byte slice, but make sure it gets converted to/from Python correctly
6173 #[ pyo3( get, set) ]
62- body : Option < String > ,
74+ body : Option < Py < PyBytes > > ,
6375}
6476
6577#[ pyo3:: pymethods]
6678impl HttpResponse {
6779 #[ new]
68- fn new ( status : u16 , headers : Vec < ( String , String ) > , body : Option < String > ) -> Self {
80+ fn new ( status : u16 , headers : Vec < ( String , String ) > , body : Option < Py < PyBytes > > ) -> Self {
6981 Self {
7082 status,
7183 headers,
@@ -89,23 +101,31 @@ impl<T: std::error::Error + Send + Sync + 'static> From<T> for Anyhow {
89101}
90102
91103#[ pyo3:: pyfunction]
92- fn send ( request : HttpRequest ) -> Result < HttpResponse , Anyhow > {
104+ #[ pyo3( pass_module) ]
105+ fn http_send ( module : & PyModule , request : HttpRequest ) -> PyResult < HttpResponse > {
93106 let mut builder = request:: Builder :: new ( )
94107 . method ( request. method . deref ( ) )
95108 . uri ( request. uri . deref ( ) ) ;
96109
97110 if let Some ( headers) = builder. headers_mut ( ) {
98111 for ( key, value) in & request. headers {
99112 headers. insert (
100- HeaderName :: from_bytes ( key. as_bytes ( ) ) ?,
101- HeaderValue :: from_bytes ( value. as_bytes ( ) ) ?,
113+ HeaderName :: from_bytes ( key. as_bytes ( ) ) . map_err ( Anyhow :: from ) ?,
114+ HeaderValue :: from_bytes ( value. as_bytes ( ) ) . map_err ( Anyhow :: from ) ?,
102115 ) ;
103116 }
104117 }
105118
106119 let response = outbound_http:: send_request (
107- builder. body ( request. body . map ( |buffer| buffer. into_bytes ( ) . into ( ) ) ) ?,
108- ) ?;
120+ builder
121+ . body (
122+ request
123+ . body
124+ . map ( |buffer| Bytes :: copy_from_slice ( buffer. as_bytes ( module. py ( ) ) ) ) ,
125+ )
126+ . map_err ( Anyhow :: from) ?,
127+ )
128+ . map_err ( Anyhow :: from) ?;
109129
110130 Ok ( HttpResponse {
111131 status : response. status ( ) . as_u16 ( ) ,
@@ -115,26 +135,66 @@ fn send(request: HttpRequest) -> Result<HttpResponse, Anyhow> {
115135 . map ( |( key, value) | {
116136 Ok ( (
117137 key. as_str ( ) . to_owned ( ) ,
118- str:: from_utf8 ( value. as_bytes ( ) ) ?. to_owned ( ) ,
138+ str:: from_utf8 ( value. as_bytes ( ) )
139+ . map_err ( Anyhow :: from) ?
140+ . to_owned ( ) ,
119141 ) )
120142 } )
121- . collect :: < Result < _ , Anyhow > > ( ) ?,
143+ . collect :: < PyResult < _ > > ( ) ?,
122144 body : response
123145 . into_body ( )
124- . map ( |bytes| String :: from_utf8_lossy ( & bytes) . into_owned ( ) ) ,
146+ . as_deref ( )
147+ . map ( |buffer| bytes ( module. py ( ) , buffer) )
148+ . transpose ( ) ?,
125149 } )
126150}
127151
128152#[ pyo3:: pymodule]
129153#[ pyo3( name = "spin_http" ) ]
130154fn spin_http_module ( _py : Python < ' _ > , module : & PyModule ) -> PyResult < ( ) > {
131- module. add_function ( pyo3:: wrap_pyfunction!( send , module) ?) ?;
155+ module. add_function ( pyo3:: wrap_pyfunction!( http_send , module) ?) ?;
132156 module. add_class :: < HttpRequest > ( ) ?;
133157 module. add_class :: < HttpResponse > ( )
134158}
135159
160+ #[ pyo3:: pyfunction]
161+ #[ pyo3( pass_module) ]
162+ fn redis_get ( module : & PyModule , address : String , key : String ) -> PyResult < Py < PyBytes > > {
163+ bytes (
164+ module. py ( ) ,
165+ & redis:: get ( & address, & key)
166+ . map_err ( |_| Anyhow ( anyhow ! ( "Error executing Redis get command" ) ) ) ?,
167+ )
168+ }
169+
170+ #[ pyo3:: pyfunction]
171+ fn redis_set ( address : String , key : String , value : & PyBytes ) -> PyResult < ( ) > {
172+ redis:: set ( & address, & key, value. as_bytes ( ) )
173+ . map_err ( |_| PyErr :: from ( Anyhow ( anyhow ! ( "Error executing Redis set command" ) ) ) )
174+ }
175+
176+ #[ pyo3:: pymodule]
177+ #[ pyo3( name = "spin_redis" ) ]
178+ fn spin_redis_module ( _py : Python < ' _ > , module : & PyModule ) -> PyResult < ( ) > {
179+ module. add_function ( pyo3:: wrap_pyfunction!( redis_get, module) ?) ?;
180+ module. add_function ( pyo3:: wrap_pyfunction!( redis_set, module) ?)
181+ }
182+
183+ #[ pyo3:: pyfunction]
184+ fn config_get ( key : String ) -> Result < String , Anyhow > {
185+ config:: get ( & key) . map_err ( Anyhow :: from)
186+ }
187+
188+ #[ pyo3:: pymodule]
189+ #[ pyo3( name = "spin_config" ) ]
190+ fn spin_config_module ( _py : Python < ' _ > , module : & PyModule ) -> PyResult < ( ) > {
191+ module. add_function ( pyo3:: wrap_pyfunction!( config_get, module) ?)
192+ }
193+
136194fn do_init ( ) -> Result < ( ) > {
137195 pyo3:: append_to_inittab!( spin_http_module) ;
196+ pyo3:: append_to_inittab!( spin_redis_module) ;
197+ pyo3:: append_to_inittab!( spin_config_module) ;
138198
139199 pyo3:: prepare_freethreaded_python ( ) ;
140200
@@ -157,45 +217,57 @@ pub extern "C" fn init() {
157217
158218#[ spin_sdk:: http_component]
159219fn handle ( request : Request ) -> Result < Response > {
160- let uri = request. uri ( ) . to_string ( ) ;
161- let request = HttpRequest {
162- method : request. method ( ) . as_str ( ) . to_owned ( ) ,
163- uri,
164- headers : request
165- . headers ( )
166- . iter ( )
167- . map ( |( k, v) | {
168- Ok ( (
169- k. as_str ( ) . to_owned ( ) ,
170- str:: from_utf8 ( v. as_bytes ( ) ) ?. to_owned ( ) ,
171- ) )
172- } )
173- . collect :: < Result < _ > > ( ) ?,
174- body : request
175- . body ( )
176- . as_ref ( )
177- . map ( |bytes| Ok :: < _ , Error > ( str:: from_utf8 ( bytes) ?. to_owned ( ) ) )
178- . transpose ( ) ?,
179- } ;
220+ Python :: with_gil ( |py| {
221+ let uri = request. uri ( ) . to_string ( ) ;
180222
181- let response = Python :: with_gil ( |py| {
182- HANDLE_REQUEST . with ( |cell| {
223+ let request = HttpRequest {
224+ method : request. method ( ) . as_str ( ) . to_owned ( ) ,
225+ uri,
226+ headers : request
227+ . headers ( )
228+ . iter ( )
229+ . map ( |( k, v) | {
230+ Ok ( (
231+ k. as_str ( ) . to_owned ( ) ,
232+ str:: from_utf8 ( v. as_bytes ( ) )
233+ . map_err ( Anyhow :: from) ?
234+ . to_owned ( ) ,
235+ ) )
236+ } )
237+ . collect :: < PyResult < _ > > ( ) ?,
238+ body : request
239+ . body ( )
240+ . as_deref ( )
241+ . map ( |buffer| bytes ( py, buffer) )
242+ . transpose ( ) ?,
243+ } ;
244+
245+ let response = HANDLE_REQUEST . with ( |cell| {
183246 cell. get ( )
184247 . unwrap ( )
185248 . call1 ( py, ( request, ) ) ?
186249 . extract :: < HttpResponse > ( py)
187- } )
188- } ) ?;
250+ } ) ?;
189251
190- let mut builder = http:: Response :: builder ( ) . status ( response. status ) ;
191- if let Some ( headers) = builder. headers_mut ( ) {
192- for ( key, value) in & response. headers {
193- headers. insert (
194- HeaderName :: try_from ( key. deref ( ) ) ?,
195- HeaderValue :: from_bytes ( value. as_bytes ( ) ) ?,
196- ) ;
252+ let mut builder = http:: Response :: builder ( ) . status ( response. status ) ;
253+ if let Some ( headers) = builder. headers_mut ( ) {
254+ for ( key, value) in & response. headers {
255+ headers. insert (
256+ HeaderName :: try_from ( key. deref ( ) ) . map_err ( Anyhow :: from) ?,
257+ HeaderValue :: from_bytes ( value. as_bytes ( ) ) . map_err ( Anyhow :: from) ?,
258+ ) ;
259+ }
197260 }
198- }
199261
200- Ok ( builder. body ( response. body . map ( |buffer| buffer. into ( ) ) ) ?)
262+ Ok :: < _ , PyErr > (
263+ builder
264+ . body (
265+ response
266+ . body
267+ . map ( |buffer| Bytes :: copy_from_slice ( buffer. as_bytes ( py) ) ) ,
268+ )
269+ . map_err ( Anyhow :: from) ?,
270+ )
271+ } )
272+ . map_err ( Error :: from)
201273}
0 commit comments