22
33use {
44 anyhow:: { Error , Result } ,
5- http:: { header:: HeaderName , HeaderValue } ,
5+ http:: { header:: HeaderName , request , HeaderValue } ,
66 once_cell:: unsync:: OnceCell ,
7- pyo3:: { types:: PyModule , PyObject , PyResult , Python } ,
8- spin_sdk:: http:: { Request , Response } ,
7+ pyo3:: { exceptions:: PyAssertionError , types:: PyModule , PyErr , PyObject , PyResult , Python } ,
8+ spin_sdk:: {
9+ http:: { Request , Response } ,
10+ outbound_http,
11+ } ,
912 std:: { ops:: Deref , str} ,
1013} ;
1114
1215thread_local ! {
1316 static HANDLE_REQUEST : OnceCell <PyObject > = OnceCell :: new( ) ;
1417}
1518
19+ #[ derive( Clone ) ]
1620#[ pyo3:: pyclass]
1721#[ pyo3( name = "Request" ) ]
1822struct HttpRequest {
19- #[ pyo3( get) ]
23+ #[ pyo3( get, set ) ]
2024 method : String ,
21- #[ pyo3( get) ]
25+ #[ pyo3( get, set ) ]
2226 uri : String ,
23- #[ pyo3( get) ]
27+ #[ pyo3( get, set ) ]
2428 headers : Vec < ( String , String ) > ,
2529 // todo: this should be a byte slice, but make sure it gets converted to/from Python correctly
26- #[ pyo3( get) ]
30+ #[ pyo3( get, set ) ]
2731 body : Option < String > ,
2832}
2933
34+ #[ pyo3:: pymethods]
35+ impl HttpRequest {
36+ #[ new]
37+ fn new (
38+ method : String ,
39+ uri : String ,
40+ headers : Vec < ( String , String ) > ,
41+ body : Option < String > ,
42+ ) -> Self {
43+ Self {
44+ method,
45+ uri,
46+ headers,
47+ body,
48+ }
49+ }
50+ }
51+
3052#[ derive( Clone ) ]
3153#[ pyo3:: pyclass]
3254#[ pyo3( name = "Response" ) ]
3355struct HttpResponse {
56+ #[ pyo3( get, set) ]
3457 status : u16 ,
58+ #[ pyo3( get, set) ]
3559 headers : Vec < ( String , String ) > ,
3660 // todo: this should be a byte slice, but make sure it gets converted to/from Python correctly
61+ #[ pyo3( get, set) ]
3762 body : Option < String > ,
3863}
3964
@@ -49,9 +74,61 @@ impl HttpResponse {
4974 }
5075}
5176
77+ struct Anyhow ( Error ) ;
78+
79+ impl From < Anyhow > for PyErr {
80+ fn from ( Anyhow ( error) : Anyhow ) -> Self {
81+ PyAssertionError :: new_err ( format ! ( "{error:?}" ) )
82+ }
83+ }
84+
85+ impl < T : std:: error:: Error + Send + Sync + ' static > From < T > for Anyhow {
86+ fn from ( error : T ) -> Self {
87+ Self ( error. into ( ) )
88+ }
89+ }
90+
91+ #[ pyo3:: pyfunction]
92+ fn send ( request : HttpRequest ) -> Result < HttpResponse , Anyhow > {
93+ let mut builder = request:: Builder :: new ( )
94+ . method ( request. method . deref ( ) )
95+ . uri ( request. uri . deref ( ) ) ;
96+
97+ if let Some ( headers) = builder. headers_mut ( ) {
98+ for ( key, value) in & request. headers {
99+ headers. insert (
100+ HeaderName :: from_bytes ( key. as_bytes ( ) ) ?,
101+ HeaderValue :: from_bytes ( value. as_bytes ( ) ) ?,
102+ ) ;
103+ }
104+ }
105+
106+ let response = outbound_http:: send_request (
107+ builder. body ( request. body . map ( |buffer| buffer. into_bytes ( ) . into ( ) ) ) ?,
108+ ) ?;
109+
110+ Ok ( HttpResponse {
111+ status : response. status ( ) . as_u16 ( ) ,
112+ headers : response
113+ . headers ( )
114+ . iter ( )
115+ . map ( |( key, value) | {
116+ Ok ( (
117+ key. as_str ( ) . to_owned ( ) ,
118+ str:: from_utf8 ( value. as_bytes ( ) ) ?. to_owned ( ) ,
119+ ) )
120+ } )
121+ . collect :: < Result < _ , Anyhow > > ( ) ?,
122+ body : response
123+ . into_body ( )
124+ . map ( |bytes| String :: from_utf8_lossy ( & bytes) . into_owned ( ) ) ,
125+ } )
126+ }
127+
52128#[ pyo3:: pymodule]
53129#[ pyo3( name = "spin_http" ) ]
54130fn spin_http_module ( _py : Python < ' _ > , module : & PyModule ) -> PyResult < ( ) > {
131+ module. add_function ( pyo3:: wrap_pyfunction!( send, module) ?) ?;
55132 module. add_class :: < HttpRequest > ( ) ?;
56133 module. add_class :: < HttpResponse > ( )
57134}
0 commit comments