diff --git a/README.md b/README.md index 13a0f82..f1a9016 100644 --- a/README.md +++ b/README.md @@ -154,6 +154,7 @@ For everything else please have a look at the [detailed list][1]. - [http_mksession](https://sn0int.readthedocs.io/en/latest/reference.html#http-mksession) - [http_request](https://sn0int.readthedocs.io/en/latest/reference.html#http-request) - [http_send](https://sn0int.readthedocs.io/en/latest/reference.html#http-send) + - [http_fetch](https://sn0int.readthedocs.io/en/latest/reference.html#http-fetch) - [http_fetch_json](https://sn0int.readthedocs.io/en/latest/reference.html#http-fetch-json) - [img_load](https://sn0int.readthedocs.io/en/latest/reference.html#img-load) - [img_exif](https://sn0int.readthedocs.io/en/latest/reference.html#img-exif) diff --git a/docs/reference.rst b/docs/reference.rst index f5d45bb..5851ed9 100644 --- a/docs/reference.rst +++ b/docs/reference.rst @@ -405,11 +405,31 @@ the following keys: if last_err() then return end if resp['status'] ~= 200 then return 'http status error: ' .. resp['status'] end +http_fetch +---------- + +This does an http_send_ and also automatically validate the status code. + +.. note:: + You almost always want this when setting the ``into_blob`` option since this + function validates the status code *before* inserting the response body into + blob storage. + +.. code-block:: lua + + -- short form + data = http_fetch(req) + if last_err() then return end + + -- long form + resp = http_send(req) + if last_err() then return end + if resp['status'] ~= 200 then return 'http status error: ' .. resp['status'] end + http_fetch_json --------------- -This is a shorthand for http_send_, validating the status code and parsing the -response body as json. +Identical to http_fetch_ but also automatically parses the response body as json. .. code-block:: lua diff --git a/modules/harness/tux-img.lua b/modules/harness/tux-img.lua index 8201cb0..de82b7e 100644 --- a/modules/harness/tux-img.lua +++ b/modules/harness/tux-img.lua @@ -8,11 +8,8 @@ function run() req = http_request(session, 'GET', 'https://www.kernel.org/theme/images/logos/tux.png', { into_blob=true, }) - r = http_send(req) + r = http_fetch(req) if last_err() then return end - if r['status'] ~= 200 then - return 'http error: ' .. r['status'] - end debug(r) db_add('image', { diff --git a/src/engine/ctx.rs b/src/engine/ctx.rs index 6312749..aececbe 100644 --- a/src/engine/ctx.rs +++ b/src/engine/ctx.rs @@ -389,6 +389,7 @@ fn ctx<'a>(env: Environment, logger: Arc>>) -> (hlua::Lua<'a runtime::http_mksession(&mut lua, state.clone()); runtime::http_request(&mut lua, state.clone()); runtime::http_send(&mut lua, state.clone()); + runtime::http_fetch(&mut lua, state.clone()); runtime::http_fetch_json(&mut lua, state.clone()); runtime::img_exif(&mut lua, state.clone()); runtime::img_load(&mut lua, state.clone()); diff --git a/src/runtime/gfx.rs b/src/runtime/gfx.rs index 8176586..1b02ac5 100644 --- a/src/runtime/gfx.rs +++ b/src/runtime/gfx.rs @@ -95,9 +95,8 @@ mod tests { req = http_request(session, 'GET', 'https://www.kernel.org/theme/images/logos/tux.png', { into_blob=true, }) - r = http_send(req) + r = http_fetch(req) if last_err() then return end - if r['status'] ~= 200 then return 'http error: ' .. r['status'] end img = img_load(r['blob']) @@ -125,9 +124,8 @@ mod tests { req = http_request(session, 'GET', 'https://www.kernel.org/theme/images/logos/tux.png', { into_blob=true, }) - r = http_send(req) + r = http_fetch(req) if last_err() then return end - if r['status'] ~= 200 then return 'http error: ' .. r['status'] end nudity = img_nudity(r['blob']) @@ -155,10 +153,9 @@ mod tests { req = http_request(session, 'GET', url, { into_blob=true, }) - r = http_send(req) + r = http_fetch(req) if last_err() then return end print(r) - if r['status'] ~= 200 then return 'wrong status code' end location = img_exif(r['blob']) print(location) diff --git a/src/runtime/http.rs b/src/runtime/http.rs index 815934d..6980ad1 100644 --- a/src/runtime/http.rs +++ b/src/runtime/http.rs @@ -31,7 +31,29 @@ pub fn http_send(lua: &mut hlua::Lua, state: Arc) { .context("invalid http request object") .map_err(|err| state.set_error(err.into()))?; - req.send_lua(state.as_ref()) + let resp = req.send(state.as_ref()) + .map_err(|err| state.set_error(err))?; + + req.response_to_lua(state.as_ref(), resp) + .map_err(|err| state.set_error(err)) + .map(|resp| resp.into()) + })) +} + +pub fn http_fetch(lua: &mut hlua::Lua, state: Arc) { + lua.set("http_fetch", hlua::function1(move |request: AnyLuaValue| -> Result { + let req = HttpRequest::try_from(request) + .context("invalid http request object") + .map_err(|err| state.set_error(err.into()))?; + + let resp = req.send(state.as_ref()) + .map_err(|err| state.set_error(err))?; + + if resp.status < 200 || resp.status > 299 { + return Err(state.set_error(format_err!("http status error: {}", resp.status))); + } + + req.response_to_lua(state.as_ref(), resp) .map_err(|err| state.set_error(err)) .map(|resp| resp.into()) })) @@ -167,6 +189,27 @@ mod tests { #[test] #[ignore] fn verify_fetch_ok() { + let script = Script::load_unchecked(r#" + function run() + session = http_mksession() + req = http_request(session, "GET", "https://httpbin.org/anything", {}) + x = http_fetch(req) + if last_err() then return end + + o = json_decode(x['text']) + if last_err() then return end + + if o['method'] ~= 'GET' then + return 'unexpected response' + end + end + "#).expect("failed to load script"); + script.test().expect("Script failed"); + } + + #[test] + #[ignore] + fn verify_fetch_json_ok() { let script = Script::load_unchecked(r#" function run() session = http_mksession() @@ -185,6 +228,19 @@ mod tests { #[test] #[ignore] fn verify_fetch_404() { + let script = Script::load_unchecked(r#" + function run() + session = http_mksession() + req = http_request(session, "GET", "https://httpbin.org/status/404", {}) + x = http_fetch(req) + end + "#).expect("failed to load script"); + script.test().err().expect("Script should have failed"); + } + + #[test] + #[ignore] + fn verify_fetch_json_404() { let script = Script::load_unchecked(r#" function run() session = http_mksession() @@ -197,7 +253,7 @@ mod tests { #[test] #[ignore] - fn verify_fetch_invalid_json() { + fn verify_fetch_json_invalid() { let script = Script::load_unchecked(r#" function run() session = http_mksession() diff --git a/src/web.rs b/src/web.rs index cf12958..b73cb8d 100644 --- a/src/web.rs +++ b/src/web.rs @@ -215,27 +215,24 @@ impl HttpRequest { .with_timeout(self.timeout) .wait_for_response()?; - Ok(res) - } - - pub fn send_lua(&self, state: &State) -> Result { - let res = self.send(state)?; - - // map result to LuaMap - let mut resp = LuaMap::new(); - resp.insert_num("status", f64::from(res.status)); - for cookie in &res.cookies { HttpRequest::register_cookies_on_state(&self.session, state, cookie); } + Ok(res) + } + + pub fn response_to_lua(&self, state: &State, res: Response) -> Result { + // map result to LuaMap + let mut resp = LuaMap::new(); + resp.insert_num("status", f64::from(res.status)); + let mut headers = LuaMap::new(); for (key, value) in res.headers { headers.insert_str(key.to_lowercase(), value); } resp.insert("headers", headers); - if self.into_blob { let blob = Blob::create(res.body); let id = state.register_blob(blob); @@ -286,6 +283,7 @@ impl Into for HttpRequest { pub struct CookieJar(HashMap); impl CookieJar { + #[inline(always)] pub fn register_in_jar(&mut self, key: String, value: String) { self.0.insert(key, value); } @@ -294,6 +292,7 @@ impl CookieJar { impl Deref for CookieJar { type Target = HashMap; + #[inline(always)] fn deref(&self) -> &Self::Target { &self.0 }