rouille/
proxy.rs

1// Copyright (c) 2016 The Rouille developers
2// Licensed under the Apache License, Version 2.0
3// <LICENSE-APACHE or
4// http://www.apache.org/licenses/LICENSE-2.0> or the MIT
5// license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
6// at your option. All files in the project carrying such
7// notice may not be copied, modified, or distributed except
8// according to those terms.
9
10//! Dispatch a request to another HTTP server.
11//!
12//! This module provides functionalities to dispatch a request to another server. This can be
13//! used to make rouille behave as a reverse proxy.
14//!
15//! This function call will return immediately after the remote server has finished sending its
16//! headers. The socket to the remote will be stored in the `ResponseBody` of the response.
17//!
18//! # Proxy() vs full_proxy()
19//!
20//! The difference between `proxy()` and `full_proxy()` is that if the target server fails to
21//! return a proper error, the `proxy()` function will return an error (in the form of a
22//! `ProxyError`) while the `full_proxy()` will return a `Response` with a status code indicating
23//! an error.
24//!
25//! The `full_proxy()` function will only return an error if the body was already extracted from
26//! the request before it was called. Since this indicates a logic error in the code, it is a good
27//! idea to `unwrap()` the `Result` returned by `full_proxy()`.
28//!
29//! # Example
30//!
31//! You can for example dispatch to a different server depending on the host requested by the
32//! client.
33//!
34//! ```
35//! use rouille::{Request, Response};
36//! use rouille::proxy;
37//!
38//! fn handle_request(request: &Request) -> Response {
39//!     let config = match request.header("Host") {
40//!         Some(h) if h == "domain1.com" => {
41//!             proxy::ProxyConfig {
42//!                 addr: "domain1.handler.localnetwork",
43//!                 replace_host: None,
44//!             }
45//!         },
46//!
47//!         Some(h) if h == "domain2.com" => {
48//!             proxy::ProxyConfig {
49//!                 addr: "domain2.handler.localnetwork",
50//!                 replace_host: None,
51//!             }
52//!         },
53//!
54//!         _ => return Response::empty_404()
55//!     };
56//!
57//!     proxy::full_proxy(request, config).unwrap()
58//! }
59//! ```
60
61use std::borrow::Cow;
62use std::error;
63use std::fmt;
64use std::io;
65use std::io::BufRead;
66use std::io::Error as IoError;
67use std::io::Read;
68use std::io::Write;
69use std::net::TcpStream;
70use std::net::ToSocketAddrs;
71use std::time::Duration;
72
73use Request;
74use Response;
75use ResponseBody;
76
77/// Error that can happen when dispatching the request to another server.
78#[derive(Debug)]
79pub enum ProxyError {
80    /// Can't pass through the body of the request because it was already extracted.
81    BodyAlreadyExtracted,
82
83    /// Could not read the body from the request, or could not connect to the remote server, or
84    /// the connection to the remote server closed unexpectedly.
85    IoError(IoError),
86
87    /// The destination server didn't produce compliant HTTP.
88    HttpParseError,
89}
90
91impl From<IoError> for ProxyError {
92    fn from(err: IoError) -> ProxyError {
93        ProxyError::IoError(err)
94    }
95}
96
97impl error::Error for ProxyError {
98    #[inline]
99    fn source(&self) -> Option<&(dyn error::Error + 'static)> {
100        match *self {
101            ProxyError::IoError(ref e) => Some(e),
102            _ => None,
103        }
104    }
105}
106
107impl fmt::Display for ProxyError {
108    #[inline]
109    fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
110        let description = match *self {
111            ProxyError::BodyAlreadyExtracted => "the body of the request was already extracted",
112            ProxyError::IoError(_) => {
113                "could not read the body from the request, or could not connect to the remote \
114                 server, or the connection to the remote server closed unexpectedly"
115            }
116            ProxyError::HttpParseError => "the destination server didn't produce compliant HTTP",
117        };
118
119        write!(fmt, "{}", description)
120    }
121}
122
123/// Configuration for the reverse proxy.
124#[derive(Debug, Clone)]
125pub struct ProxyConfig<A> {
126    /// The address to connect to. For example `example.com:80`.
127    pub addr: A,
128    /// If `Some`, the `Host` header will be replaced with this value.
129    pub replace_host: Option<Cow<'static, str>>,
130}
131
132/// Sends the request to another HTTP server using the configuration.
133///
134/// If the function fails to get a response from the target, an error is returned. If you want
135/// to instead return a response with a status code such as 502 (`Bad Gateway`) or 504
136/// (`Gateway Time-out`), see `full_proxy`.
137///
138/// > **Note**: Implementation is very hacky for the moment.
139///
140/// > **Note**: SSL is not supported.
141// TODO: ^
142pub fn proxy<A>(request: &Request, config: ProxyConfig<A>) -> Result<Response, ProxyError>
143where
144    A: ToSocketAddrs,
145{
146    let mut socket = TcpStream::connect(config.addr)?;
147    socket.set_read_timeout(Some(Duration::from_secs(60)))?;
148    socket.set_write_timeout(Some(Duration::from_secs(60)))?;
149
150    let mut data = match request.data() {
151        Some(d) => d,
152        None => return Err(ProxyError::BodyAlreadyExtracted),
153    };
154
155    socket
156        .write_all(format!("{} {} HTTP/1.1\r\n", request.method(), request.raw_url()).as_bytes())?;
157    for (header, value) in request.headers() {
158        let value = if header == "Host" {
159            if let Some(ref replace) = config.replace_host {
160                &**replace
161            } else {
162                value
163            }
164        } else {
165            value
166        };
167        if header == "Connection" {
168            continue;
169        }
170
171        socket.write_all(format!("{}: {}\r\n", header, value).as_bytes())?;
172    }
173    socket.write_all(b"Connection: close\r\n\r\n")?;
174    io::copy(&mut data, &mut socket)?;
175
176    let mut socket = io::BufReader::new(socket);
177
178    let mut headers = Vec::new();
179    let status_code;
180    {
181        let mut lines = socket.by_ref().lines();
182
183        {
184            let line = match lines.next() {
185                Some(l) => l,
186                None => return Err(ProxyError::HttpParseError),
187            }?;
188            let mut splits = line.splitn(3, ' ');
189            let _ = splits.next();
190            let status_str = match splits.next() {
191                Some(l) => l,
192                None => return Err(ProxyError::HttpParseError),
193            };
194            status_code = match status_str.parse() {
195                Ok(s) => s,
196                Err(_) => return Err(ProxyError::HttpParseError),
197            };
198        }
199
200        for header in lines {
201            let header = header?;
202            if header.is_empty() {
203                break;
204            }
205
206            let mut splits = header.splitn(2, ':');
207            let header = match splits.next() {
208                Some(v) => v,
209                None => return Err(ProxyError::HttpParseError),
210            };
211            let val = match splits.next() {
212                Some(v) => v,
213                None => return Err(ProxyError::HttpParseError),
214            };
215            let val = &val[1..];
216
217            headers.push((header.to_owned().into(), val.to_owned().into()));
218        }
219    }
220
221    Ok(Response {
222        status_code,
223        headers,
224        data: ResponseBody::from_reader(socket),
225        upgrade: None,
226    })
227}
228
229/// Error that can happen when calling `full_proxy`.
230#[derive(Debug)]
231pub enum FullProxyError {
232    /// Can't pass through the body of the request because it was already extracted.
233    BodyAlreadyExtracted,
234}
235
236impl error::Error for FullProxyError {}
237
238impl fmt::Display for FullProxyError {
239    #[inline]
240    fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
241        let description = match *self {
242            FullProxyError::BodyAlreadyExtracted => "the body of the request was already extracted",
243        };
244
245        write!(fmt, "{}", description)
246    }
247}
248
249/// Sends the request to another HTTP server using the configuration.
250///
251/// Contrary to `proxy`, if the server fails to return a proper response then a response is
252/// generated with the status code 502 or 504.
253///
254/// The only possible remaining error is if the body of the request was already extracted. Since
255/// this would be a logic error, it is acceptable to unwrap it.
256pub fn full_proxy<A>(request: &Request, config: ProxyConfig<A>) -> Result<Response, FullProxyError>
257where
258    A: ToSocketAddrs,
259{
260    match proxy(request, config) {
261        Ok(r) => Ok(r),
262        Err(ProxyError::IoError(_)) => Ok(Response::text("Gateway Time-out").with_status_code(504)),
263        Err(ProxyError::HttpParseError) => Ok(Response::text("Bad Gateway").with_status_code(502)),
264        Err(ProxyError::BodyAlreadyExtracted) => Err(FullProxyError::BodyAlreadyExtracted),
265    }
266}