
+ fn create_request(method: String, url: String, options: ?Options (null)) ClientRequest !invalid_url !ssl !connect {

    return ClientRequest.create(method, url, options) ! {
        match E {
            E.invalid_url => throw invalid_url, EMSG
            E.ssl => throw ssl, EMSG
            default => throw connect, EMSG
        }
    }
}

+ fn request(method: String, url: String, options: ?Options (null)) ClientResponse
    !invalid_url !invalid_output_path !ssl !connect !disconnect !invalid_response
{

    let req = ClientRequest.create(method, url, options) ! {
        match E {
            E.invalid_output_path => throw invalid_output_path, EMSG
            E.invalid_url => throw invalid_url, EMSG
            E.ssl => throw ssl, EMSG
            default => throw connect, EMSG
        }
    }

    while (req.progress() ! {
        match E {
            E.disconnect => throw disconnect, EMSG
            default => throw invalid_response, EMSG
        }
    }) {}

    let res = req.response() ! throw invalid_response
    if res.status == 301 {
        let follow = !isset(options) 
        if isset(options) : follow = options.follow_redirects
        if follow {
            let location = res.headers.get("location") !? null
            if isset(location) {
                return request(method, location, options) ! {
                    match E {
                        E.invalid_output_path => throw invalid_output_path, EMSG
                        E.invalid_url => throw invalid_url, EMSG
                        E.ssl => throw ssl, EMSG
                        default => throw connect, EMSG
                    }
                }
            }
        }
    }
    return res
}

+ fn download(url: String, to_path: String, method: String ("GET"), options: ?Options (null))
    !invalid_path !invalid_url !ssl !connect !disconnect !invalid_response
{
    if !isset(options) : options = Options{}
    options.output_to_file = to_path

    request(method, url, options) ! {
        match E {
            E.invalid_output_path => throw invalid_path, EMSG
            E.invalid_url => throw invalid_url, EMSG
            E.ssl => throw ssl, EMSG
            E.connect => throw connect, EMSG
            E.disconnect => throw disconnect, EMSG
            default => throw invalid_response, EMSG
        }
    }
}
