Chapter 6
The curl
requests we have used so far have all been expertly crafted to work. We haven’t tried to game the API or make it fail, because we are nice people.
Not everyone is like this, unfortunately! And it’s not only people with bad intentions we need to look out for, but also simple mistakes. Anyone can make a mistake — it happens. That does not mean we should laugh at them for trying and send back some indecipherable message.
Descriptive messages are a very important part of any web API. Web APIs are used by our fellow developers and, by building an API, we take the responsibility of providing a good service for them. Any service should be easy to use. Put yourself inside a user’s shoes and pinpoint the issues someone could have with your service.
The obvious problem in our current web API is that we don’t send back meaningful errors when something goes wrong.
Let’s take the following curl
request as an example. Notice anything wrong? We forgot to close the JSON
document. Before we can realize our mistake, we send the request…
curl -i -X POST http://localhost:4567/users -H "Content-Type: application/json" -d '{"first_name":"Samuel", "last_name":"Da Costa", "age":19'
and we end up with a scary 500 Internal Server Error
. This seems to mean that there was something wrong with the server, not with our request.
HTTP/1.1 500 Internal Server Error Content-Type: text/plain Content-Length: 4679 Connection: keep-alive Server: thin [JSON::ParserError]
Luckily, Sinatra sends back the whole stack trace, so we can easily debug it. Not every framework is that nice, though. That’s the server’s duty — to provide descriptive errors to the client. A 500
is as descriptive as looking at a black box that just says: “Sorry bro, it didn’t work”.
HTTP comes with a set of features to handle errors. Indeed, thanks to all its HTTP status codes, we can configure our API to send back descriptive error messages to a client in a format that it can understand.
6.1. The different classes of HTTP status codes
There are five classes of HTTP codes:
1XX
Informational: Status codes starting with1
are known as informational codes. Most of them are rarely used nowadays.2XX
Success: These codes indicate that the exchange between the server and the client was successful.3XX
Redirection:3XX
codes indicate that the client must take additional action before the request can be completed.4XX
Client Error: There was something wrong with the request the client sent and the server cannot process it.5XX
Server Error: The client sent a valid request but the server was not able to process it successfully.
We will be using status codes from some of these classes in this chapter.
6.2. Global
We will add error handling for each route, but let’s first see the errors we need to handle for every request.
6.2.1. 405 Method Not Allowed
This HTTP status code can be used when the client tries to access a resource using an unsupported HTTP method. For example, in our API, what would happen if a client tried to use the PUT
method with the /users
URI?
Let’s give it a try.
curl -i -X PUT http://localhost:4567/users
Output
HTTP/1.1 404 Not Found Content-Type: text/html;charset=utf-8 X-Cascade: pass Content-Length: 467 X-XSS-Protection: 1; mode=block X-Content-Type-Options: nosniff X-Frame-Options: SAMEORIGIN Connection: keep-alive Server: thin <!DOCTYPE html> <html> <head> <MORE HTML>
That’s not very helpful… Plus, it returns 404
. While we do have a resource living at that URI, it’s not the right HTTP method… we can do better. All we need to do is catch any request accessing /users
with an unsupported method: put
, patch
and delete
. With a bit of metaprogramming, it’s not hard to do.
In the code below, we loop through each of the HTTP methods we don’t support and define a Sinatra route that returns 405 Method Not Allowed
for each one of them.
# webapi.rb # /users # head /users # get /users # post /users [:put, :patch, :delete].each do |method| send(method, '/users') do halt 405 end end # options /users/:first_name
Let’s give it another try after restarting the server, shall we?
curl -i -X PUT http://localhost:4567/users
Output
HTTP/1.1 405 Method Not Allowed Content-Type: text/html;charset=utf-8 Content-Length: 0 [MORE HEADERS]
Alright! It’s working correctly, let’s proceed.
6.2.2. 406 Not Acceptable
We already implemented one HTTP status code in the previous chapters. If the client requests a media type that our API does not support, we will return 406 Not Acceptable
. To the client receiving this, it means that the server was not able to generate a representation for the resource according to the criteria the client had fixed. The response should also include what formats are actually available for the client to pick from.
# webapi.rb # users # ... helpers do # def json_or_default? # def xml? def accepted_media_type return 'json' unless request.accept.any? request.accept.each do |type| return 'json' if json_or_default?(type) return 'xml' if xml?(type) end content_type 'text/plain' halt 406, 'application/json, application/xml' end # def type # def send_data end
Let’s try with a fake media type that we do not support.
curl -i http://localhost:4567/users -H "Accept: moar/curl"
Output
HTTP/1.1 406 Not Acceptable Content-Type: text/plain;charset=utf-8 Content-Length: 33 ... More Headers ... application/json, application/xml
With this, the client can understand what is actually supported and act accordingly. Sadly, it’s not defined more clearly in a standard.
Alternatively, we could actually return a JSON representation instead of telling the client that we can’t give it anything. We would set the Content-Type
header to application/json
to let the client do its own checking before it parses the response. Both this option and the previous one are acceptable as defined in the HTTP RFC.
# webapi.rb # users # ... helpers do # def json_or_default? # def xml? def accepted_media_type return 'json' unless request.accept.any? request.accept.each do |type| return 'json' if json_or_default?(type) return 'xml' if xml?(type) end 'json' end # def type # def send_data end
Don’t forget to restart the server.
curl -i http://localhost:4567/users -H "Accept: moar/curl"
Output
HTTP/1.1 200 OK Content-Type: application/json Content-Length: 203 ... More Headers ... [ {"first_name":"Thibault", "last_name":"Denizet", "age":25, "id":"thibault"}, {"first_name":"Simon", "last_name":"Random", "age":26, "id":"simon"}, {"first_name":"John", "last_name":"Smith", "age":28, "id":"john"} ]
If your server cannot generate a representation the way the client requests, you can either return 406
(with or without a list of actually supported media types), or just return the default format and let the client handle it.
6.3. POST /users
For the post /users
route, we are currently not handling any errors. If you run the following curl
request, it will crash and send you back 500
and the whole stack trace.
curl -X POST -i http://localhost:4567/users -H "Content-Type: application/fake" -d 'Weirdly Formatted Data'
Output
HTTP/1.1 500 Internal Server Error Content-Type: text/plain Content-Length: 4647 Connection: keep-alive Server: thin JSON::ParserError: 757: unexpected token at 'Weirdly Formatted Data' [Stack Trace]
This does not help the client in understanding what happened. It does help us humans, because we can read the Ruby stack trace and see that there was a JSON::ParserError
. But to an automated client, this only means that something went wrong on the server. We need to fix this.
6.3.1. 415 Unsupported Media Type
To fix it we will use the 415
HTTP status code. 415 Unsupported Media Type
is exactly what its name implies — we can return it to the client when we don’t understand the format of the data sent by the client.
To prevent the execution of the code there, we will first check if the data was sent as a JSON document, the only format we support for requests. Our code must be guarded from unwanted media types.
We just need the following line to do it.
halt 415 unless request.env['CONTENT_TYPE'] == 'application/json'
And it comes in the post /users
route like this:
# webapi.rb # Stuff # ... post '/users' do halt 415 unless request.env['CONTENT_TYPE'] == 'application/json' users[user['first_name'].downcase.to_sym] = user url = "http://localhost:4567/users/#{user['first_name']}" response.headers['Location'] = url status 201 end
Don’t forget to restart the server.
Now, let’s see what happens when we send the curl
request seen in the previous section.
curl -X POST -i http://localhost:4567/users -H "Content-Type: application/fake" -d 'Weirdly Formatted Data'
Output
HTTP/1.1 415 Unsupported Media Type [MORE HEADERS]
That’s much more descriptive, right? The only problem is that this does not tell the client which media types are actually supported. We will see what we can do about this later. For now, our endpoint won’t crash anymore whatever the received format is.
6.3.2. 400 Bad Request
The 400 Bad Request
is used way too often in modern web applications. Its real purpose is to indicate that the server could not understand the request due to some syntax issue. For example, a malformed JSON document.
Let’s give it a try and break some stuff.
curl -X POST -i http://localhost:4567/users -H "Content-Type: application/json" -d '{"first_name":"Mark"'
Output
HTTP/1.1 500 Internal Server Error Content-Type: text/plain Content-Length: 4645 Connection: keep-alive Server: thin JSON::ParserError: 757: unexpected token at '{"first_name":"Mark"' [Stack Trace]
Boom! Broken. Since our JSON document, {"first_name":"Mark"
, is not valid, our server crashes when trying to parse it. Here is how we can fix this. We are going to catch the exception raised when parsing an invalid JSON document (JSON::ParserError
). In the rescue
, we will send back the error to the client, in the format of its choice (specified by the Accept
header). Note that we are still using the send_data
method we wrote earlier.
# webapi.rb # Stuff # ... post '/users' do halt 415 unless request.env['CONTENT_TYPE'] == 'application/json' begin user = JSON.parse(request.body.read) rescue JSON::ParserError => e halt 400, send_data(json: -> { { message: e.to_s } }, xml: -> { { message: e.to_s } }) end users[user['first_name'].downcase.to_sym] = user url = "http://localhost:4567/users/#{user['first_name']}" response.headers['Location'] = url status 201 end
Don’t forget to restart your server!
curl -X POST -i http://localhost:4567/users -H "Content-Type: application/json" -d '{"first_name":"Mark"'
Output
HTTP/1.1 400 Bad Request Content-Type: application/json Content-Length: 65 X-Content-Type-Options: nosniff Connection: keep-alive Server: thin {"message":"757: unexpected token at '{"first_name":"Mark"'"}
Great! We are getting a 400
back and the body contains the error message.
6.3.3. 409 Conflict
Currently, if we try to re-create a user that already exists, it’s just going to override the existing one. Not good. This should throw up some kind of conflict error saying that a user cannot be overridden. The problem here is how we save users in a Ruby hash. Since we use the first name as a key, we don’t want to allow the override of an existing user.
Luckily, that’s what the 409 Conflict
HTTP code is for. Note that this code is only allowed in situations where the client can actually fix the conflict. In our case, the client can either change the first name or use another endpoint to update the user.
Our use of the first name as a key is there only for simplicity and any production application should not use such a mechanism. Unique generated IDs are way better!
To prevent any override, we will use this code. If we find a user with the first name sent by the client, we’ll immediately halt and send back a response to the client with a 409
status code and an explanation message.
if users[user['first_name'].downcase.to_sym] message = { message: "User #{user['first_name']} already in DB." } halt 409, send_data(json: -> { message }, xml: -> { message }) end
Here is our full POST
endpoint:
# webapi.rb # Stuff # ... post '/users' do halt 415 unless request.env['CONTENT_TYPE'] == 'application/json' begin user = JSON.parse(request.body.read) rescue JSON::ParserError => e halt 400, send_data(json: -> { { message: e.to_s } }, xml: -> { { message: e.to_s } }) end if users[user['first_name'].downcase.to_sym] message = { message: "User #{user['first_name']} already in DB." } halt 409, send_data(json: -> { message }, xml: -> { message }) end users[user['first_name'].downcase.to_sym] = user url = "http://localhost:4567/users/#{user['first_name']}" response.headers['Location'] = url status 201 end
Let’s try to recreate an existing user (for example, Thibault), after restarting the server.
curl -X POST -i http://localhost:4567/users -H "Content-Type: application/json" -d '{"first_name":"Thibault", "last_name":"Denizet", "age":25}'
Output
HTTP/1.1 409 Conflict Content-Type: application/json Content-Length: 42 X-Content-Type-Options: nosniff Connection: keep-alive Server: thin {"message":"User Thibault already in DB."}
Great, we’re telling the client that its request triggered a conflict that needs to be solved. We can now detect when updates conflict with the users’ data stored in the users
hash.
6.4. GET /users/:first_name
Let’s now turn our attention to the get /users/:first_name
route. We know our endpoint works fine when the user exists. But what happens when it doesn’t?
curl -i http://localhost:4567/users/frank
HTTP/1.1 200 OK Content-Type: application/json Content-Length: 4 ... More Headers ... null
What the heck is this null
doing here? Plus, we are telling the client it’s a JSON document with the Content-Type
. We really need to fix this!
6.4.1. 404 Not Found
The 404 Not Found
status code can help us. This code is meant to tell the client that nothing was found at the URI specified by the client; in other words, the requested resource does not exist.
This fits what we need for the get /users/:first_name
route pretty well. Let’s add a little halt that will return 404
if the user is not found in our hash.
# webapi.rb # Stuff # ... get '/users/:first_name' do |first_name| halt 404 unless users[first_name.to_sym] send_data(json: -> { users[first_name.to_sym].merge(id: first_name) }, xml: -> { { first_name => users[first_name.to_sym] } }) end
Restart the server and send the following request.
curl -i http://localhost:4567/users/frank
Output
HTTP/1.1 404 Not Found
... More Headers ...
Looks good!
6.4.2. 410 Gone
Sadly 404 Not Found
can seem quite generic to a client. We can use other codes to give a more specific response. One of them is 410 Gone
which indicates that a resource used to live at the given URI, but doesn’t anymore. It has probably been deleted since the server does not have the new location.
Currently, if we delete a user and then try to access it, we will just get 404
, as if it never existed.
It’s not always possible for a server to indicate a deleted resource and there is nothing forcing us to use this HTTP status. It is helpful for a client though, since it will let the client know that this URI does not point to anything anymore. It shouldn’t be considered as a bug since there used to be something there. The client should take note that it’s now gone and move on with its life.
Let’s quickly add a mechanism to keep track of what has been deleted. In a production application with a real database, it is possible to achieve this kind of mechanism by using a boolean in order to see if something has been deleted or not. There are other ways to do it, but we won’t be covering them.
The first thing we need is a hash, named deleted_users
, that will contain all the deleted users (duh!).
# webapi.rb # require users = { thibault: { first_name: 'Thibault', last_name: 'Denizet', age: 25 }, simon: { first_name: 'Simon', last_name: 'Random', age: 26 }, john: { first_name: 'John', last_name: 'Smith', age: 28 } } deleted_users = {} # Helpers # Routes
Then we need to update the get /users/:first_name
route to halt with 410
if the deleted_users
hash contains the specified user.
# webapi.rb # stuff get '/users/:first_name' do |first_name| halt 410 if deleted_users[first_name.to_sym] halt 404 unless users[first_name.to_sym] send_data(json: -> { users[first_name.to_sym].merge(id: first_name) }, xml: -> { { first_name => users[first_name.to_sym] } }) end
Finally, we need to fill the deleted_users
hash every time a user gets deleted in the delete /users/:first_name
route.
# webapi.rb # stuff delete '/users/:first_name' do |first_name| first_name = first_name.to_sym deleted_users[first_name] = users[first_name] if users[first_name] users.delete(first_name) status 204 end
Everything is in place now and we can start sending some test requests. First, restart the server. Then delete the simon
user with this command:
curl -i -X DELETE http://localhost:4567/users/simon
Output
HTTP/1.1 204 No Content [ ... ]
Now, what happens when we try to access the Simon resource?
curl -i http://localhost:4567/users/simon
HTTP/1.1 410 Gone Content-Type: text/html;charset=utf-8 Content-Length: 0 [More Headers]
Our little API is getting more expressive by the minute!
6.5. PUT /users/:first_name
For this route, we can handle 415 Unsupported Media Type
and 400 Bad Request
. However, I’m not going to show you how. Instead, I want you to do it.
Testing 415
curl -X PUT -i http://localhost:4567/users/thibault -H "Content-Type: application/fake" -d 'Weirdly Formatted Data'
Testing 400
curl -X PUT -i http://localhost:4567/users/thibault -H "Content-Type: application/json" -d '{"first_name":"Tibo"'
6.6. PATCH /users/:first_name
For this route, we can handle 415 Unsupported Media Type
, 404 Not Found
, 410 Gone
and 400 Bad Request
. You can get inspired by what we did for POST /users/:first_name
and GET /users/:first_name
. Below, you will find curl
commands to test if you have correctly implemented each status code.
Testing 415
curl -X PATCH -i http://localhost:4567/users/thibault -H "Content-Type: application/fake" -d 'Weirdly Formatted Data'
Testing 400
curl -X PATCH -i http://localhost:4567/users/thibault -H "Content-Type: application/json" -d '{"first_name":"Tibo"'
Testing 404
curl -X PATCH -i http://localhost:4567/users/mark -H "Content-Type: application/json" -d '{"first_name":"Marc"}'
Testing 410
curl -i -X DELETE http://localhost:4567/users/simon
curl -X PATCH -i http://localhost:4567/users/simon -H "Content-Type: application/json" -d '{"first_name":"Super Simon"}'
6.7. Wrap Up
In this chapter, we’ve seen how to implement errors to let our clients understand exactly what went wrong when something didn’t go as planned.
In the next chapter, we will tackle a sensitive subject: versioning!
- Inheritance
-
< Net::Protocol
< Object
What Is This Library?
This library provides your program functions to access WWW documents via HTTP, Hyper Text Transfer Protocol version 1.1. For
details of HTTP, refer [RFC2616] (www.ietf.org/rfc/rfc2616.txt).
Examples
Getting Document From WWW Server
Example 1: Simple GET+print
require 'net/http' Net::HTTP.get_print 'www.example.com', '/index.html'
Example 2: Simple GET+print by URL
require 'net/http' require 'uri' Net::HTTP.get_print URI.parse('http://www.example.com/index.html')
Example 3: More generic GET+print
require 'net/http' require 'uri' url = URI.parse('http://www.example.com/index.html') res = Net::HTTP.start(url.host, url.port) {|http| http.get('/index.html') } puts res.body
Example 4: More generic GET+print
require 'net/http' url = URI.parse('http://www.example.com/index.html') req = Net::HTTP::Get.new(url.path) res = Net::HTTP.start(url.host, url.port) {|http| http.request(req) } puts res.body
Posting Form Data
require 'net/http' require 'uri' #1: Simple POST res = Net::HTTP.post_form(URI.parse('http://www.example.com/search.cgi'), {'q'=>'ruby', 'max'=>'50'}) puts res.body #2: POST with basic authentication res = Net::HTTP.post_form(URI.parse('http://jack:pass@www.example.com/todo.cgi'), {'from'=>'2005-01-01', 'to'=>'2005-03-31'}) puts res.body #3: Detailed control url = URI.parse('http://www.example.com/todo.cgi') req = Net::HTTP::Post.new(url.path) req.basic_auth 'jack', 'pass' req.set_form_data({'from'=>'2005-01-01', 'to'=>'2005-03-31'}, ';') res = Net::HTTP.new(url.host, url.port).start {|http| http.request(req) } case res when Net::HTTPSuccess, Net::HTTPRedirection # OK else res.error! end
Accessing via Proxy
Net::HTTP.Proxy creates http proxy class.
It has same methods of Net::HTTP but its instances
always connect to proxy, instead of given host.
require 'net/http' proxy_addr = 'your.proxy.host' proxy_port = 8080 : Net::HTTP::Proxy(proxy_addr, proxy_port).start('www.example.com') {|http| # always connect to your.proxy.addr:8080 : }
Since Net::HTTP.Proxy returns Net::HTTP itself when proxy_addr is nil, there‘s
no need to change code if there‘s proxy or not.
There are two additional parameters in Net::HTTP.Proxy which allow to specify proxy
user name and password:
Net::HTTP::Proxy(proxy_addr, proxy_port, proxy_user = nil, proxy_pass = nil)
You may use them to work with authorization-enabled proxies:
require 'net/http' require 'uri' proxy_host = 'your.proxy.host' proxy_port = 8080 uri = URI.parse(ENV['http_proxy']) proxy_user, proxy_pass = uri.userinfo.split(/:/) if uri.userinfo Net::HTTP::Proxy(proxy_host, proxy_port, proxy_user, proxy_pass).start('www.example.com') {|http| # always connect to your.proxy.addr:8080 using specified username and password : }
Note that net/http never rely on HTTP_PROXY environment variable. If you
want to use proxy, set it explicitly.
Following Redirection
require 'net/http' require 'uri' def fetch(uri_str, limit = 10) # You should choose better exception. raise ArgumentError, 'HTTP redirect too deep' if limit == 0 response = Net::HTTP.get_response(URI.parse(uri_str)) case response when Net::HTTPSuccess then response when Net::HTTPRedirection then fetch(response['location'], limit - 1) else response.error! end end print fetch('http://www.ruby-lang.org')
Net::HTTPSuccess and Net::HTTPRedirection is a HTTPResponse class. All HTTPResponse objects belong to its own
response class which indicate HTTP result status.
For details of response classes, see section «HTTP Response Classes».
Basic Authentication
require 'net/http' Net::HTTP.start('www.example.com') {|http| req = Net::HTTP::Get.new('/secret-page.html') req.basic_auth 'account', 'password' response = http.request(req) print response.body }
HTTP Request Classes
Here is HTTP request class hierarchy.
Net::HTTPRequest Net::HTTP::Get Net::HTTP::Head Net::HTTP::Post Net::HTTP::Put Net::HTTP::Proppatch Net::HTTP::Lock Net::HTTP::Unlock Net::HTTP::Options Net::HTTP::Propfind Net::HTTP::Delete Net::HTTP::Move Net::HTTP::Copy Net::HTTP::Mkcol Net::HTTP::Trace
HTTP Response Classes
Here is HTTP response class hierarchy. All classes
are defined in Net module.
HTTPResponse HTTPUnknownResponse HTTPInformation # 1xx HTTPContinue # 100 HTTPSwitchProtocl # 101 HTTPSuccess # 2xx HTTPOK # 200 HTTPCreated # 201 HTTPAccepted # 202 HTTPNonAuthoritativeInformation # 203 HTTPNoContent # 204 HTTPResetContent # 205 HTTPPartialContent # 206 HTTPRedirection # 3xx HTTPMultipleChoice # 300 HTTPMovedPermanently # 301 HTTPFound # 302 HTTPSeeOther # 303 HTTPNotModified # 304 HTTPUseProxy # 305 HTTPTemporaryRedirect # 307 HTTPClientError # 4xx HTTPBadRequest # 400 HTTPUnauthorized # 401 HTTPPaymentRequired # 402 HTTPForbidden # 403 HTTPNotFound # 404 HTTPMethodNotAllowed # 405 HTTPNotAcceptable # 406 HTTPProxyAuthenticationRequired # 407 HTTPRequestTimeOut # 408 HTTPConflict # 409 HTTPGone # 410 HTTPLengthRequired # 411 HTTPPreconditionFailed # 412 HTTPRequestEntityTooLarge # 413 HTTPRequestURITooLong # 414 HTTPUnsupportedMediaType # 415 HTTPRequestedRangeNotSatisfiable # 416 HTTPExpectationFailed # 417 HTTPServerError # 5xx HTTPInternalServerError # 500 HTTPNotImplemented # 501 HTTPBadGateway # 502 HTTPServiceUnavailable # 503 HTTPGatewayTimeOut # 504 HTTPVersionNotSupported # 505
Switching Net::HTTP versions
You can use net/http.rb 1.1 features (bundled with Ruby 1.6) by calling HTTP.version_1_1. Calling Net::HTTP.version_1_2 allows you to use 1.2
features again.
# example Net::HTTP.start {|http1| ...(http1 has 1.2 features)... } Net::HTTP.version_1_1 Net::HTTP.start {|http2| ...(http2 has 1.1 features)... } Net::HTTP.version_1_2 Net::HTTP.start {|http3| ...(http3 has 1.2 features)... }
This function is NOT thread-safe.
Classes & Modules
- Net::HTTP::Copy
- Net::HTTP::Delete
- Net::HTTP::Get
- Net::HTTP::Head
- Net::HTTP::Lock
- Net::HTTP::Mkcol
- Net::HTTP::Move
- Net::HTTP::Options
- Net::HTTP::Post
- Net::HTTP::Propfind
- Net::HTTP::Proppatch
- Net::HTTP::Put
- Net::HTTP::Trace
- Net::HTTP::Unlock
Attributes
Name | Visibility | R/W | Description |
---|---|---|---|
address | public | R |
The host name to connect to.
|
close_on_empty_response | public | RW | |
open_timeout | public | RW |
Seconds to wait until connection is opened. If the HTTP object cannot open a connection in this many seconds, it raises a TimeoutError exception. |
port | public | R | The port number to connect to. |
proxy_address | public | R | |
proxy_pass | public | R | |
proxy_port | public | R | |
proxy_user | public | R | |
read_timeout | public | R |
Seconds to wait until reading one block (by one read(2) call). If the HTTP object cannot open a connection in this many seconds, it raises a TimeoutError exception. |
Aliases
Method | Alias | Description |
---|---|---|
new | → newobj | |
request_put | → put2 | |
version_1_1? | → is_version_1_1? | |
version_1_2? | → is_version_1_2? |
Methods
Class
Visibility | Signature |
---|---|
public | Proxy (p_addr, p_port = nil, p_user = nil, p_pass = nil) |
public | default_port () |
public | get (uri_or_host, path = nil, port = nil) |
public | get_print (uri_or_host, path = nil, port = nil) |
public | get_response (uri_or_host, path = nil, port = nil, &block) |
public | http_default_port () |
public | https_default_port () |
public | new (address, port = nil) |
public | new (address, port = nil, p_addr = nil, p_port = nil, p_user = nil, p_pass = nil) |
public | post_form (url, params) |
public | proxy_class? () |
public | ssl_context_accessor (name) |
public | start (address, port = nil, p_addr = nil, p_port = nil, p_user = nil, p_pass = nil) {|+http+| …} |
public | version_1_1 () |
public | version_1_1? () |
public | version_1_2 () |
public | version_1_2? () |
Instance
Visibility | Signature |
---|---|
public | active? () |
public | copy (path, initheader = nil) |
public | delete (path, initheader = {‘Depth’ => ‘Infinity’}) |
public | finish () |
public | get (path, initheader = nil, dest = nil) {|+body_segment+| …} |
public | get2 (path, initheader = nil) |
public | head (path, initheader = nil) |
public | head2 (path, initheader = nil, &block) |
public | inspect () |
public | lock (path, body, initheader = nil) |
public | mkcol (path, body = nil, initheader = nil) |
public | move (path, initheader = nil) |
public | options (path, initheader = nil) |
public | peer_cert () |
public | post (path, data, initheader = nil, dest = nil) {|+body_segment+| …} |
public | post2 (path, data, initheader = nil) |
public | propfind (path, body = nil, initheader = {‘Depth’ => ‘0’}) |
public | proppatch (path, body, initheader = nil) |
public | proxy? () |
public | proxy_address () |
public | proxy_pass () |
public | proxy_port () |
public | proxy_user () |
public | proxyaddr () |
public | proxyport () |
public | read_timeout= (sec) |
public | request (req, body = nil) {|+response+| …} |
public | request_get (path, initheader = nil) {|+response+| …} |
public | request_head (path, initheader = nil, &block) |
public | request_post (path, data, initheader = nil) {|+response+| …} |
public | send_request (name, path, data = nil, header = nil) |
public | set_debug_output (output) |
public | ssl_timeout () |
public | ssl_timeout= (sec) |
public | start ( {|http| …} |
public | started? () |
public | timeout= (sec) |
public | trace (path, initheader = nil) |
public | unlock (path, body, initheader = nil) |
public | use_ssl () |
public | use_ssl= (flag) |
public | use_ssl? () |
public | use_ssl? () |
Class Method Detail
Proxy(p_addr, p_port = nil, p_user = nil, p_pass = nil)
Creates an HTTP proxy class. Arguments are
address/port of proxy host and username/password if authorization on proxy
server is required. You can replace the HTTP class
with created proxy class.
If ADDRESS is nil, this method returns self (Net::HTTP).
# Example proxy_class = Net::HTTP::Proxy('proxy.example.com', 8080) : proxy_class.start('www.ruby-lang.org') {|http| # connecting proxy.foo.org:8080 : }
default_port()
The default port to use for HTTP requests; defaults
to 80.
get(uri_or_host, path = nil, port = nil)
Send a GET request to the target and return
the response as a string. The target can either be specified as
(uri), or as (host, path, port = 80);
so:
print Net::HTTP.get(URI.parse('http://www.example.com/index.html'))
or:
print Net::HTTP.get('www.example.com', '/index.html')
get_print(uri_or_host, path = nil, port = nil)
Get body from target and output it to
+$stdout+. The target can either be specified as (uri), or as
(host, path, port = 80); so:
Net::HTTP.get_print URI.parse('http://www.example.com/index.html')
or:
Net::HTTP.get_print 'www.example.com', '/index.html'
get_response(uri_or_host, path = nil, port = nil, &block)
Send a GET request to the target and return
the response as a Net::HTTPResponse object.
The target can either be specified as (uri), or as (host,
path, port = 80); so:
res = Net::HTTP.get_response(URI.parse('http://www.example.com/index.html')) print res.body
or:
res = Net::HTTP.get_response('www.example.com', '/index.html') print res.body
http_default_port()
The default port to use for HTTP requests; defaults
to 80.
https_default_port()
The default port to use for HTTPS requests; defaults to 443.
new(address, port = nil)
Creates a new Net::HTTP object for the specified address.
This method does not open the TCP connection.
new(address, port = nil, p_addr = nil, p_port = nil, p_user = nil, p_pass = nil)
Creates a new Net::HTTP object. If proxy_addr is given,
creates an Net::HTTP object with proxy support.
This method does not open the TCP connection.
post_form(url, params)
Posts HTML form data to the URL. Form data must be represented as
a Hash of String to
String, e.g:
{ "cmd" => "search", "q" => "ruby", "max" => "50" }
This method also does Basic Authentication iff URL.user exists.
Example:
require 'net/http' require 'uri' HTTP.post_form URI.parse('http://www.example.com/search.cgi'), { "q" => "ruby", "max" => "50" }
proxy_class?()
returns true if self is a class which was created by HTTP::Proxy.
ssl_context_accessor(name)
start(address, port = nil, p_addr = nil, p_port = nil, p_user = nil, p_pass = nil) {|+http+| …}
creates a new Net::HTTP object and opens its TCP connection and HTTP session. If the optional block is given, the
newly created Net::HTTP object is passed to it and
closed when the block finishes. In this case, the return value of this
method is the return value of the block. If no block is given, the return
value of this method is the newly created Net::HTTP
object itself, and the caller is responsible for closing it upon
completion.
version_1_1()
Turns on net/http 1.1 (ruby 1.6) features. Defaults to OFF in ruby 1.8.
version_1_1?()
true if net/http is in version 1.1 compatible mode. Defaults to true.
version_1_2()
Turns on net/http 1.2 (ruby 1.8) features. Defaults to ON in ruby 1.8.
I strongly recommend to call this method always.
require 'net/http' Net::HTTP.version_1_2
version_1_2?()
true if net/http is in version 1.2 mode. Defaults to true.
Instance Method Detail
active?()
Alias for started?
copy(path, initheader = nil)
Sends a COPY request to the path
and gets a response, as an HTTPResponse
object.
delete(path, initheader = {‘Depth’ => ‘Infinity’})
Sends a DELETE request to the path
and gets a response, as an HTTPResponse
object.
finish()
Finishes HTTP session and closes TCP connection.
Raises IOError if not started.
get(path, initheader = nil, dest = nil) {|+body_segment+| …}
Gets data from path on the connected-to host. header must
be a Hash like { ‘Accept’ =>
’*/*’, … }.
In version 1.1 (ruby 1.6), this method returns a pair of objects, a Net::HTTPResponse object and the entity body
string. In version 1.2 (ruby 1.8), this method returns a Net::HTTPResponse object.
If called with a block, yields each fragment of the entity body in turn as
a string as it is read from the socket. Note that in this case, the
returned response object will not contain a (meaningful) body.
dest argument is obsolete. It still works but you must not use it.
In version 1.1, this method might raise an exception for 3xx (redirect). In
this case you can get a HTTPResponse object by
«anException.response».
In version 1.2, this method never raises exception.
# version 1.1 (bundled with Ruby 1.6) response, body = http.get('/index.html') # version 1.2 (bundled with Ruby 1.8 or later) response = http.get('/index.html') # using block File.open('result.txt', 'w') {|f| http.get('/~foo/') do |str| f.write str end }
get2(path, initheader = nil)
head(path, initheader = nil)
Gets only the header from path on the connected-to host.
header is a Hash like {
‘Accept’ => ’*/*’, … }.
This method returns a Net::HTTPResponse
object.
In version 1.1, this method might raise an exception for 3xx (redirect). On
the case you can get a HTTPResponse object by
«anException.response». In version 1.2, this method never raises
an exception.
response = nil Net::HTTP.start('some.www.server', 80) {|http| response = http.head('/index.html') } p response['content-type']
head2(path, initheader = nil, &block)
inspect()
lock(path, body, initheader = nil)
Sends a LOCK request to the path
and gets a response, as an HTTPResponse
object.
mkcol(path, body = nil, initheader = nil)
Sends a MKCOL request to the path
and gets a response, as an HTTPResponse
object.
move(path, initheader = nil)
Sends a MOVE request to the path
and gets a response, as an HTTPResponse
object.
options(path, initheader = nil)
Sends a OPTIONS request to the
path and gets a response, as an HTTPResponse object.
peer_cert()
post(path, data, initheader = nil, dest = nil) {|+body_segment+| …}
Posts data (must be a String) to
path. header must be a Hash
like { ‘Accept’ => ’*/*’, … }.
In version 1.1 (ruby 1.6), this method returns a pair of objects, a Net::HTTPResponse object and an entity body
string. In version 1.2 (ruby 1.8), this method returns a Net::HTTPResponse object.
If called with a block, yields each fragment of the entity body in turn as
a string as it are read from the socket. Note that in this case, the
returned response object will not contain a (meaningful) body.
dest argument is obsolete. It still works but you must not use it.
In version 1.1, this method might raise an exception for 3xx (redirect). In
this case you can get an HTTPResponse object by
«anException.response». In version 1.2, this method never raises
exception.
# version 1.1 response, body = http.post('/cgi-bin/search.rb', 'query=foo') # version 1.2 response = http.post('/cgi-bin/search.rb', 'query=foo') # using block File.open('result.txt', 'w') {|f| http.post('/cgi-bin/search.rb', 'query=foo') do |str| f.write str end }
You should set Content-Type: header field for POST. If no Content-Type:
field given, this method uses «application/x-www-form-urlencoded»
by default.
post2(path, data, initheader = nil)
propfind(path, body = nil, initheader = {‘Depth’ => ‘0’})
Sends a PROPFIND request to the
path and gets a response, as an HTTPResponse object.
proppatch(path, body, initheader = nil)
Sends a PROPPATCH request to the
path and gets a response, as an HTTPResponse object.
proxy?()
True if self is a HTTP proxy class.
proxy_address()
Address of proxy host. If self does not use a proxy, nil.
proxy_pass()
User password for accessing proxy. If self does not use a proxy, nil.
proxy_port()
Port number of proxy host. If self does not use a proxy, nil.
proxy_user()
User name for accessing proxy. If self does not use a proxy, nil.
proxyaddr()
proxyport()
read_timeout=(sec)
Setter for the read_timeout attribute.
request(req, body = nil) {|+response+| …}
request_get(path, initheader = nil) {|+response+| …}
Sends a GET request to the path
and gets a response, as an HTTPResponse
object.
When called with a block, yields an HTTPResponse object. The body of this response
will not have been read yet; the caller can process it using HTTPResponse#read_body, if desired.
Returns the response.
This method never raises Net::* exceptions.
response = http.request_get('/index.html') # The entity body is already read here. p response['content-type'] puts response.body # using block http.request_get('/index.html') {|response| p response['content-type'] response.read_body do |str| # read body now print str end }
request_head(path, initheader = nil, &block)
Sends a HEAD request to the path
and gets a response, as an HTTPResponse
object.
Returns the response.
This method never raises Net::* exceptions.
response = http.request_head('/index.html') p response['content-type']
request_post(path, data, initheader = nil) {|+response+| …}
Sends a POST request to the path
and gets a response, as an HTTPResponse
object.
When called with a block, yields an HTTPResponse object. The body of this response
will not have been read yet; the caller can process it using HTTPResponse#read_body, if desired.
Returns the response.
This method never raises Net::* exceptions.
# example response = http.request_post('/cgi-bin/nice.rb', 'datadatadata...') p response.status puts response.body # body is already read # using block http.request_post('/cgi-bin/nice.rb', 'datadatadata...') {|response| p response.status p response['content-type'] response.read_body do |str| # read body now print str end }
send_request(name, path, data = nil, header = nil)
Sends an HTTP request to the HTTP
server. This method also sends DATA string if DATA is given.
Returns a HTTPResponse object.
This method never raises Net::* exceptions.
response = http.send_request('GET', '/index.html') puts response.body
set_debug_output(output)
WARNING This method causes serious security hole. Never use this
method in production code.
Set an output stream for debugging.
http = Net::HTTP.new http.set_debug_output $stderr http.start { .... }
ssl_timeout()
ssl_timeout=(sec)
start( {|http| …}
Opens TCP connection and HTTP session.
When this method is called with block, gives a HTTP
object to the block and closes the TCP connection / HTTP session after the block executed.
When called with a block, returns the return value of the block; otherwise,
returns self.
started?()
returns true if the HTTP session is started.
timeout=(sec)
trace(path, initheader = nil)
Sends a TRACE request to the path
and gets a response, as an HTTPResponse
object.
unlock(path, body, initheader = nil)
Sends a UNLOCK request to the path
and gets a response, as an HTTPResponse
object.
use_ssl()
use_ssl=(flag)
Turn on/off SSL. This flag must be set before starting session. If you
change use_ssl value after session started,
a Net::HTTP object raises IOError.
use_ssl?()
use_ssl?()
returns true if use SSL/TLS with HTTP.