proxy-wasm-rust-sdk icon indicating copy to clipboard operation
proxy-wasm-rust-sdk copied to clipboard

How do I get http_call_response

Open alexhu20 opened this issue 1 year ago • 10 comments

Description

I have use case that need to make a separate http call in wasm and getting a response back. I only see that the base context has an API called on_http_call_response to fetch the entire response body.

Is there a way to do so in my client implementation?


Another question: The response body example seems incorrect, I have to do the following in order to buffer the entire response body, am I doing it wrong?

    fn on_http_request_body(&mut self, _body_size: usize, _end_of_stream: bool) -> Action {
        let msg = &format!("[on_http_request_body] ContextID {} on request body.", self.context_id);
        log(Info, msg);

        if !_end_of_stream {
            info!("[on_http_request_body] ContextID {} current body size: {}", self.context_id, self.request_body_length);
            
            if let Some(body_size) = self.get_http_request_body(self.request_body_length, _body_size) {
                let mut body_slice: Vec<u8> = body_size;
                self.request_body.append(&mut body_slice);
            }
            else { 
                info!("[on_http_request_body] ContextID {} else request body error???", self.context_id);
            }
            self.request_body_length += _body_size;
            return Action::Pause
        }
        
        Action::Continue
    }

alexhu20 avatar Sep 03 '24 17:09 alexhu20

Use dispatch_http_call() and on_http_call_response.


Try self.get_http_request_body(0, _body_size)

antonengelhardt avatar Sep 03 '24 21:09 antonengelhardt

Use dispatch_http_call() and on_http_call_response.

Try self.get_http_request_body(0, _body_size)

I see the contract in the base context trait, e.g.:

    fn on_http_call_response(&mut self, _token_id: u32, _num_headers: usize, _body_size: usize, _num_trailers: usize) {
        let _ = 
            self.get_http_call_response_body(0, _body_size)
                .map(|value| {
                    let response_body = String::from_utf8(value.to_vec()).unwrap();
                    info!("[on_http_call_response] token id:{}, response body: {}", _token_id, response_body)
                });
    }

Is this what you are referering to? is there a way to get the response else where? other than in the context

For example I have a client called depdendenClient, and I need to dispatch a http call and getting a response body back then modify the request header

alexhu20 avatar Sep 03 '24 22:09 alexhu20

@alexhu20 If i understood you correctly, this might be what you are looking for :)

antonengelhardt avatar Sep 04 '24 18:09 antonengelhardt

@antonengelhardt yes! this is the one I am talking about. However, is there another way to handle this outside of the context? Reason being we have several outbound calls rely on the dispatch_http_call before send it to upstream

alexhu20 avatar Sep 05 '24 05:09 alexhu20

@alexhu20 Do you need to make several calls for each request or once (at the beginning/when the filter starts)?

antonengelhardt avatar Sep 05 '24 06:09 antonengelhardt

We do make several calls for each request, and rely on the result then determine whether continue with the upstream or terminate send response back to the downstream

alexhu20 avatar Sep 05 '24 15:09 alexhu20

@alexhu20 You get the token id when you dispatch a call, you can store it in the context struct and you will also get the token id when the response callback is executed. Then you can match it and handle the request furthermore.

Alternatively, you can store the state as an enum in the context struct and work with that.

For both methods, you can check my pinned repo.

antonengelhardt avatar Sep 05 '24 17:09 antonengelhardt

I have something with registering a callback map HashMap<u32, Box<dyn HttpCallHandler>>, and the HttpCallHandler defines the contract on how to further process the response from the http_call individually

pub struct CustomHttpContext {
    pub http_call_handlers: HashMap<u32, Box<dyn HttpCallHandler>>
}

pub trait HttpCallHandler {
    fn process_http_call(&self, headers: Vec<(String, String)>, body: Option<Vec<u8>>) -> Result<(), Err> {
    }

    fn on_error(&self, status_code: u32, error: WasmPluginError) {
        error!("Error while handle http call {:?}", error)
    }

    fn on_ok(&self) {
        info!("succeed!")
    }
}

impl Context for CustomHttpContext {
    fn on_http_call_response(&mut self, token_id: u32, _num_headers: usize, body_size: usize, _num_trailers: usize) {
        info!("[on_http_call_response] ContextID {}, token_id: {}", self.context_id, token_id);
        let http_call_headers = self.get_http_call_response_headers();
        let http_call_response = self.get_http_call_response_body(0, body_size);

        match self.http_call_handlers.remove(&token_id) {
            None => {}
            Some(response_handler) => {
                info!("[on_http_call_response] has reached");
                match response_handler.process_http_call(http_call_headers, http_call_response) {
                    Ok(_) => {
                        response_handler.on_ok()
                    }
                    Err(e) => {
                        response_handler.on_error(400, e)
                    }
                } 
            }
        }
    }
}

impl HttpContext for CustomHttpContext {
    fn on_http_request_headers(&mut self, num_headers: usize, end_of_stream: bool) -> Action {
        let result = dispatch_http_call(.....);
        match result {
                    Ok(token) => {
                        let http_call = CustomHttpHandler { token_id: token };
                        &self.http_call_handlers.insert(token, Box::new(http_call));
                    }
                    Err(Status) => {
                        error!("error");
                        return Action::Continue;
                    }
                }
    }
}

In this something you'd advise to do so?

alexhu20 avatar Sep 10 '24 19:09 alexhu20

I am not really sure if this works...

You can dispatch multiple calls in any implementation of RootContext because you can make use of the on_tick function there and by keeping track of some state enum.

antonengelhardt avatar Sep 16 '24 18:09 antonengelhardt

Hello, @alexhu20 , I also encountered a similar problem to yours, and referring to asynchronous programming implementations in other languages, I think JavaScript's Promise is very suitable for this scenario (single-threaded execution, driven by other runtime's event loops). Therefore, I implemented this programming paradigm in proxy-wasm and submitted a PR. You can see if the implementation here meets your needs https://github.com/proxy-wasm/proxy-wasm-rust-sdk/pull/265

jizhuozhi avatar Oct 13 '24 10:10 jizhuozhi