diff --git a/nyash.toml b/nyash.toml index 08f0e163..82515b76 100644 --- a/nyash.toml +++ b/nyash.toml @@ -90,6 +90,8 @@ setStatus = { method_id = 1 } setHeader = { method_id = 2 } write = { method_id = 3 } readBody = { method_id = 4 } +getStatus = { method_id = 5 } +getHeader = { method_id = 6, args = ["name"] } fini = { method_id = 4294967295 } [libraries."libnyash_net_plugin.so".HttpClientBox] @@ -98,4 +100,5 @@ type_id = 23 [libraries."libnyash_net_plugin.so".HttpClientBox.methods] birth = { method_id = 0 } get = { method_id = 1 } +post = { method_id = 2 } fini = { method_id = 4294967295 } diff --git a/plugins/nyash-net-plugin/src/lib.rs b/plugins/nyash-net-plugin/src/lib.rs index c3184fd7..c1653fee 100644 --- a/plugins/nyash-net-plugin/src/lib.rs +++ b/plugins/nyash-net-plugin/src/lib.rs @@ -39,9 +39,12 @@ const M_RESP_SET_STATUS: u32 = 1; // arg: i32 const M_RESP_SET_HEADER: u32 = 2; // args: name, value (string) const M_RESP_WRITE: u32 = 3; // arg: bytes/string const M_RESP_READ_BODY: u32 = 4; // -> Bytes +const M_RESP_GET_STATUS: u32 = 5; // -> i32 +const M_RESP_GET_HEADER: u32 = 6; // arg: name -> string (or empty) // Client -const M_CLIENT_GET: u32 = 1; // arg: url -> Handle(Response) +const M_CLIENT_GET: u32 = 1; // arg: url -> Handle(Response) +const M_CLIENT_POST: u32 = 2; // args: url, body(bytes/string) -> Handle(Response) // Global State static SERVER_INSTANCES: Lazy>> = Lazy::new(|| Mutex::new(HashMap::new())); @@ -216,6 +219,18 @@ unsafe fn response_invoke(m: u32, id: u32, args: *const u8, args_len: usize, res M_RESP_READ_BODY => { if let Some(rp) = RESPONSES.lock().unwrap().get(&id) { write_tlv_bytes(&rp.body, res, res_len) } else { E_INV_HANDLE } } + M_RESP_GET_STATUS => { + if let Some(rp) = RESPONSES.lock().unwrap().get(&id) { write_tlv_i32(rp.status, res, res_len) } else { E_INV_HANDLE } + } + M_RESP_GET_HEADER => { + if let Ok(name) = tlv_parse_string(slice(args, args_len)) { + if let Some(rp) = RESPONSES.lock().unwrap().get(&id) { + let v = rp.headers.get(&name).cloned().unwrap_or_default(); + return write_tlv_string(&v, res, res_len); + } else { return E_INV_HANDLE; } + } + E_INV_ARGS + } _ => E_INV_METHOD, } } @@ -248,6 +263,33 @@ unsafe fn client_invoke(m: u32, id: u32, args: *const u8, args_len: usize, res: // Return Handle(Response) write_tlv_handle(T_RESPONSE, resp_id, res, res_len) } + M_CLIENT_POST => { + // args: TLV String(url), Bytes body + let data = slice(args, args_len); + let (_, argc, mut pos) = tlv_parse_header(data).map_err(|_| ()).or(Err(())).unwrap_or((1,0,4)); + if argc < 2 { return E_INV_ARGS; } + let (_t1, s1, p1) = tlv_parse_entry_hdr(data, pos).map_err(|_| ()).or(Err(())).unwrap_or((0,0,0)); + if data[pos] != 6 { return E_INV_ARGS; } + let url = std::str::from_utf8(&data[p1..p1+s1]).map_err(|_| ()).or(Err(())) .unwrap_or("").to_string(); + pos = p1 + s1; + let (t2, s2, p2) = tlv_parse_entry_hdr(data, pos).map_err(|_| ()).or(Err(())).unwrap_or((0,0,0)); + if t2 != 6 && t2 != 7 { return E_INV_ARGS; } + let body = data[p2..p2+s2].to_vec(); + + let path = parse_path(&url); + // Create Request + let req_id = REQUEST_ID.fetch_add(1, Ordering::Relaxed); + REQUESTS.lock().unwrap().insert(req_id, RequestState { path, body, response_id: None }); + // Enqueue to active server if running + if let Some(sid) = *ACTIVE_SERVER_ID.lock().unwrap() { + if let Some(s) = SERVER_INSTANCES.lock().unwrap().get_mut(&sid) { if s.running { s.pending.push_back(req_id); } } + } + // Create paired client Response + let resp_id = RESPONSE_ID.fetch_add(1, Ordering::Relaxed); + RESPONSES.lock().unwrap().insert(resp_id, ResponseState { status: 200, headers: HashMap::new(), body: vec![] }); + if let Some(rq) = REQUESTS.lock().unwrap().get_mut(&req_id) { rq.response_id = Some(resp_id); } + write_tlv_handle(T_RESPONSE, resp_id, res, res_len) + } _ => E_INV_METHOD, } } diff --git a/tests/e2e_plugin_net.rs b/tests/e2e_plugin_net.rs index 890d648d..e94a2556 100644 --- a/tests/e2e_plugin_net.rs +++ b/tests/e2e_plugin_net.rs @@ -124,3 +124,38 @@ body let result = i2.execute(ast2).expect("exec2"); assert_eq!(result.to_string_box().value, "Y"); } + +#[test] +fn e2e_http_post_and_headers() { + if !try_init_plugins() { return; } + + let code = r#" +local srv, cli, r, req, resp, body, st, hv +srv = new HttpServerBox() +srv.start(8090) + +cli = new HttpClientBox() +r = cli.post("http://localhost/api", "DATA") + +req = srv.accept() +// check server saw body +body = req.readBody() +// prepare response +resp = new HttpResponseBox() +resp.setStatus(201) +resp.setHeader("X-Test", "V") +resp.write("R") +req.respond(resp) + +// client reads status, header, body +st = r.getStatus() +hv = r.getHeader("X-Test") +body = r.readBody() +st.toString() + ":" + hv + ":" + body +"#; + + let ast = NyashParser::parse_from_string(code).expect("parse failed"); + let mut interpreter = nyash_rust::interpreter::NyashInterpreter::new(); + let result = interpreter.execute(ast).expect("exec failed"); + assert_eq!(result.to_string_box().value, "201:V:R"); +}