Ruby http error

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.

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 with 1 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!

# frozen_string_literal: false # This class is the base class for Net::HTTP response classes. # # == About the Examples # # :include: doc/net-http/examples.rdoc # # == Returned Responses # # Method Net::HTTP.get_response returns # an instance of one of the subclasses of Net::HTTPResponse: # # Net::HTTP.get_response(uri) # # => #<Net::HTTPOK 200 OK readbody=true> # Net::HTTP.get_response(hostname, ‘/nosuch’) # # => #<Net::HTTPNotFound 404 Not Found readbody=true> # # As does method Net::HTTP#request: # # req = Net::HTTP::Get.new(uri) # Net::HTTP.start(hostname) do |http| # http.request(req) # end # => #<Net::HTTPOK 200 OK readbody=true> # # Class Net::HTTPResponse includes module Net::HTTPHeader, # which provides access to response header values via (among others): # # — Hash-like method <tt>[]</tt>. # — Specific reader methods, such as +content_type+. # # Examples: # # res = Net::HTTP.get_response(uri) # => #<Net::HTTPOK 200 OK readbody=true> # res[‘Content-Type’] # => «text/html; charset=UTF-8» # res.content_type # => «text/html» # # == Response Subclasses # # Class Net::HTTPResponse has a subclass for each # {HTTP status code}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes]. # You can look up the response class for a given code: # # Net::HTTPResponse::CODE_TO_OBJ[‘200’] # => Net::HTTPOK # Net::HTTPResponse::CODE_TO_OBJ[‘400’] # => Net::HTTPBadRequest # Net::HTTPResponse::CODE_TO_OBJ[‘404’] # => Net::HTTPNotFound # # And you can retrieve the status code for a response object: # # Net::HTTP.get_response(uri).code # => «200» # Net::HTTP.get_response(hostname, ‘/nosuch’).code # => «404» # # The response subclasses (indentation shows class hierarchy): # # — Net::HTTPUnknownResponse (for unhandled HTTP extensions). # # — Net::HTTPInformation: # # — Net::HTTPContinue (100) # — Net::HTTPSwitchProtocol (101) # — Net::HTTPProcessing (102) # — Net::HTTPEarlyHints (103) # # — Net::HTTPSuccess: # # — Net::HTTPOK (200) # — Net::HTTPCreated (201) # — Net::HTTPAccepted (202) # — Net::HTTPNonAuthoritativeInformation (203) # — Net::HTTPNoContent (204) # — Net::HTTPResetContent (205) # — Net::HTTPPartialContent (206) # — Net::HTTPMultiStatus (207) # — Net::HTTPAlreadyReported (208) # — Net::HTTPIMUsed (226) # # — Net::HTTPRedirection: # # — Net::HTTPMultipleChoices (300) # — Net::HTTPMovedPermanently (301) # — Net::HTTPFound (302) # — Net::HTTPSeeOther (303) # — Net::HTTPNotModified (304) # — Net::HTTPUseProxy (305) # — Net::HTTPTemporaryRedirect (307) # — Net::HTTPPermanentRedirect (308) # # — Net::HTTPClientError: # # — Net::HTTPBadRequest (400) # — Net::HTTPUnauthorized (401) # — Net::HTTPPaymentRequired (402) # — Net::HTTPForbidden (403) # — Net::HTTPNotFound (404) # — Net::HTTPMethodNotAllowed (405) # — Net::HTTPNotAcceptable (406) # — Net::HTTPProxyAuthenticationRequired (407) # — Net::HTTPRequestTimeOut (408) # — Net::HTTPConflict (409) # — Net::HTTPGone (410) # — Net::HTTPLengthRequired (411) # — Net::HTTPPreconditionFailed (412) # — Net::HTTPRequestEntityTooLarge (413) # — Net::HTTPRequestURITooLong (414) # — Net::HTTPUnsupportedMediaType (415) # — Net::HTTPRequestedRangeNotSatisfiable (416) # — Net::HTTPExpectationFailed (417) # — Net::HTTPMisdirectedRequest (421) # — Net::HTTPUnprocessableEntity (422) # — Net::HTTPLocked (423) # — Net::HTTPFailedDependency (424) # — Net::HTTPUpgradeRequired (426) # — Net::HTTPPreconditionRequired (428) # — Net::HTTPTooManyRequests (429) # — Net::HTTPRequestHeaderFieldsTooLarge (431) # — Net::HTTPUnavailableForLegalReasons (451) # # — Net::HTTPServerError: # # — Net::HTTPInternalServerError (500) # — Net::HTTPNotImplemented (501) # — Net::HTTPBadGateway (502) # — Net::HTTPServiceUnavailable (503) # — Net::HTTPGatewayTimeOut (504) # — Net::HTTPVersionNotSupported (505) # — Net::HTTPVariantAlsoNegotiates (506) # — Net::HTTPInsufficientStorage (507) # — Net::HTTPLoopDetected (508) # — Net::HTTPNotExtended (510) # — Net::HTTPNetworkAuthenticationRequired (511) # # There is also the Net::HTTPBadResponse exception which is raised when # there is a protocol error. # class Net::HTTPResponse class << self # true if the response has a body. def body_permitted? self::HAS_BODY end def exception_type # :nodoc: internal use only self::EXCEPTION_TYPE end def read_new(sock) #:nodoc: internal use only httpv, code, msg = read_status_line(sock) res = response_class(code).new(httpv, code, msg) each_response_header(sock) do |k,v| res.add_field k, v end res end private def read_status_line(sock) str = sock.readline m = /AHTTP(?:/(d+.d+))?s+(ddd)(?:s+(.*))?z/in.match(str) or raise Net::HTTPBadResponse, «wrong status line: #{str.dump}« m.captures end def response_class(code) CODE_TO_OBJ[code] or CODE_CLASS_TO_OBJ[code[0,1]] or Net::HTTPUnknownResponse end def each_response_header(sock) key = value = nil while true line = sock.readuntil(«n«, true).sub(/s+z/, ») break if line.empty? if line[0] == ?s or line[0] == ?t and value value << ‘ ‘ unless value.empty? value << line.strip else yield key, value if key key, value = line.strip.split(/s*:s*/, 2) raise Net::HTTPBadResponse, ‘wrong header line format’ if value.nil? end end yield key, value if key end end # next is to fix bug in RDoc, where the private inside class << self # spills out. public include Net::HTTPHeader def initialize(httpv, code, msg) #:nodoc: internal use only @http_version = httpv @code = code @message = msg initialize_http_header nil @body = nil @read = false @uri = nil @decode_content = false @body_encoding = false @ignore_eof = true end # The HTTP version supported by the server. attr_reader :http_version # The HTTP result code string. For example, ‘302’. You can also # determine the response type by examining which response subclass # the response object is an instance of. attr_reader :code # The HTTP result message sent by the server. For example, ‘Not Found’. attr_reader :message alias msg message # :nodoc: obsolete # The URI used to fetch this response. The response URI is only available # if a URI was used to create the request. attr_reader :uri # Set to true automatically when the request did not contain an # Accept-Encoding header from the user. attr_accessor :decode_content # The encoding to use for the response body. If Encoding, use that encoding. # If other true value, attempt to detect the appropriate encoding, and use # that. attr_reader :body_encoding # Set the encoding to use for the response body. If given a String, find # the related Encoding. def body_encoding=(value) value = Encoding.find(value) if value.is_a?(String) @body_encoding = value end # Whether to ignore EOF when reading bodies with a specified Content-Length # header. attr_accessor :ignore_eof def inspect «#<#{self.class} #{@code} #{@message} readbody=#{@read} end # # response <-> exception relationship # def code_type #:nodoc: self.class end def error! #:nodoc: message = @code message += ‘ ‘ + @message.dump if @message raise error_type().new(message, self) end def error_type #:nodoc: self.class::EXCEPTION_TYPE end # Raises an HTTP error if the response is not 2xx (success). def value error! unless self.kind_of?(Net::HTTPSuccess) end def uri= uri # :nodoc: @uri = uri.dup if uri end # # header (for backward compatibility only; DO NOT USE) # def response #:nodoc: warn «Net::HTTPResponse#response is obsolete», uplevel: 1 if $VERBOSE self end def header #:nodoc: warn «Net::HTTPResponse#header is obsolete», uplevel: 1 if $VERBOSE self end def read_header #:nodoc: warn «Net::HTTPResponse#read_header is obsolete», uplevel: 1 if $VERBOSE self end # # body # def reading_body(sock, reqmethodallowbody) #:nodoc: internal use only @socket = sock @body_exist = reqmethodallowbody && self.class.body_permitted? begin yield self.body # ensure to read body ensure @socket = nil end end # Gets the entity body returned by the remote HTTP server. # # If a block is given, the body is passed to the block, and # the body is provided in fragments, as it is read in from the socket. # # If +dest+ argument is given, response is read into that variable, # with <code>dest#<<</code> method (it could be String or IO, or any # other object responding to <code><<</code>). # # Calling this method a second or subsequent time for the same # HTTPResponse object will return the value already read. # # http.request_get(‘/index.html’) {|res| # puts res.read_body # } # # http.request_get(‘/index.html’) {|res| # p res.read_body.object_id # 538149362 # p res.read_body.object_id # 538149362 # } # # # using iterator # http.request_get(‘/index.html’) {|res| # res.read_body do |segment| # print segment # end # } # def read_body(dest = nil, &block) if @read raise IOError, «#{self.class}#read_body called twice» if dest or block return @body end to = procdest(dest, block) stream_check if @body_exist read_body_0 to @body = to else @body = nil end @read = true case enc = @body_encoding when Encoding, false, nil # Encoding: force given encoding # false/nil: do not force encoding else # other value: detect encoding from body enc = detect_encoding(@body) end @body.force_encoding(enc) if enc @body end # Returns the full entity body. # # Calling this method a second or subsequent time will return the # string already read. # # http.request_get(‘/index.html’) {|res| # puts res.body # } # # http.request_get(‘/index.html’) {|res| # p res.body.object_id # 538149362 # p res.body.object_id # 538149362 # } # def body read_body() end # Because it may be necessary to modify the body, Eg, decompression # this method facilitates that. def body=(value) @body = value end alias entity body #:nodoc: obsolete private # :nodoc: def detect_encoding(str, encoding=nil) if encoding elsif encoding = type_params[‘charset’] elsif encoding = check_bom(str) else encoding = case content_type&.downcase when %r{text/x(?:ht)?ml|application/(?:[^+]++)?xml} /A<xml[ trn]+ version[ trn]*=[ trn]*(?:»[0-9.]+»|'[0-9.]*’)[ trn]+ encoding[ trn]*=[ trn]* (?:»([A-Za-z][A-Za-z0-9._]*)»|'([A-Za-z][A-Za-z0-9._]*)’)/x =~ str encoding = $1 || $2 || Encoding::UTF_8 when %r{text/html.*} sniff_encoding(str) end end return encoding end # :nodoc: def sniff_encoding(str, encoding=nil) # the encoding sniffing algorithm # http://www.w3.org/TR/html5/parsing.html#determining-the-character-encoding if enc = scanning_meta(str) enc # 6. last visited page or something # 7. frequency elsif str.ascii_only? Encoding::US_ASCII elsif str.dup.force_encoding(Encoding::UTF_8).valid_encoding? Encoding::UTF_8 end # 8. implementation-defined or user-specified end # :nodoc: def check_bom(str) case str.byteslice(0, 2) when «xFExFF« return Encoding::UTF_16BE when «xFFxFE« return Encoding::UTF_16LE end if «xEFxBBxBF« == str.byteslice(0, 3) return Encoding::UTF_8 end nil end # :nodoc: def scanning_meta(str) require ‘strscan’ ss = StringScanner.new(str) if ss.scan_until(/<meta[tnfr ]*/) attrs = {} # attribute_list got_pragma = false need_pragma = nil charset = nil # step: Attributes while attr = get_attribute(ss) name, value = *attr next if attrs[name] attrs[name] = true case name when ‘http-equiv’ got_pragma = true if value == ‘content-type’ when ‘content’ encoding = extracting_encodings_from_meta_elements(value) unless charset charset = encoding end need_pragma = true when ‘charset’ need_pragma = false charset = value end end # step: Processing return if need_pragma.nil? return if need_pragma && !got_pragma charset = Encoding.find(charset) rescue nil return unless charset charset = Encoding::UTF_8 if charset == Encoding::UTF_16 return charset # tentative end nil end def get_attribute(ss) ss.scan(/[tnfr /]*/) if ss.peek(1) == ‘>’ ss.getch return nil end name = ss.scan(/[^=tnfr />]*/) name.downcase! raise if name.empty? ss.skip(/[tnfr ]*/) if ss.getch != ‘=’ value = » return [name, value] end ss.skip(/[tnfr ]*/) case ss.peek(1) when ‘»‘ ss.getch value = ss.scan(/[^»]+/) value.downcase! ss.getch when «‘» ss.getch value = ss.scan(/[^’]+/) value.downcase! ss.getch when ‘>’ value = » else value = ss.scan(/[^tnfr >]+/) value.downcase! end [name, value] end def extracting_encodings_from_meta_elements(value) # http://dev.w3.org/html5/spec/fetching-resources.html#algorithm-for-extracting-an-encoding-from-a-meta-element if /charset[tnfr ]*=(?:»([^»]*)»|'([^’]*)’|[«‘]|z|([^tnfr ;]+))/i =~ value return $1 || $2 || $3 end return nil end ## # Checks for a supported Content-Encoding header and yields an Inflate # wrapper for this response’s socket when zlib is present. If the # Content-Encoding is not supported or zlib is missing, the plain socket is # yielded. # # If a Content-Range header is present, a plain socket is yielded as the # bytes in the range may not be a complete deflate block. def inflater # :nodoc: return yield @socket unless Net::HTTP::HAVE_ZLIB return yield @socket unless @decode_content return yield @socket if self[‘content-range’] v = self[‘content-encoding’] case v&.downcase when ‘deflate’, ‘gzip’, ‘x-gzip’ then self.delete ‘content-encoding’ inflate_body_io = Inflater.new(@socket) begin yield inflate_body_io success = true ensure begin inflate_body_io.finish if self[‘content-length’] self[‘content-length’] = inflate_body_io.bytes_inflated.to_s end rescue => err # Ignore #finish’s error if there is an exception from yield raise err if success end end when ‘none’, ‘identity’ then self.delete ‘content-encoding’ yield @socket else yield @socket end end def read_body_0(dest) inflater do |inflate_body_io| if chunked? read_chunked dest, inflate_body_io return end @socket = inflate_body_io clen = content_length() if clen @socket.read clen, dest, @ignore_eof return end clen = range_length() if clen @socket.read clen, dest return end @socket.read_all dest end end ## # read_chunked reads from +@socket+ for chunk-size, chunk-extension, CRLF, # etc. and +chunk_data_io+ for chunk-data which may be deflate or gzip # encoded. # # See RFC 2616 section 3.6.1 for definitions def read_chunked(dest, chunk_data_io) # :nodoc: total = 0 while true line = @socket.readline hexlen = line.slice(/[0-9a-fA-F]+/) or raise Net::HTTPBadResponse, «wrong chunk size line: #{line}« len = hexlen.hex break if len == 0 begin chunk_data_io.read len, dest ensure total += len @socket.read 2 # rn end end until @socket.readline.empty? # none end end def stream_check raise IOError, ‘attempt to read body out of block’ if @socket.closed? end def procdest(dest, block) raise ArgumentError, ‘both arg and block given for HTTP method’ if dest and block if block Net::ReadAdapter.new(block) else dest || » end end ## # Inflater is a wrapper around Net::BufferedIO that transparently inflates # zlib and gzip streams. class Inflater # :nodoc: ## # Creates a new Inflater wrapping +socket+ def initialize socket @socket = socket # zlib with automatic gzip detection @inflate = Zlib::Inflate.new(32 + Zlib::MAX_WBITS) end ## # Finishes the inflate stream. def finish return if @inflate.total_in == 0 @inflate.finish end ## # The number of bytes inflated, used to update the Content-Length of # the response. def bytes_inflated @inflate.total_out end ## # Returns a Net::ReadAdapter that inflates each read chunk into +dest+. # # This allows a large response body to be inflated without storing the # entire body in memory. def inflate_adapter(dest) if dest.respond_to?(:set_encoding) dest.set_encoding(Encoding::ASCII_8BIT) elsif dest.respond_to?(:force_encoding) dest.force_encoding(Encoding::ASCII_8BIT) end block = proc do |compressed_chunk| @inflate.inflate(compressed_chunk) do |chunk| compressed_chunk.clear dest << chunk end end Net::ReadAdapter.new(block) end ## # Reads +clen+ bytes from the socket, inflates them, then writes them to # +dest+. +ignore_eof+ is passed down to Net::BufferedIO#read # # Unlike Net::BufferedIO#read, this method returns more than +clen+ bytes. # At this time there is no way for a user of Net::HTTPResponse to read a # specific number of bytes from the HTTP response body, so this internal # API does not return the same number of bytes as were requested. # # See https://bugs.ruby-lang.org/issues/6492 for further discussion. def read clen, dest, ignore_eof = false temp_dest = inflate_adapter(dest) @socket.read clen, temp_dest, ignore_eof end ## # Reads the rest of the socket, inflates it, then writes it to +dest+. def read_all dest temp_dest = inflate_adapter(dest) @socket.read_all temp_dest end end end
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.

Понравилась статья? Поделить с друзьями:
  • Ruby argument error
  • Ruantiblock update error another instance of update is already running
  • Ru store синтаксическая ошибка
  • Ru store ошибка разбора пакета
  • Ru store ошибка при синтаксическом анализе пакета