Reputation: 53
I'd like to create a curl-like function in Rust.
So far, here is the code I use:
match Url::parse(url) {
Ok(u) => {
match TcpStream::connect(u.host.as_slice(), 80) {
Ok(mut socket) => {
let req = format!(
"GET {:s} HTTP/1.1\r\nHost: {:s}\r\nAccept: */*\r\nContent-Length: 0\r\nContent-Type: aplication/x-www-form-urlencoded\r\n",
u.path.path.as_slice(), u.host
);
socket.write(req.as_bytes());
match socket.read_to_string() {
Ok(res) => println!("{}", res.as_slice()),
Err(e) => fail!("Error: {}", e)
};
drop(socket);
},
Err(e) => fail!("Error: {}", e)
};
},
Err(e) => fail!("Error: {}", e)
};
The problem is, if the HTTP request is properly formatted,
println!("{}", res.as_slice())
will not display anything, with any url.
If it's not well formatted the same code will display 403 or 400 errors. Is that supposed to mean that the socket var is well populated?
Where could the problem come from? The server or the request?
By the way, the code being a little too long, I'd like to use the macro try! but it raises an error when doing, for instance:
try!(TcpStream::connect(u.host.as_slice(), 80)
Upvotes: 3
Views: 1287
Reputation: 127711
It looks like your request really is incorrect, but not in a way the server could tell you.
GET {:s} HTTP/1.1\r\nHost: {:s}\r\nAccept: */*\r\nContent-Length: 0\r\nContent-Type: aplication/x-www-form-urlencoded\r\n
You see, an HTTP request is supposed to be ended in two newlines, that is, two \r\n
sequences. Also GET request cannot have body (though most web servers will probably ignore that):
GET {:s} HTTP/1.1\r\nHost: {:s}\r\nAccept: */*\r\n\r\n
This request should work.
As for try!
, it seems that you're trying to use it in a main()
function or something like this, right? try!
is intended to return from the function upon error, that is, this:
try!(TcpStream::connect(u.host.as_slice(), 80))
is rewritten into
match TcpStream::connect(u.host.as_slice(), 80) {
Ok(s) => s,
Err(e) => return Err(e)
}
If your function does not return Result
, it won't work. So your code will look better like this:
extern crate url;
use std::io::{IoResult, File, TcpStream};
use url::Url;
fn download_file(url: &Url, file: &Path) -> IoResult<()> {
let mut socket = try!(TcpStream::connect(url.host.as_slice(), 80));
let req = format!(
"GET {:s} HTTP/1.1\r\nHost: {:s}\r\nAccept: */*\r\n\r\n",
url.path.path.as_slice(), url.host
);
try!(socket.write(req.as_bytes()));
let res = try!(socket.read_to_end());
let mut file = File::create(file);
try!(file.write(res.as_slice()));
Ok(())
}
fn main() {
let url = "http://kitten.jpg.to/";
match Url::parse(url) {
Ok(url) => match download_file(&url, &Path::new("/tmp/kitten_link.http")) {
Ok(_) => println!("Download successful"),
Err(e) => fail!("Error: {}", e)
},
Err(e) => fail!("Error: {}", e)
}
}
BTW, you also don't need to drop sockets or other things explicitly. They are destroyed automatically when they go out of scope. I've also changed read_to_string()
to read_to_end()
because binary files will not be correct after they are passed through a String
.
Upvotes: 3