Implementing HTTP proxy that can support HTTPS requests by using actix-web

Hello,
I would like to write a simple HTTP proxy server that can handle HTTPS requests too.
Based on 7231 RFC server must support CONNECT header.
I could not find any reference in Actix-web that point out CONNECT header.
Would someone please give a hint for implementing CONNECT functionality.

I the above, I have no idea to handling this.


It must support something like this :

P.S.: I did ask this question in actix-web issues but nobody doesn't answer me yet, because, I have a little time, I did ask it here too.

You need to use bind_tls rather than bind to enable ssl connections to the actix server: actix_web::server::HttpServer - Rust. Here's some example code for creating the acceptor to pass to this: https://github.com/actix/actix-web/blob/4e7fac08b9675dad2965611e1c33cc734603de4f/tests/test_server.rs#L1027-L1033

This will require enabling actix's tls feature - you can do this with actix = { version = "...", features = ["tls"] } in Cargo.toml.

1 Like

Thank you for your response, I think my question was a little ambiguous, I edited my question.
I will explain my problem: my question is about an HTTP proxy server that can handle HTTPS requests too.
AFAIK, for implementing this we don't need to setup SSL/TLS on the server, instead of that we must setup HTTP tunneling by using CONNECT header.
By using CONNECT header we can transfer non-HTTP requests over HTTP that will use in proxy servers.


CONNECT header has two major part, as you can see in the above photo, the first part is about HTTP request and in fact connection request, if the connection was successful, the second part will use that is binary data.


In actix I couldn't find any solution to implementing second part (for sending binary data after an HTTP request in same connection). in fact, web-socket is something like this.


As a result, I'm finding for a solution to giving control of TcpStream after actix response for manually handling this.


Sorry for my bad English

I would like to say sorry for my English, I'm an ESL (English as a Second Language) person, If is somewhere In sentences, please tell me to describe that.

2 Likes

I couldn't find any easy solution for you.
Actix-Web is built on Actix and probably you can use Actix's context to implementing CONNECT by yourself, If you haven't enough time to wait for an easy solution or you are not able to implement this, I'd suggest you try other solutions like Hyper-proxy, Actix's family are great libraries, but at this time, it hasn't a hyperactive community and probably you can not find your answer.

1 Like

Thank you for your response, In my situation changing library is impossible.
My company is implementing an application that major web activities are on Actix-web and a simple part of that must work hybrid (both of the proxy server and web server on the same port), so I think it's not possible to combining actix-web and hyper-proxy.

I'm not familiar to actix's context, could you please give a hint for implementing this?

You can combine actix-web and hyper proxy by using to a proxy (such as nginx).

Unfortunately, I'm not fluent on Actix, I had a look at ws/context.rs, due to the similarity of WS and connect tunnel, probably it can helping you.

I'm not allowed to use external resources, hence Nginx and similar solutions aren't fit of me.


I did check the ws/context.rs page previously, I couldn't understand how it works.

Thanks for explaining it more! I definitely misunderstood the question. Unfortunately I'm not really an expert on HTTP internals, but I can probably help with the actix API.

If you have the headers sent correctly, and want to send more binary data, have you tried using HttpResponseBuilder::body?

I think using a Body::Actor with HttpContext will let you dynamically return further binary data.

Something like

struct ProxyingResponse {
    // ...
}

impl Actor for ProxyingResponse {
    type Context = HttpContext<Self, ()>;

    // could implement the 'started' method here to have some code run when this actor is created
}

// can use messages to send data into the ProxyingResponse to be returned to the request
impl Handler<MessageType> for ProxyingResponse {
    type Result: Something;
    fn handle(&mut self, msg: MessageType, ctx: &mut HttpContext<Self, ()>) -> Self::Result {
        // key is that in here, we have access to 'ctx'
        // for example, we can call
        ctx.write(vec![1, 2, 3]);
    }
}

fn index(req: HttpRequest) -> HttpResponse {
    let context = HttpContext::create(req, ProxyingResponse::new());
    HttpResponse::build(StatusCode::OK)
        .body(Body::Actor(Box::new(context))
}

You might be able to combine this with using the payload method on HttpRequest provided by the HttpMessage implementation in order to get read more binary data from the stream? I think that Payload could then be read as a futures::Stream.

This definitely isn't a complete answer, but it might be useful regardless. I think that all the pieces are here in actix, and we just need to figure out how to plumb them together and make it all work. I'm not an expert on what needs to be done to implement the proxy part, but hopefully this can help with the using-actix part.

2 Likes

I think it's exact what I need.
Unfortunately I'm not good at Actix and i can not understand your sample code very well.
I'm reading actix documentations and If i had a problem to understanding your code, I will ask you again in this topic.

Thank you so much. :hibiscus::hibiscus:

1 Like

我说么,我怎么全能读懂呢。。

Hello again @daboross, In these days, I did read actix-web documentation carefully, currently I'm familiar to actix actor system too, but unfortunately, I'm not able to implement what you said.

extern crate actix;
extern crate actix_web;
extern crate env_logger;
use actix::Actor;
use actix_web::HttpContext;
use actix::Handler;

use actix_web::{http, middleware, server, App, Error, HttpRequest, HttpResponse};



struct ProxyingResponse {
    x:Vec<u8>
}

impl Actor for ProxyingResponse {
    type Context = actix_web::HttpContext<Self, ()>;

}

struct MessageType;

impl actix::Message for MessageType{
    type Result= ();

}

impl ProxyingResponse {
    fn new() -> Self {
        Self { x : Vec::new()}
    }

}

impl Handler<MessageType> for ProxyingResponse {
    type Result= ();
    fn handle(&mut self, msg: MessageType, ctx: &mut HttpContext<Self, ()>) -> Self::Result {
        ctx.write(vec![49, 50, 51]);
    }
}

fn index(req: &HttpRequest) -> Result<HttpResponse, Error> {
    println!("{:?}", req);
    let x = ProxyingResponse::new();
    Ok(HttpResponse::Ok()
        .content_type("text/plain")
        .body(actix_web::HttpContext::create(req.clone(),x)))
        //.body("Welcome!")) 
}

fn main() {
    if ::std::env::var("RUST_LOG").is_err() {
        ::std::env::set_var("RUST_LOG", "actix_web=info");
    }
    env_logger::init();
    let sys = actix::System::new("ws-example");

    server::new(|| {
        App::new()
            .middleware(middleware::Logger::default())
            .resource("/", |r| r.f(index))
    }).bind("127.0.0.1:8443")
        .unwrap()
        .start();

    println!("Started http server: 127.0.0.1:8443");
    let _ = sys.run();
}

the result :

$nc 127.0.0.1 8443
GET / HTTP/1.1

HTTP/1.1 200 OK
transfer-encoding: chunked
content-type: text/plain
date: Fri, 15 Mar 2019 06:33:32 GMT

0

GET / HTTP/1.1

HTTP/1.1 200 OK
transfer-encoding: chunked
content-type: text/plain
date: Fri, 15 Mar 2019 06:33:33 GMT

0

I expected response body to be 123 instead of 0 !
Could you please help me to find where I made the mistake :bouquet:.

Hi!

Sorry it's taken me so long to respond to this!

I think the main thing that I forgot to mention was that the code in impl Handler<MessageType> for ProxyingResponse... won't ever run automatically. It's implementing a message handler - some other code needs to send the MessageType message to ProxyingResponse for it to run that.

I put that in as a way to allow you to send in data into the ProxyingResponse which it didn't already have. Some other code, for instance code reading in data from your proxied location, could send a MessageType message to the ProxyingResponse - then when ProxyingResponse receives that message, it writes the data.

If you want to write data without input from other actors, then you could implemented the started method in impl Actor for ProxyingResponse { ... in here ... }, and that code should run once.

If you're still working on this and have more questions, let me know!