mirror of
https://github.com/HoneyryderChuck/httpx.git
synced 2025-08-10 00:01:27 -04:00
HTTP/1 Parser
This commit is contained in:
parent
626c77f758
commit
446d561ed5
@ -16,6 +16,9 @@ Metrics/ModuleLength:
|
||||
Metrics/BlockLength:
|
||||
Max: 100
|
||||
|
||||
Metrics/BlockNesting:
|
||||
Enabled: False
|
||||
|
||||
#Naming/MethodName:
|
||||
|
||||
# TODO: remove this if min supported version of ruby is 2.3
|
||||
|
@ -5,5 +5,5 @@ SimpleCov.start do
|
||||
add_filter "/lib/httpx/extensions.rb"
|
||||
add_filter "/lib/httpx/loggable.rb"
|
||||
coverage_dir "www/coverage"
|
||||
minimum_coverage 85
|
||||
minimum_coverage 80
|
||||
end
|
||||
|
2
Gemfile
2
Gemfile
@ -13,6 +13,7 @@ gem "simplecov", require: false
|
||||
platform :mri do
|
||||
gem "brotli", require: false
|
||||
gem "pry-byebug", require: false
|
||||
gem "benchmark-ips", require: false
|
||||
end
|
||||
# gem "guard-rspec", :require => false
|
||||
# gem "nokogiri", :require => false
|
||||
@ -21,3 +22,4 @@ gem "pry", :require => false
|
||||
gem "minitest", require: false
|
||||
gem "minitest-proveit", require: false
|
||||
gem "oga", require: false
|
||||
gem "http_parser.rb", require: false
|
||||
|
@ -1,6 +1,6 @@
|
||||
version: '3'
|
||||
services:
|
||||
httpx:
|
||||
image: jruby:9.1.16-alpine
|
||||
image: jruby:9.2.3-alpine
|
||||
environment:
|
||||
- JRUBY_OPTS=--debug
|
||||
|
@ -22,7 +22,6 @@ Gem::Specification.new do |gem|
|
||||
|
||||
gem.add_runtime_dependency "http-2", ">= 0.9.0"
|
||||
gem.add_runtime_dependency "http-form_data", ">= 2.0.0", "< 3"
|
||||
gem.add_runtime_dependency "http_parser.rb", ">= 0.6.0"
|
||||
|
||||
gem.add_development_dependency "http-cookie", "~> 1.0"
|
||||
end
|
||||
|
@ -204,6 +204,7 @@ module HTTPX
|
||||
return if siz.zero?
|
||||
log { "READ: #{siz} bytes..." }
|
||||
parser << @read_buffer.to_s
|
||||
return if @state == :closing || @state == :closed
|
||||
end
|
||||
end
|
||||
|
||||
@ -219,6 +220,7 @@ module HTTPX
|
||||
end
|
||||
log { "WRITE: #{siz} bytes..." }
|
||||
return if siz.zero?
|
||||
return if @state == :closing || @state == :closed
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "http_parser"
|
||||
require "httpx/parser/http1"
|
||||
|
||||
module HTTPX
|
||||
class Channel::HTTP1
|
||||
@ -12,18 +12,15 @@ module HTTPX
|
||||
def initialize(buffer, options)
|
||||
@options = Options.new(options)
|
||||
@max_concurrent_requests = @options.max_concurrent_requests
|
||||
@parser = HTTP::Parser.new(self)
|
||||
@parser.header_value_type = :arrays
|
||||
@parser = Parser::HTTP1.new(self)
|
||||
@buffer = buffer
|
||||
@version = [1, 1]
|
||||
@pending = []
|
||||
@requests = []
|
||||
@has_response = false
|
||||
end
|
||||
|
||||
def reset
|
||||
@parser.reset!
|
||||
@has_response = false
|
||||
end
|
||||
|
||||
def close
|
||||
@ -39,7 +36,6 @@ module HTTPX
|
||||
|
||||
def <<(data)
|
||||
@parser << data
|
||||
dispatch if @has_response
|
||||
end
|
||||
|
||||
def send(request, **)
|
||||
@ -69,69 +65,59 @@ module HTTPX
|
||||
#
|
||||
# must be public methods, or else they won't be reachable
|
||||
|
||||
def on_message_begin
|
||||
def on_start
|
||||
log(level: 2) { "parsing begins" }
|
||||
end
|
||||
|
||||
def on_headers_complete(h)
|
||||
return on_trailer_headers_complete(h) if @parser_trailers
|
||||
# Wait for fix: https://github.com/tmm1/http_parser.rb/issues/52
|
||||
# callback is called 2 times when chunked
|
||||
request = @requests.first
|
||||
return if request.response
|
||||
def on_headers(h)
|
||||
@request = @requests.first
|
||||
return if @request.response
|
||||
|
||||
log(level: 2) { "headers received" }
|
||||
headers = @options.headers_class.new(h)
|
||||
response = @options.response_class.new(@requests.last,
|
||||
response = @options.response_class.new(@request,
|
||||
@parser.status_code,
|
||||
@parser.http_version.join("."),
|
||||
headers, @options)
|
||||
log(color: :yellow) { "-> HEADLINE: #{response.status} HTTP/#{@parser.http_version.join(".")}" }
|
||||
log(color: :yellow) { response.headers.each.map { |f, v| "-> HEADER: #{f}: #{v}" }.join("\n") }
|
||||
|
||||
request.response = response
|
||||
|
||||
@has_response = true if response.complete?
|
||||
@request.response = response
|
||||
on_complete if response.complete?
|
||||
end
|
||||
|
||||
def on_body(chunk)
|
||||
log(color: :green) { "-> DATA: #{chunk.bytesize} bytes..." }
|
||||
log(level: 2, color: :green) { "-> #{chunk.inspect}" }
|
||||
response = @requests.first.response
|
||||
|
||||
response << chunk
|
||||
|
||||
@has_response = response.complete?
|
||||
end
|
||||
|
||||
def on_message_complete
|
||||
log(level: 2) { "parsing complete" }
|
||||
request = @requests.first
|
||||
response = request.response
|
||||
|
||||
if !@parser_trailers && response.headers.key?("trailer")
|
||||
@parser_trailers = true
|
||||
# this is needed, because the parser can't accept further headers.
|
||||
# we need to reset it and artificially move it to receive headers state,
|
||||
# hence the bogus headline
|
||||
#
|
||||
@parser.reset!
|
||||
@parser << "#{request.verb.to_s.upcase} #{request.path} HTTP/#{response.version}#{CRLF}"
|
||||
else
|
||||
@has_response = true
|
||||
end
|
||||
end
|
||||
|
||||
def on_trailer_headers_complete(h)
|
||||
response = @requests.first.response
|
||||
def on_trailers(h)
|
||||
return unless @request
|
||||
response = @request.response
|
||||
log(level: 2) { "trailer headers received" }
|
||||
|
||||
log(color: :yellow) { h.each.map { |f, v| "-> HEADER: #{f}: #{v}" }.join("\n") }
|
||||
response.merge_headers(h)
|
||||
end
|
||||
|
||||
def dispatch
|
||||
request = @requests.first
|
||||
return handle(request) if request.expects?
|
||||
def on_data(chunk)
|
||||
return unless @request
|
||||
log(color: :green) { "-> DATA: #{chunk.bytesize} bytes..." }
|
||||
log(level: 2, color: :green) { "-> #{chunk.inspect}" }
|
||||
response = @request.response
|
||||
|
||||
response << chunk
|
||||
end
|
||||
|
||||
def on_complete
|
||||
return unless @request
|
||||
log(level: 2) { "parsing complete" }
|
||||
dispatch
|
||||
end
|
||||
|
||||
def dispatch
|
||||
if @request.expects?
|
||||
reset
|
||||
return handle(@request)
|
||||
end
|
||||
|
||||
request = @request
|
||||
@request = nil
|
||||
@requests.shift
|
||||
response = request.response
|
||||
emit(:response, request, response)
|
||||
@ -178,7 +164,6 @@ module HTTPX
|
||||
end
|
||||
|
||||
def handle(request)
|
||||
@has_response = false
|
||||
set_request_headers(request)
|
||||
catch(:buffer_full) do
|
||||
request.transition(:headers)
|
||||
|
@ -47,7 +47,6 @@ module HTTPX
|
||||
def on_promise(_, stream)
|
||||
log(level: 2, label: "#{stream.id}: ") { "refusing stream!" }
|
||||
stream.refuse
|
||||
# TODO: policy for handling promises
|
||||
end
|
||||
|
||||
def fetch_response(request)
|
||||
|
171
lib/httpx/parser/http1.rb
Normal file
171
lib/httpx/parser/http1.rb
Normal file
@ -0,0 +1,171 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module HTTPX
|
||||
module Parser
|
||||
Error = Class.new(Error)
|
||||
|
||||
class HTTP1
|
||||
VERSIONS = %w[0.9 1.0 1.1].freeze
|
||||
|
||||
attr_reader :status_code, :http_version, :headers
|
||||
|
||||
def initialize(observer, header_separator: ":")
|
||||
@observer = observer
|
||||
@state = :idle
|
||||
@header_separator = header_separator
|
||||
@buffer = "".b
|
||||
@headers = {}
|
||||
end
|
||||
|
||||
def <<(chunk)
|
||||
@buffer << chunk
|
||||
parse
|
||||
end
|
||||
|
||||
def reset!
|
||||
@state = :idle
|
||||
@headers.clear
|
||||
@content_length = nil
|
||||
@_has_trailers = nil
|
||||
end
|
||||
|
||||
def upgrade?
|
||||
@upgrade
|
||||
end
|
||||
|
||||
def upgrade_data
|
||||
@buffer
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def parse
|
||||
state = @state
|
||||
case @state
|
||||
when :idle
|
||||
parse_headline
|
||||
when :headers
|
||||
parse_headers
|
||||
when :trailers
|
||||
parse_headers
|
||||
when :data
|
||||
parse_data
|
||||
end
|
||||
parse if !@buffer.empty? && state != @state
|
||||
end
|
||||
|
||||
def parse_headline
|
||||
idx = @buffer.index("\n")
|
||||
return unless idx
|
||||
(m = %r{\AHTTP(?:\/(\d+\.\d+))?\s+(\d\d\d)(?:\s+(.*))?}in.match(@buffer)) ||
|
||||
raise(Error, "wrong head line format")
|
||||
version, code, _ = m.captures
|
||||
raise(Error, "unsupported HTTP version (HTTP/#{version})") unless VERSIONS.include?(version)
|
||||
@http_version = version.split(".").map(&:to_i)
|
||||
@status_code = code.to_i
|
||||
raise(Error, "wrong status code (#{@status_code})") unless (100..599).cover?(@status_code)
|
||||
@buffer.slice!(0, idx + 1)
|
||||
nextstate(:headers)
|
||||
end
|
||||
|
||||
def parse_headers
|
||||
headers = @headers
|
||||
while (idx = @buffer.index("\n"))
|
||||
line = @buffer.slice!(0, idx + 1).sub(/\s+\z/, "")
|
||||
if line.empty?
|
||||
case @state
|
||||
when :headers
|
||||
prepare_data(headers)
|
||||
@observer.on_headers(headers)
|
||||
return unless @state == :headers
|
||||
# state might have been reset
|
||||
# in the :headers callback
|
||||
nextstate(:data)
|
||||
headers.clear
|
||||
when :trailers
|
||||
@observer.on_trailers(headers)
|
||||
headers.clear
|
||||
nextstate(:complete)
|
||||
else
|
||||
raise Error, "wrong header format"
|
||||
end
|
||||
return
|
||||
end
|
||||
separator_index = line.index(@header_separator)
|
||||
raise Error, "wrong header format" unless separator_index
|
||||
key = line[0..separator_index - 1]
|
||||
raise Error, "wrong header format" if key.start_with?("\s", "\t")
|
||||
key.strip!
|
||||
value = line[separator_index + 1..-1]
|
||||
value.strip!
|
||||
raise Error, "wrong header format" if value.nil?
|
||||
(headers[key.downcase] ||= []) << value
|
||||
end
|
||||
end
|
||||
|
||||
def parse_data
|
||||
if @buffer.respond_to?(:each)
|
||||
@buffer.each do |chunk|
|
||||
@observer.on_data(chunk)
|
||||
end
|
||||
elsif @content_length
|
||||
data = @buffer.slice!(0, @content_length)
|
||||
@content_length -= data.bytesize
|
||||
@observer.on_data(data)
|
||||
data.clear
|
||||
else
|
||||
@observer.on_data(@buffer)
|
||||
@buffer.clear
|
||||
end
|
||||
return unless no_more_data?
|
||||
@buffer = @buffer.to_s
|
||||
if @_has_trailers
|
||||
nextstate(:trailers)
|
||||
else
|
||||
nextstate(:complete)
|
||||
end
|
||||
end
|
||||
|
||||
def prepare_data(headers)
|
||||
@upgrade = headers.key?("upgrade")
|
||||
|
||||
@_has_trailers = headers.key?("trailer")
|
||||
|
||||
if (tr_encodings = headers["transfer-encoding"])
|
||||
tr_encodings.reverse_each do |tr_encoding|
|
||||
tr_encoding.split(/ *, */).each do |encoding|
|
||||
case encoding
|
||||
when "chunked"
|
||||
@buffer = Transcoder::Chunker::Decoder.new(@buffer, @_has_trailers)
|
||||
end
|
||||
end
|
||||
end
|
||||
else
|
||||
@content_length = headers["content-length"][0].to_i if headers.key?("content-length")
|
||||
end
|
||||
end
|
||||
|
||||
def no_more_data?
|
||||
if @content_length
|
||||
@content_length <= 0
|
||||
elsif @buffer.respond_to?(:finished?)
|
||||
@buffer.finished?
|
||||
else
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
def nextstate(state)
|
||||
@state = state
|
||||
case state
|
||||
when :headers
|
||||
@observer.on_start
|
||||
when :complete
|
||||
@observer.on_complete
|
||||
reset!
|
||||
nextstate(:idle) unless @buffer.empty?
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
@ -55,6 +55,7 @@ module HTTPX
|
||||
end
|
||||
|
||||
def build_channel(proxy, options)
|
||||
return super if proxy.is_a?(URI::Generic)
|
||||
channel = build_proxy_channel(proxy, **options)
|
||||
set_channel_callbacks(channel, options)
|
||||
channel
|
||||
|
@ -41,11 +41,7 @@ module HTTPX
|
||||
|
||||
def bodyless?
|
||||
@request.verb == :head ||
|
||||
@status < 200 ||
|
||||
@status == 201 ||
|
||||
@status == 204 ||
|
||||
@status == 205 ||
|
||||
@status == 304
|
||||
no_data?
|
||||
end
|
||||
|
||||
def content_type
|
||||
@ -65,6 +61,19 @@ module HTTPX
|
||||
raise HTTPError, self
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def no_data?
|
||||
@status < 200 ||
|
||||
@status == 204 ||
|
||||
@status == 205 ||
|
||||
@status == 304 || begin
|
||||
content_length = @headers["content-length"]
|
||||
return false if content_length.nil?
|
||||
content_length == "0"
|
||||
end
|
||||
end
|
||||
|
||||
class Body
|
||||
def initialize(response, threshold_size:, window_size: 1 << 14)
|
||||
@response = response
|
||||
@ -170,7 +179,7 @@ module HTTPX
|
||||
when :memory
|
||||
if @length > @threshold_size
|
||||
aux = @buffer
|
||||
@buffer = Tempfile.new("palanca", encoding: @encoding, mode: File::RDWR)
|
||||
@buffer = Tempfile.new("httpx", encoding: @encoding, mode: File::RDWR)
|
||||
aux.rewind
|
||||
::IO.copy_stream(aux, @buffer)
|
||||
# TODO: remove this if/when minor ruby is 2.3
|
||||
|
@ -4,15 +4,14 @@ require "forwardable"
|
||||
|
||||
module HTTPX::Transcoder
|
||||
module Chunker
|
||||
module_function
|
||||
Error = Class.new(HTTPX::Error)
|
||||
CRLF = "\r\n".b
|
||||
|
||||
class Encoder
|
||||
extend Forwardable
|
||||
|
||||
def_delegator :@raw, :readpartial
|
||||
|
||||
CRLF = "\r\n"
|
||||
|
||||
def initialize(body)
|
||||
@raw = body
|
||||
end
|
||||
@ -30,6 +29,80 @@ module HTTPX::Transcoder
|
||||
end
|
||||
end
|
||||
|
||||
class Decoder
|
||||
extend Forwardable
|
||||
|
||||
def_delegator :@buffer, :empty?
|
||||
|
||||
def_delegator :@buffer, :<<
|
||||
|
||||
def_delegator :@buffer, :clear
|
||||
|
||||
def initialize(buffer, trailers = false)
|
||||
@buffer = buffer
|
||||
@chunk_length = nil
|
||||
@chunk_buffer = "".b
|
||||
@finished = false
|
||||
@state = :length
|
||||
@trailers = trailers
|
||||
end
|
||||
|
||||
def to_s
|
||||
@buffer
|
||||
end
|
||||
|
||||
def each
|
||||
loop do
|
||||
case @state
|
||||
when :length
|
||||
index = @buffer.index(CRLF)
|
||||
return unless index && index.positive?
|
||||
# Read hex-length
|
||||
hexlen = @buffer.slice!(0, index)
|
||||
hexlen[/\h/] || raise(Error, "wrong chunk size line: #{hexlen}")
|
||||
@chunk_length = hexlen.hex
|
||||
# check if is last chunk
|
||||
@finished = @chunk_length.zero?
|
||||
nextstate(:crlf)
|
||||
when :crlf
|
||||
crlf_size = @finished && !@trailers ? 4 : 2
|
||||
# consume CRLF
|
||||
return if @buffer.bytesize < crlf_size
|
||||
raise Error, "wrong chunked encoding format" unless @buffer.start_with?(CRLF * (crlf_size / 2))
|
||||
@buffer.slice!(0, crlf_size)
|
||||
if @chunk_length.nil?
|
||||
nextstate(:length)
|
||||
else
|
||||
return if @finished
|
||||
nextstate(:data)
|
||||
end
|
||||
when :data
|
||||
@chunk_buffer << (slice = @buffer.slice!(0, @chunk_length))
|
||||
@chunk_length -= slice.bytesize
|
||||
if @chunk_length.zero?
|
||||
yield @chunk_buffer unless @chunk_buffer.empty?
|
||||
@chunk_buffer.clear
|
||||
@chunk_length = nil
|
||||
nextstate(:crlf)
|
||||
end
|
||||
end
|
||||
break if @buffer.empty?
|
||||
end
|
||||
end
|
||||
|
||||
def finished?
|
||||
@finished
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def nextstate(state)
|
||||
@state = state
|
||||
end
|
||||
end
|
||||
|
||||
module_function
|
||||
|
||||
def encode(chunks)
|
||||
Encoder.new(chunks)
|
||||
end
|
||||
|
58
test/parser_test.rb
Normal file
58
test/parser_test.rb
Normal file
@ -0,0 +1,58 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative "test_helper"
|
||||
|
||||
class HTTP1ParserTest < Minitest::Test
|
||||
include HTTPX
|
||||
|
||||
class RequestObserver
|
||||
attr_reader :headers, :body
|
||||
|
||||
def initialize
|
||||
@headers = {}
|
||||
@body = "".b
|
||||
end
|
||||
|
||||
def on_headers(h)
|
||||
@headers.merge!(h)
|
||||
end
|
||||
|
||||
def on_data(data)
|
||||
@body << data
|
||||
end
|
||||
|
||||
def on_trailers(*); end
|
||||
|
||||
def on_start; end
|
||||
|
||||
def on_complete; end
|
||||
end
|
||||
|
||||
JSON.parse(File.read(File.expand_path("support/responses.json", __dir__))).each do |res_json|
|
||||
res_json["headers"] ||= {}
|
||||
|
||||
define_method "test_parse_response_#{res_json["name"]}" do
|
||||
observer = RequestObserver.new
|
||||
parser = Parser::HTTP1.new(observer)
|
||||
parser << res_json["raw"].b
|
||||
|
||||
if res_json.key?("upgrade") && (res_json["upgrade"] != 0)
|
||||
expect(@parser.upgrade?).to be true
|
||||
expect(@parser.upgrade_data).to eq(res_json["upgrade"])
|
||||
end
|
||||
|
||||
assert parser.http_version[0] == res_json["http_major"]
|
||||
assert parser.http_version[1] == res_json["http_minor"]
|
||||
|
||||
assert parser.status_code == res_json["status_code"]
|
||||
|
||||
assert observer.headers.size == res_json["num_headers"]
|
||||
res_json["headers"].each do |field, value|
|
||||
assert value == observer.headers[field.downcase].join("; ")
|
||||
end
|
||||
|
||||
assert observer.body == res_json["body"]
|
||||
assert observer.body.size == res_json["body_size"] if res_json["body_size"]
|
||||
end
|
||||
end
|
||||
end
|
@ -1,8 +1,16 @@
|
||||
#!/bin/sh
|
||||
apk --update add g++ make git bash
|
||||
|
||||
RUBY_PLATFORM=`ruby -e 'puts RUBY_PLATFORM'`
|
||||
|
||||
if [[ "$RUBY_PLATFORM" = "java" ]]; then
|
||||
apk --update add git bash
|
||||
else
|
||||
apk --update add g++ make git bash
|
||||
fi
|
||||
|
||||
export PATH=$GEM_HOME/bin:$BUNDLE_PATH/gems/bin:$PATH
|
||||
mkdir -p "$GEM_HOME" && chmod 777 "$GEM_HOME"
|
||||
gem install bundler -v="1.16.1" --no-doc --conservative
|
||||
gem install bundler -v="1.17.0" --no-doc --conservative
|
||||
cd /home && bundle install --quiet --jobs 4 && \
|
||||
bundle exec rake test:ci
|
||||
|
||||
|
395
test/support/responses.json
Normal file
395
test/support/responses.json
Normal file
@ -0,0 +1,395 @@
|
||||
[
|
||||
{
|
||||
"name": "google 301",
|
||||
"type": "HTTP_RESPONSE",
|
||||
"raw": "HTTP/1.1 301 Moved Permanently\r\nLocation: http://www.google.com/\r\nContent-Type: text/html; charset=UTF-8\r\nDate: Sun, 26 Apr 2009 11:11:49 GMT\r\nExpires: Tue, 26 May 2009 11:11:49 GMT\r\nX-$PrototypeBI-Version: 1.6.0.3\r\nCache-Control: public, max-age=2592000\r\nServer: gws\r\nContent-Length: 219 \r\n\r\n<HTML><HEAD><meta http-equiv=\"content-type\" content=\"text/html;charset=utf-8\">\n<TITLE>301 Moved</TITLE></HEAD><BODY>\n<H1>301 Moved</H1>\nThe document has moved\n<A HREF=\"http://www.google.com/\">here</A>.\r\n</BODY></HTML>\r\n",
|
||||
"should_keep_alive": true,
|
||||
"message_complete_on_eof": false,
|
||||
"http_major": 1,
|
||||
"http_minor": 1,
|
||||
"status_code": 301,
|
||||
"status": "Moved Permanently",
|
||||
"num_headers": 8,
|
||||
"headers": {
|
||||
"Location": "http://www.google.com/",
|
||||
"Content-Type": "text/html; charset=UTF-8",
|
||||
"Date": "Sun, 26 Apr 2009 11:11:49 GMT",
|
||||
"Expires": "Tue, 26 May 2009 11:11:49 GMT",
|
||||
"X-$PrototypeBI-Version": "1.6.0.3",
|
||||
"Cache-Control": "public, max-age=2592000",
|
||||
"Server": "gws",
|
||||
"Content-Length": "219"
|
||||
},
|
||||
"body": "<HTML><HEAD><meta http-equiv=\"content-type\" content=\"text/html;charset=utf-8\">\n<TITLE>301 Moved</TITLE></HEAD><BODY>\n<H1>301 Moved</H1>\nThe document has moved\n<A HREF=\"http://www.google.com/\">here</A>.\r\n</BODY></HTML>\r\n",
|
||||
"strict": true
|
||||
},
|
||||
{
|
||||
"name": "no content-length response",
|
||||
"type": "HTTP_RESPONSE",
|
||||
"raw": "HTTP/1.1 200 OK\r\nDate: Tue, 04 Aug 2009 07:59:32 GMT\r\nServer: Apache\r\nX-Powered-By: Servlet/2.5 JSP/2.1\r\nContent-Type: text/xml; charset=utf-8\r\nConnection: close\r\n\r\n<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<SOAP-ENV:Envelope xmlns:SOAP-ENV=\"http://schemas.xmlsoap.org/soap/envelope/\">\n <SOAP-ENV:Body>\n <SOAP-ENV:Fault>\n <faultcode>SOAP-ENV:Client</faultcode>\n <faultstring>Client Error</faultstring>\n </SOAP-ENV:Fault>\n </SOAP-ENV:Body>\n</SOAP-ENV:Envelope>",
|
||||
"should_keep_alive": false,
|
||||
"message_complete_on_eof": true,
|
||||
"http_major": 1,
|
||||
"http_minor": 1,
|
||||
"status_code": 200,
|
||||
"status": "OK",
|
||||
"num_headers": 5,
|
||||
"headers": {
|
||||
"Date": "Tue, 04 Aug 2009 07:59:32 GMT",
|
||||
"Server": "Apache",
|
||||
"X-Powered-By": "Servlet/2.5 JSP/2.1",
|
||||
"Content-Type": "text/xml; charset=utf-8",
|
||||
"Connection": "close"
|
||||
},
|
||||
"body": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<SOAP-ENV:Envelope xmlns:SOAP-ENV=\"http://schemas.xmlsoap.org/soap/envelope/\">\n <SOAP-ENV:Body>\n <SOAP-ENV:Fault>\n <faultcode>SOAP-ENV:Client</faultcode>\n <faultstring>Client Error</faultstring>\n </SOAP-ENV:Fault>\n </SOAP-ENV:Body>\n</SOAP-ENV:Envelope>",
|
||||
"strict": true
|
||||
},
|
||||
{
|
||||
"name": "404 no headers no body",
|
||||
"type": "HTTP_RESPONSE",
|
||||
"raw": "HTTP/1.1 404 Not Found\r\n\r\n",
|
||||
"should_keep_alive": false,
|
||||
"message_complete_on_eof": true,
|
||||
"http_major": 1,
|
||||
"http_minor": 1,
|
||||
"status_code": 404,
|
||||
"status": "Not Found",
|
||||
"num_headers": 0,
|
||||
"headers": {
|
||||
|
||||
},
|
||||
"body_size": 0,
|
||||
"body": "",
|
||||
"strict": true
|
||||
},
|
||||
{
|
||||
"name": "301 no response phrase",
|
||||
"type": "HTTP_RESPONSE",
|
||||
"raw": "HTTP/1.1 301\r\n\r\n",
|
||||
"should_keep_alive": false,
|
||||
"message_complete_on_eof": true,
|
||||
"http_major": 1,
|
||||
"http_minor": 1,
|
||||
"status_code": 301,
|
||||
"status": "",
|
||||
"num_headers": 0,
|
||||
"headers": {
|
||||
|
||||
},
|
||||
"body": "",
|
||||
"strict": true
|
||||
},
|
||||
{
|
||||
"name": "200 trailing space on chunked body",
|
||||
"type": "HTTP_RESPONSE",
|
||||
"raw": "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nTransfer-Encoding: chunked\r\n\r\n25 \r\nThis is the data in the first chunk\r\n\r\n1C\r\nand this is the second one\r\n\r\n0 \r\n\r\n",
|
||||
"should_keep_alive": true,
|
||||
"message_complete_on_eof": false,
|
||||
"http_major": 1,
|
||||
"http_minor": 1,
|
||||
"status_code": 200,
|
||||
"status": "OK",
|
||||
"num_headers": 2,
|
||||
"headers": {
|
||||
"Content-Type": "text/plain",
|
||||
"Transfer-Encoding": "chunked"
|
||||
},
|
||||
"body_size": 65,
|
||||
"body": "This is the data in the first chunk\r\nand this is the second one\r\n",
|
||||
"strict": true
|
||||
},
|
||||
{
|
||||
"name": "no carriage ret",
|
||||
"type": "HTTP_RESPONSE",
|
||||
"raw": "HTTP/1.1 200 OK\nContent-Type: text/html; charset=utf-8\nConnection: close\n\nthese headers are from http://news.ycombinator.com/",
|
||||
"should_keep_alive": false,
|
||||
"message_complete_on_eof": true,
|
||||
"http_major": 1,
|
||||
"http_minor": 1,
|
||||
"status_code": 200,
|
||||
"status": "OK",
|
||||
"num_headers": 2,
|
||||
"headers": {
|
||||
"Content-Type": "text/html; charset=utf-8",
|
||||
"Connection": "close"
|
||||
},
|
||||
"body": "these headers are from http://news.ycombinator.com/",
|
||||
"strict": true
|
||||
},
|
||||
{
|
||||
"name": "proxy connection",
|
||||
"type": "HTTP_RESPONSE",
|
||||
"raw": "HTTP/1.1 200 OK\r\nContent-Type: text/html; charset=UTF-8\r\nContent-Length: 11\r\nProxy-Connection: close\r\nDate: Thu, 31 Dec 2009 20:55:48 +0000\r\n\r\nhello world",
|
||||
"should_keep_alive": false,
|
||||
"message_complete_on_eof": false,
|
||||
"http_major": 1,
|
||||
"http_minor": 1,
|
||||
"status_code": 200,
|
||||
"status": "OK",
|
||||
"num_headers": 4,
|
||||
"headers": {
|
||||
"Content-Type": "text/html; charset=UTF-8",
|
||||
"Content-Length": "11",
|
||||
"Proxy-Connection": "close",
|
||||
"Date": "Thu, 31 Dec 2009 20:55:48 +0000"
|
||||
},
|
||||
"body": "hello world",
|
||||
"strict": true
|
||||
},
|
||||
{
|
||||
"name": "underscore header key",
|
||||
"type": "HTTP_RESPONSE",
|
||||
"raw": "HTTP/1.1 200 OK\r\nServer: DCLK-AdSvr\r\nContent-Type: text/xml\r\nContent-Length: 0\r\nDCLK_imp: v7;x;114750856;0-0;0;17820020;0/0;21603567/21621457/1;;~okv=;dcmt=text/xml;;~cs=o\r\n\r\n",
|
||||
"should_keep_alive": true,
|
||||
"message_complete_on_eof": false,
|
||||
"http_major": 1,
|
||||
"http_minor": 1,
|
||||
"status_code": 200,
|
||||
"status": "OK",
|
||||
"num_headers": 4,
|
||||
"headers": {
|
||||
"Server": "DCLK-AdSvr",
|
||||
"Content-Type": "text/xml",
|
||||
"Content-Length": "0",
|
||||
"DCLK_imp": "v7;x;114750856;0-0;0;17820020;0/0;21603567/21621457/1;;~okv=;dcmt=text/xml;;~cs=o"
|
||||
},
|
||||
"body": "",
|
||||
"strict": true
|
||||
},
|
||||
{
|
||||
"name": "bonjourmadame.fr",
|
||||
"type": "HTTP_RESPONSE",
|
||||
"raw": "HTTP/1.0 301 Moved Permanently\r\nDate: Thu, 03 Jun 2010 09:56:32 GMT\r\nServer: Apache/2.2.3 (Red Hat)\r\nCache-Control: public\r\nPragma: \r\nLocation: http://www.bonjourmadame.fr/\r\nVary: Accept-Encoding\r\nContent-Length: 0\r\nContent-Type: text/html; charset=UTF-8\r\nConnection: keep-alive\r\n\r\n",
|
||||
"should_keep_alive": true,
|
||||
"message_complete_on_eof": false,
|
||||
"http_major": 1,
|
||||
"http_minor": 0,
|
||||
"status_code": 301,
|
||||
"status": "Moved Permanently",
|
||||
"num_headers": 9,
|
||||
"headers": {
|
||||
"Date": "Thu, 03 Jun 2010 09:56:32 GMT",
|
||||
"Server": "Apache/2.2.3 (Red Hat)",
|
||||
"Cache-Control": "public",
|
||||
"Pragma": "",
|
||||
"Location": "http://www.bonjourmadame.fr/",
|
||||
"Vary": "Accept-Encoding",
|
||||
"Content-Length": "0",
|
||||
"Content-Type": "text/html; charset=UTF-8",
|
||||
"Connection": "keep-alive"
|
||||
},
|
||||
"body": "",
|
||||
"strict": true
|
||||
},
|
||||
{
|
||||
"name": "field underscore",
|
||||
"type": "HTTP_RESPONSE",
|
||||
"raw": "HTTP/1.1 200 OK\r\nDate: Tue, 28 Sep 2010 01:14:13 GMT\r\nServer: Apache\r\nCache-Control: no-cache, must-revalidate\r\nExpires: Mon, 26 Jul 1997 05:00:00 GMT\r\n.et-Cookie: PlaxoCS=1274804622353690521; path=/; domain=.plaxo.com\r\nVary: Accept-Encoding\r\n_eep-Alive: timeout=45\r\n_onnection: Keep-Alive\r\nTransfer-Encoding: chunked\r\nContent-Type: text/html\r\nConnection: close\r\n\r\n0\r\n\r\n",
|
||||
"should_keep_alive": false,
|
||||
"message_complete_on_eof": false,
|
||||
"http_major": 1,
|
||||
"http_minor": 1,
|
||||
"status_code": 200,
|
||||
"status": "OK",
|
||||
"num_headers": 11,
|
||||
"headers": {
|
||||
"Date": "Tue, 28 Sep 2010 01:14:13 GMT",
|
||||
"Server": "Apache",
|
||||
"Cache-Control": "no-cache, must-revalidate",
|
||||
"Expires": "Mon, 26 Jul 1997 05:00:00 GMT",
|
||||
".et-Cookie": "PlaxoCS=1274804622353690521; path=/; domain=.plaxo.com",
|
||||
"Vary": "Accept-Encoding",
|
||||
"_eep-Alive": "timeout=45",
|
||||
"_onnection": "Keep-Alive",
|
||||
"Transfer-Encoding": "chunked",
|
||||
"Content-Type": "text/html",
|
||||
"Connection": "close"
|
||||
},
|
||||
"body": "",
|
||||
"strict": true
|
||||
},
|
||||
{
|
||||
"name": "non-ASCII in status line",
|
||||
"type": "HTTP_RESPONSE",
|
||||
"raw": "HTTP/1.1 500 Oriëntatieprobleem\r\nDate: Fri, 5 Nov 2010 23:07:12 GMT+2\r\nContent-Length: 0\r\nConnection: close\r\n\r\n",
|
||||
"should_keep_alive": false,
|
||||
"message_complete_on_eof": false,
|
||||
"http_major": 1,
|
||||
"http_minor": 1,
|
||||
"status_code": 500,
|
||||
"status": "Oriëntatieprobleem",
|
||||
"num_headers": 3,
|
||||
"headers": {
|
||||
"Date": "Fri, 5 Nov 2010 23:07:12 GMT+2",
|
||||
"Content-Length": "0",
|
||||
"Connection": "close"
|
||||
},
|
||||
"body": "",
|
||||
"strict": true
|
||||
},
|
||||
{
|
||||
"name": "http version 0.9",
|
||||
"type": "HTTP_RESPONSE",
|
||||
"raw": "HTTP/0.9 200 OK\r\n\r\n",
|
||||
"should_keep_alive": false,
|
||||
"message_complete_on_eof": true,
|
||||
"http_major": 0,
|
||||
"http_minor": 9,
|
||||
"status_code": 200,
|
||||
"status": "OK",
|
||||
"num_headers": 0,
|
||||
"headers": {
|
||||
|
||||
},
|
||||
"body": "",
|
||||
"strict": true
|
||||
},
|
||||
{
|
||||
"name": "neither content-length nor transfer-encoding response",
|
||||
"type": "HTTP_RESPONSE",
|
||||
"raw": "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\n\r\nhello world",
|
||||
"should_keep_alive": false,
|
||||
"message_complete_on_eof": true,
|
||||
"http_major": 1,
|
||||
"http_minor": 1,
|
||||
"status_code": 200,
|
||||
"status": "OK",
|
||||
"num_headers": 1,
|
||||
"headers": {
|
||||
"Content-Type": "text/plain"
|
||||
},
|
||||
"body": "hello world",
|
||||
"strict": true
|
||||
},
|
||||
{
|
||||
"name": "HTTP/1.0 with keep-alive and EOF-terminated 200 status",
|
||||
"type": "HTTP_RESPONSE",
|
||||
"raw": "HTTP/1.0 200 OK\r\nConnection: keep-alive\r\n\r\n",
|
||||
"should_keep_alive": false,
|
||||
"message_complete_on_eof": true,
|
||||
"http_major": 1,
|
||||
"http_minor": 0,
|
||||
"status_code": 200,
|
||||
"status": "OK",
|
||||
"num_headers": 1,
|
||||
"headers": {
|
||||
"Connection": "keep-alive"
|
||||
},
|
||||
"body_size": 0,
|
||||
"body": "",
|
||||
"strict": true
|
||||
},
|
||||
{
|
||||
"name": "HTTP/1.0 with keep-alive and a 204 status",
|
||||
"type": "HTTP_RESPONSE",
|
||||
"raw": "HTTP/1.0 204 No content\r\nConnection: keep-alive\r\n\r\n",
|
||||
"should_keep_alive": true,
|
||||
"message_complete_on_eof": false,
|
||||
"http_major": 1,
|
||||
"http_minor": 0,
|
||||
"status_code": 204,
|
||||
"status": "No content",
|
||||
"num_headers": 1,
|
||||
"headers": {
|
||||
"Connection": "keep-alive"
|
||||
},
|
||||
"body_size": 0,
|
||||
"body": "",
|
||||
"strict": true
|
||||
},
|
||||
{
|
||||
"name": "HTTP/1.1 with an EOF-terminated 200 status",
|
||||
"type": "HTTP_RESPONSE",
|
||||
"raw": "HTTP/1.1 200 OK\r\n\r\n",
|
||||
"should_keep_alive": false,
|
||||
"message_complete_on_eof": true,
|
||||
"http_major": 1,
|
||||
"http_minor": 1,
|
||||
"status_code": 200,
|
||||
"status": "OK",
|
||||
"num_headers": 0,
|
||||
"headers": {
|
||||
|
||||
},
|
||||
"body_size": 0,
|
||||
"body": "",
|
||||
"strict": true
|
||||
},
|
||||
{
|
||||
"name": "HTTP/1.1 with a 204 status",
|
||||
"type": "HTTP_RESPONSE",
|
||||
"raw": "HTTP/1.1 204 No content\r\n\r\n",
|
||||
"should_keep_alive": true,
|
||||
"message_complete_on_eof": false,
|
||||
"http_major": 1,
|
||||
"http_minor": 1,
|
||||
"status_code": 204,
|
||||
"status": "No content",
|
||||
"num_headers": 0,
|
||||
"headers": {
|
||||
|
||||
},
|
||||
"body_size": 0,
|
||||
"body": "",
|
||||
"strict": true
|
||||
},
|
||||
{
|
||||
"name": "HTTP/1.1 with a 204 status and keep-alive disabled",
|
||||
"type": "HTTP_RESPONSE",
|
||||
"raw": "HTTP/1.1 204 No content\r\nConnection: close\r\n\r\n",
|
||||
"should_keep_alive": false,
|
||||
"message_complete_on_eof": false,
|
||||
"http_major": 1,
|
||||
"http_minor": 1,
|
||||
"status_code": 204,
|
||||
"status": "No content",
|
||||
"num_headers": 1,
|
||||
"headers": {
|
||||
"Connection": "close"
|
||||
},
|
||||
"body_size": 0,
|
||||
"body": "",
|
||||
"strict": true
|
||||
},
|
||||
{
|
||||
"name": "HTTP/1.1 with chunked endocing and a 200 response",
|
||||
"type": "HTTP_RESPONSE",
|
||||
"raw": "HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n0\r\n\r\n",
|
||||
"should_keep_alive": true,
|
||||
"message_complete_on_eof": false,
|
||||
"http_major": 1,
|
||||
"http_minor": 1,
|
||||
"status_code": 200,
|
||||
"status": "OK",
|
||||
"num_headers": 1,
|
||||
"headers": {
|
||||
"Transfer-Encoding": "chunked"
|
||||
},
|
||||
"body_size": 0,
|
||||
"body": "",
|
||||
"strict": true
|
||||
},
|
||||
{
|
||||
"name": "field space",
|
||||
"type": "HTTP_RESPONSE",
|
||||
"strict": false,
|
||||
"raw": "HTTP/1.1 200 OK\r\nServer: Microsoft-IIS/6.0\r\nX-Powered-By: ASP.NET\r\nen-US Content-Type: text/xml\r\nContent-Type: text/xml\r\nContent-Length: 16\r\nDate: Fri, 23 Jul 2010 18:45:38 GMT\r\nConnection: keep-alive\r\n\r\n<xml>hello</xml>",
|
||||
"should_keep_alive": true,
|
||||
"message_complete_on_eof": false,
|
||||
"http_major": 1,
|
||||
"http_minor": 1,
|
||||
"status_code": 200,
|
||||
"status": "OK",
|
||||
"num_headers": 7,
|
||||
"headers": {
|
||||
"Server": "Microsoft-IIS/6.0",
|
||||
"X-Powered-By": "ASP.NET",
|
||||
"en-US Content-Type": "text/xml",
|
||||
"Content-Type": "text/xml",
|
||||
"Content-Length": "16",
|
||||
"Date": "Fri, 23 Jul 2010 18:45:38 GMT",
|
||||
"Connection": "keep-alive"
|
||||
},
|
||||
"body": "<xml>hello</xml>"
|
||||
}
|
||||
]
|
Loading…
x
Reference in New Issue
Block a user