1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
/* Copyright 2016 Joshua Gentry
 *
 * Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
 * http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
 * <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
 * option. This file may not be copied, modified, or distributed
 * except according to those terms.
 */
use std::fmt::{Debug, Formatter, Error, Write};
use std::io::Read;

use hyper::client::Client;
use hyper::header::{Cookie, CookieJar, CookiePair, SetCookie};

//*************************************************************************************************
/// A HTTP client connection that is cookie aware.
///
/// This object is implemented for testing purposes.  It allows for a series of HTTP requests to
/// a server and remmember the cookie's the server has responded with so they can be passed back
/// to the server for the next request.
///
/// # Examples
/// ```
/// # extern crate iron;
/// # extern crate fe_session;
/// use iron::{Iron, Request, Response};
/// use iron::status::Status;
/// use fe_session::FeConnection;
///
/// # fn main() {
/// let iron = Iron::new(|_ : &mut Request| {
///    Ok(Response::with((Status::Ok, "success")))
/// });
/// let mut server = iron.http(("localhost", 3000)).unwrap();
///
/// let mut conn = FeConnection::new("localhost", 3000);
/// assert_eq!(conn.get_string("/login?bob"), "success");
/// assert_eq!(conn.get_string("/logout"), "success");
///
/// server.close().unwrap();
/// # }
/// ```
pub struct FeConnection
{
    //---------------------------------------------------------------------------------------------
    /// The set of cookies we'll be sending to the server.
    cookies : CookieJar<'static>,

    //---------------------------------------------------------------------------------------------
    /// The server to connect to.
    host : String,

    //---------------------------------------------------------------------------------------------
    /// The port the server is listening on.
    port : u16
}

impl FeConnection
{
    //*********************************************************************************************
    /// Create a new instance of the object.
    ///
    /// # Examples
    /// ```
    /// use fe_session::FeConnection;
    ///
    /// let conn = FeConnection::new("localhost", 3000);
    /// ```
    pub fn new(
        server : &str,
        port   : u16
        ) -> FeConnection
    {
        trace!("new({}, {})", &server, port);

        FeConnection {
            cookies : CookieJar::new(&[]),
            host    : server.to_string(),
            port    : port
        }
    }

    //*********************************************************************************************
    /// Returns the value of the specified cookie.
    ///
    /// If the cookie doesn't exist this this method will return an empty string.
    pub fn get_cookie(
        &self,
        name : &str
        ) -> String
    {
        if let Some(cookie) = self.cookies.find(name)
        {
            trace!("get_cookie({}) => '{}'", name, &cookie.value);
            cookie.value
        }
        else
        {
            trace!("get_cookie({}) => ''", name);
            String::new()
        }
    }

    //*********************************************************************************************
    /// Sets a cookie to send to the server.
    ///
    /// This method would replace any existing cookie with the same name.
    pub fn set_cookie(
        &self,
        name : &str,
        val  : &String
        )
    {
        trace!("set_cookie({}, {})", name, val);
        self.cookies.add(CookiePair::new(String::from(name), val.clone()));
    }

    //*********************************************************************************************
    /// Issues an HTTP GET request to the server and returns the response as a string.  If the
    /// connection cannot be made or the server returns an error, this method will return an empty
    /// string.
    ///
    /// # Examples
    /// ```
    /// # extern crate iron;
    /// # extern crate fe_session;
    /// use iron::{Iron, Request, Response};
    /// use iron::status::Status;
    /// use fe_session::FeConnection;
    ///
    /// fn main() {
    /// let iron = Iron::new(|_ : &mut Request| {
    ///    Ok(Response::with((Status::Ok, "success")))
    /// });
    /// let mut server = iron.http(("localhost", 3000)).unwrap();
    ///
    /// let mut conn = FeConnection::new("localhost", 3000);
    /// assert_eq!(conn.get_string("/login?bob"), "success");
    /// assert_eq!(conn.get_string("/logout"), "success");
    ///
    /// server.close().unwrap();
    /// # }
    /// ```
    pub fn get_string(
        &mut self,
        path  : &str
        ) -> String
    {
        trace!("get_string({})", path);

        //-----------------------------------------------------------------------------------------
        // Build the URL.
        let mut url = String::with_capacity(64);
        write!(url, "http://{}:{}{}", self.host, self.port, path).unwrap();

        //-----------------------------------------------------------------------------------------
        // Issue the HTTP get.
        let hyper = Client::new();

        match hyper.get(&url).header(Cookie::from_cookie_jar(&self.cookies)).send()
        {
            Ok(mut response) => {
                if response.status.is_success()
                {
                    //-----------------------------------------------------------------------------
                    // Request was successful, apply any changes to the cookies to the CookieJar.
                    if let Some(new) = response.headers.get::<SetCookie>()
                    {
                        new.apply_to_cookie_jar(&mut self.cookies);
                    }

                    //-----------------------------------------------------------------------------
                    // Read the response into a string.
                    let mut result = String::with_capacity(128);

                    match response.read_to_string(&mut result)
                    {
                        //-------------------------------------------------------------------------
                        // Succesfully read the response.
                        Ok(_) => {
                            debug!("get_string({}) => '{}''", url, result);
                            result
                        },
                        //-------------------------------------------------------------------------
                        // Error readin the response.
                        Err(err)  => {
                            warn!("get_string({}) | Read error: {}", url, err);
                            String::new()
                        }
                    }
                }
                //---------------------------------------------------------------------------------
                // The HTTP response returned an error status.
                else
                {
                    warn!("get_string({}) | HTTP error: {}", &url, &response.status);
                    String::new()
                }
            },
            //-------------------------------------------------------------------------------------
            // Request failed.
            Err(err) => {
                warn!("get_string({}) | Hyper error: {}", url, err);
                String::new()
            }
        }
    }
}

impl Debug for FeConnection
{
    //********************************************************************************************
    /// Displays debug information about the instance.
    fn fmt(
        &self,
        fmt : &mut Formatter
        ) -> Result<(), Error>
    {
        fmt.debug_struct("FeConnection")
            .field("host", &self.host)
            .field("port", &self.port)
            .finish()
    }
}