oortle-yajl-ruby 0.5.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (132) hide show
  1. data/.gitignore +6 -0
  2. data/CHANGELOG.md +177 -0
  3. data/MIT-LICENSE +20 -0
  4. data/README.rdoc +270 -0
  5. data/Rakefile +35 -0
  6. data/VERSION.yml +4 -0
  7. data/benchmark/encode.rb +46 -0
  8. data/benchmark/encode_json_and_marshal.rb +35 -0
  9. data/benchmark/encode_json_and_yaml.rb +47 -0
  10. data/benchmark/http.rb +30 -0
  11. data/benchmark/parse.rb +49 -0
  12. data/benchmark/parse_json_and_marshal.rb +47 -0
  13. data/benchmark/parse_json_and_yaml.rb +56 -0
  14. data/benchmark/parse_stream.rb +48 -0
  15. data/benchmark/subjects/item.json +1 -0
  16. data/benchmark/subjects/ohai.json +1216 -0
  17. data/benchmark/subjects/ohai.marshal_dump +0 -0
  18. data/benchmark/subjects/ohai.yml +975 -0
  19. data/benchmark/subjects/twitter_search.json +1 -0
  20. data/benchmark/subjects/twitter_stream.json +430 -0
  21. data/benchmark/subjects/unicode.json +1 -0
  22. data/examples/http/twitter_search_api.rb +15 -0
  23. data/examples/http/twitter_stream_api.rb +27 -0
  24. data/examples/parsing/from_file.rb +14 -0
  25. data/examples/parsing/from_stdin.rb +9 -0
  26. data/examples/parsing/from_string.rb +15 -0
  27. data/ext/api/yajl_common.h +85 -0
  28. data/ext/api/yajl_gen.h +123 -0
  29. data/ext/api/yajl_parse.h +182 -0
  30. data/ext/extconf.rb +8 -0
  31. data/ext/yajl.c +157 -0
  32. data/ext/yajl_alloc.c +65 -0
  33. data/ext/yajl_alloc.h +50 -0
  34. data/ext/yajl_buf.c +119 -0
  35. data/ext/yajl_buf.h +73 -0
  36. data/ext/yajl_bytestack.h +85 -0
  37. data/ext/yajl_encode.c +179 -0
  38. data/ext/yajl_encode.h +44 -0
  39. data/ext/yajl_ext.c +774 -0
  40. data/ext/yajl_ext.h +74 -0
  41. data/ext/yajl_gen.c +290 -0
  42. data/ext/yajl_lex.c +744 -0
  43. data/ext/yajl_lex.h +135 -0
  44. data/ext/yajl_parser.c +447 -0
  45. data/ext/yajl_parser.h +79 -0
  46. data/lib/yajl.rb +80 -0
  47. data/lib/yajl/bzip2.rb +11 -0
  48. data/lib/yajl/bzip2/stream_reader.rb +29 -0
  49. data/lib/yajl/bzip2/stream_writer.rb +15 -0
  50. data/lib/yajl/deflate.rb +6 -0
  51. data/lib/yajl/deflate/stream_reader.rb +38 -0
  52. data/lib/yajl/deflate/stream_writer.rb +21 -0
  53. data/lib/yajl/gzip.rb +6 -0
  54. data/lib/yajl/gzip/stream_reader.rb +28 -0
  55. data/lib/yajl/gzip/stream_writer.rb +14 -0
  56. data/lib/yajl/http_stream.rb +150 -0
  57. data/lib/yajl/json_gem.rb +14 -0
  58. data/lib/yajl/json_gem/encoding.rb +40 -0
  59. data/lib/yajl/json_gem/parsing.rb +26 -0
  60. data/spec/encoding/encoding_spec.rb +186 -0
  61. data/spec/http/fixtures/http.bzip2.dump +0 -0
  62. data/spec/http/fixtures/http.deflate.dump +0 -0
  63. data/spec/http/fixtures/http.gzip.dump +0 -0
  64. data/spec/http/fixtures/http.raw.dump +1226 -0
  65. data/spec/http/http_spec.rb +158 -0
  66. data/spec/json_gem_compatibility/compatibility_spec.rb +178 -0
  67. data/spec/parsing/active_support_spec.rb +64 -0
  68. data/spec/parsing/chunked_spec.rb +98 -0
  69. data/spec/parsing/fixtures/fail.15.json +1 -0
  70. data/spec/parsing/fixtures/fail.16.json +1 -0
  71. data/spec/parsing/fixtures/fail.17.json +1 -0
  72. data/spec/parsing/fixtures/fail.26.json +1 -0
  73. data/spec/parsing/fixtures/fail11.json +1 -0
  74. data/spec/parsing/fixtures/fail12.json +1 -0
  75. data/spec/parsing/fixtures/fail13.json +1 -0
  76. data/spec/parsing/fixtures/fail14.json +1 -0
  77. data/spec/parsing/fixtures/fail19.json +1 -0
  78. data/spec/parsing/fixtures/fail20.json +1 -0
  79. data/spec/parsing/fixtures/fail21.json +1 -0
  80. data/spec/parsing/fixtures/fail22.json +1 -0
  81. data/spec/parsing/fixtures/fail23.json +1 -0
  82. data/spec/parsing/fixtures/fail24.json +1 -0
  83. data/spec/parsing/fixtures/fail25.json +1 -0
  84. data/spec/parsing/fixtures/fail27.json +2 -0
  85. data/spec/parsing/fixtures/fail28.json +2 -0
  86. data/spec/parsing/fixtures/fail3.json +1 -0
  87. data/spec/parsing/fixtures/fail4.json +1 -0
  88. data/spec/parsing/fixtures/fail5.json +1 -0
  89. data/spec/parsing/fixtures/fail6.json +1 -0
  90. data/spec/parsing/fixtures/fail9.json +1 -0
  91. data/spec/parsing/fixtures/pass.array.json +6 -0
  92. data/spec/parsing/fixtures/pass.codepoints_from_unicode_org.json +1 -0
  93. data/spec/parsing/fixtures/pass.contacts.json +1 -0
  94. data/spec/parsing/fixtures/pass.db100.xml.json +1 -0
  95. data/spec/parsing/fixtures/pass.db1000.xml.json +1 -0
  96. data/spec/parsing/fixtures/pass.dc_simple_with_comments.json +11 -0
  97. data/spec/parsing/fixtures/pass.deep_arrays.json +1 -0
  98. data/spec/parsing/fixtures/pass.difficult_json_c_test_case.json +1 -0
  99. data/spec/parsing/fixtures/pass.difficult_json_c_test_case_with_comments.json +1 -0
  100. data/spec/parsing/fixtures/pass.doubles.json +1 -0
  101. data/spec/parsing/fixtures/pass.empty_array.json +1 -0
  102. data/spec/parsing/fixtures/pass.empty_string.json +1 -0
  103. data/spec/parsing/fixtures/pass.escaped_bulgarian.json +4 -0
  104. data/spec/parsing/fixtures/pass.escaped_foobar.json +1 -0
  105. data/spec/parsing/fixtures/pass.item.json +1 -0
  106. data/spec/parsing/fixtures/pass.json-org-sample1.json +23 -0
  107. data/spec/parsing/fixtures/pass.json-org-sample2.json +11 -0
  108. data/spec/parsing/fixtures/pass.json-org-sample3.json +26 -0
  109. data/spec/parsing/fixtures/pass.json-org-sample4-nows.json +88 -0
  110. data/spec/parsing/fixtures/pass.json-org-sample4.json +89 -0
  111. data/spec/parsing/fixtures/pass.json-org-sample5.json +27 -0
  112. data/spec/parsing/fixtures/pass.map-spain.xml.json +1 -0
  113. data/spec/parsing/fixtures/pass.ns-invoice100.xml.json +1 -0
  114. data/spec/parsing/fixtures/pass.ns-soap.xml.json +1 -0
  115. data/spec/parsing/fixtures/pass.numbers-fp-4k.json +6 -0
  116. data/spec/parsing/fixtures/pass.numbers-fp-64k.json +61 -0
  117. data/spec/parsing/fixtures/pass.numbers-int-4k.json +11 -0
  118. data/spec/parsing/fixtures/pass.numbers-int-64k.json +154 -0
  119. data/spec/parsing/fixtures/pass.twitter-search.json +1 -0
  120. data/spec/parsing/fixtures/pass.twitter-search2.json +1 -0
  121. data/spec/parsing/fixtures/pass.unicode.json +3315 -0
  122. data/spec/parsing/fixtures/pass.yelp.json +1 -0
  123. data/spec/parsing/fixtures/pass1.json +56 -0
  124. data/spec/parsing/fixtures/pass2.json +1 -0
  125. data/spec/parsing/fixtures/pass3.json +6 -0
  126. data/spec/parsing/fixtures_spec.rb +41 -0
  127. data/spec/parsing/one_off_spec.rb +54 -0
  128. data/spec/rcov.opts +4 -0
  129. data/spec/spec.opts +2 -0
  130. data/spec/spec_helper.rb +6 -0
  131. data/yajl-ruby.gemspec +179 -0
  132. metadata +198 -0
@@ -0,0 +1,79 @@
1
+ /*
2
+ * Copyright 2007-2009, Lloyd Hilaiel.
3
+ *
4
+ * Redistribution and use in source and binary forms, with or without
5
+ * modification, are permitted provided that the following conditions are
6
+ * met:
7
+ *
8
+ * 1. Redistributions of source code must retain the above copyright
9
+ * notice, this list of conditions and the following disclaimer.
10
+ *
11
+ * 2. Redistributions in binary form must reproduce the above copyright
12
+ * notice, this list of conditions and the following disclaimer in
13
+ * the documentation and/or other materials provided with the
14
+ * distribution.
15
+ *
16
+ * 3. Neither the name of Lloyd Hilaiel nor the names of its
17
+ * contributors may be used to endorse or promote products derived
18
+ * from this software without specific prior written permission.
19
+ *
20
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
21
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
22
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
24
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
25
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
28
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
29
+ * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30
+ * POSSIBILITY OF SUCH DAMAGE.
31
+ */
32
+
33
+ #ifndef __YAJL_PARSER_H__
34
+ #define __YAJL_PARSER_H__
35
+
36
+ #include "api/yajl_parse.h"
37
+ #include "yajl_bytestack.h"
38
+ #include "yajl_buf.h"
39
+
40
+
41
+ typedef enum {
42
+ yajl_state_start = 0,
43
+ yajl_state_parse_complete,
44
+ yajl_state_parse_error,
45
+ yajl_state_lexical_error,
46
+ yajl_state_map_start,
47
+ yajl_state_map_sep,
48
+ yajl_state_map_need_val,
49
+ yajl_state_map_got_val,
50
+ yajl_state_map_need_key,
51
+ yajl_state_array_start,
52
+ yajl_state_array_got_val,
53
+ yajl_state_array_need_val
54
+ } yajl_state;
55
+
56
+ struct yajl_handle_t {
57
+ const yajl_callbacks * callbacks;
58
+ void * ctx;
59
+ yajl_lexer lexer;
60
+ const char * parseError;
61
+ unsigned int errorOffset;
62
+ /* temporary storage for decoded strings */
63
+ yajl_buf decodeBuf;
64
+ /* a stack of states. access with yajl_state_XXX routines */
65
+ yajl_bytestack stateStack;
66
+ /* memory allocation routines */
67
+ yajl_alloc_funcs alloc;
68
+ };
69
+
70
+ yajl_status
71
+ yajl_do_parse(yajl_handle handle, unsigned int * offset,
72
+ const unsigned char * jsonText, unsigned int jsonTextLen);
73
+
74
+ unsigned char *
75
+ yajl_render_error_string(yajl_handle hand, const unsigned char * jsonText,
76
+ unsigned int jsonTextLen, int verbose);
77
+
78
+
79
+ #endif
@@ -0,0 +1,80 @@
1
+ # encoding: UTF-8
2
+ require 'yajl_ext'
3
+
4
+ # = Extras
5
+ # We're not going to load these auotmatically, because you might not need them ;)
6
+ #
7
+ # require 'yajl/http_stream.rb' unless defined?(Yajl::HttpStream)
8
+ # require 'yajl/gzip.rb' unless defined?(Yajl::Gzip)
9
+ # require 'yajl/deflate.rb' unless defined?(Yajl::Deflate)
10
+ # require 'yajl/bzip2.rb' unless defined?(Yajl::Bzip2)
11
+
12
+ # = Yajl
13
+ #
14
+ # Ruby bindings to the excellent Yajl (Yet Another JSON Parser) ANSI C library.
15
+ module Yajl
16
+ VERSION = "0.5.7"
17
+
18
+ class Parser
19
+ # A helper method for parse-and-forget use-cases
20
+ #
21
+ # +io+ is the stream to parse JSON from
22
+ #
23
+ # The +options+ hash allows you to set two parsing options - :allow_comments and :check_utf8
24
+ #
25
+ # :allow_comments accepts a boolean will enable/disable checks for in-line comments in the JSON stream
26
+ #
27
+ # :check_utf8 accepts a boolean will enable/disable UTF8 validation for the JSON stream
28
+ def self.parse(io, options={}, read_bufsize=nil, &block)
29
+ new(options).parse(io, read_bufsize, &block)
30
+ end
31
+ end
32
+
33
+ class Encoder
34
+ # A helper method for encode-and-forget use-cases
35
+ #
36
+ # Examples:
37
+ # Yajl::Encoder.encode(obj[, io, :pretty => true, :indent => "\t"])
38
+ #
39
+ # output = Yajl::Encoder.encode(obj[, :pretty => true, :indent => "\t"])
40
+ #
41
+ # +obj+ is a ruby object to encode to JSON format
42
+ #
43
+ # +io+ is the optional IO stream to encode the ruby object to.
44
+ # If +io+ isn't passed, the resulting JSON string is returned. If +io+ is passed, nil is returned.
45
+ #
46
+ # The +options+ hash allows you to set two encoding options - :pretty and :indent
47
+ #
48
+ # :pretty accepts a boolean and will enable/disable "pretty printing" the resulting output
49
+ #
50
+ # :indent accepts a string and will be used as the indent character(s) during the pretty print process
51
+ def self.encode(obj, *args, &block)
52
+ # TODO: this code smells, any ideas?
53
+ options = {}
54
+ io = nil
55
+ args.each do |arg|
56
+ if arg.is_a?(Hash)
57
+ options = arg
58
+ elsif arg.respond_to?(:read)
59
+ io = arg
60
+ end
61
+ end if args.any?
62
+ new(options).encode(obj, io, &block)
63
+ end
64
+ end
65
+
66
+ # DEPRECATED - See Yajl::Parser and Yajl::Encoder
67
+ module Stream
68
+ # DEPRECATED - See Yajl::Parser
69
+ def self.parse(io)
70
+ STDERR.puts "WARNING: Yajl::Stream has be deprecated and will most likely be gone in the next release. Use the Yajl::Parser class instead."
71
+ Parser.new.parse(io)
72
+ end
73
+
74
+ # DEPRECATED - See Yajl::Encoder
75
+ def self.encode(obj, io)
76
+ STDERR.puts "WARNING: Yajl::Stream has be deprecated and will most likely be gone in the next release. Use the Yajl::Encoder class instead."
77
+ Encoder.new.encode(obj, io)
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,11 @@
1
+ # encoding: UTF-8
2
+
3
+ require 'yajl' unless defined?(Yajl::Parser)
4
+
5
+ begin
6
+ require 'bzip2' unless defined?(Bzip2)
7
+ require 'yajl/bzip2/stream_reader.rb'
8
+ require 'yajl/bzip2/stream_writer.rb'
9
+ rescue LoadError => e
10
+ raise "Unable to load the bzip2 library. Is the bzip2-ruby gem installed?"
11
+ end
@@ -0,0 +1,29 @@
1
+ # encoding: UTF-8
2
+ module Yajl
3
+ module Bzip2
4
+ # This is a wrapper around Bzip::Reader to allow it's #read method to adhere
5
+ # to the IO spec, allowing for two parameters (length, and buffer)
6
+ class StreamReader < ::Bzip2::Reader
7
+
8
+ # A helper method to allow use similar to IO#read
9
+ def read(len=nil, buffer=nil)
10
+ unless buffer.nil?
11
+ buffer.replace super(len)
12
+ return buffer
13
+ end
14
+ super(len)
15
+ end
16
+
17
+ # Helper method for one-off parsing from a bzip2-compressed stream
18
+ #
19
+ # See Yajl::Parser#parse for parameter documentation
20
+ def self.parse(input, options={}, buffer_size=nil, &block)
21
+ if input.is_a?(String)
22
+ input = StringIO.new(input)
23
+ end
24
+
25
+ Yajl::Parser.new(options).parse(new(input), buffer_size, &block)
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,15 @@
1
+ # encoding: UTF-8
2
+ module Yajl
3
+ module Bzip2
4
+ # A wrapper around the Bzip2::Writer class for easier JSON stream encoding
5
+ class StreamWriter < ::Bzip2::Writer
6
+
7
+ # A helper method for encoding to a bzip2-compressed stream
8
+ #
9
+ # Look up Yajl::Encoder#encode for parameter documentation
10
+ def self.encode(obj, io)
11
+ Yajl::Encoder.new.encode(obj, new(io))
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,6 @@
1
+ # encoding: UTF-8
2
+
3
+ require 'yajl' unless defined?(Yajl::Parser)
4
+ require 'zlib' unless defined?(Zlib)
5
+ require 'yajl/deflate/stream_reader.rb'
6
+ require 'yajl/deflate/stream_writer.rb'
@@ -0,0 +1,38 @@
1
+ # encoding: UTF-8
2
+ module Yajl
3
+ module Deflate
4
+ # This is a wrapper around Zlib::Inflate, creating a #read method that adheres
5
+ # to the IO spec, allowing for two parameters (length, and buffer)
6
+ class StreamReader < ::Zlib::Inflate
7
+
8
+ # Wrapper to the initialize method so we can set the initial IO to parse from.
9
+ def initialize(io, options)
10
+ @io = io
11
+ super(options)
12
+ end
13
+
14
+ # A helper method to allow use similar to IO#read
15
+ def read(len=nil, buffer=nil)
16
+ buffer.replace inflate(@io.read(len)) and return unless buffer.nil?
17
+ inflate(@io.read(len))
18
+ end
19
+ alias :eof? :finished?
20
+
21
+ # Helper method for one-off parsing from a deflate-compressed stream
22
+ #
23
+ # See Yajl::Parser#parse for parameter documentation
24
+ def self.parse(input, options={}, buffer_size=nil, &block)
25
+ if input.is_a?(String)
26
+ input = StringIO.new(input)
27
+ end
28
+
29
+ if options.is_a?(Hash)
30
+ deflate_options = options.delete(:deflate_options)
31
+ Yajl::Parser.new(options).parse(new(input, deflate_options), buffer_size, &block)
32
+ elsif options.is_a?(Fixnum)
33
+ Yajl::Parser.new.parse(new(input, options), buffer_size, &block)
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,21 @@
1
+ # encoding: UTF-8
2
+ module Yajl
3
+ module Deflate
4
+ # A wrapper around the Zlib::Deflate class for easier JSON stream parsing
5
+ class StreamWriter < ::Zlib::Deflate
6
+
7
+ # A helper method to allow use similar to IO#write
8
+ def write(str)
9
+ deflate(str)
10
+ str.size unless str.nil?
11
+ end
12
+
13
+ # A helper method for one-off encoding to a deflate-compressed stream
14
+ #
15
+ # Look up Yajl::Encoder#encode for parameter documentation
16
+ def self.encode(obj, io)
17
+ Yajl::Encoder.new.encode(obj, new(io))
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,6 @@
1
+ # encoding: UTF-8
2
+
3
+ require 'yajl' unless defined?(Yajl::Parser)
4
+ require 'zlib' unless defined?(Zlib)
5
+ require 'yajl/gzip/stream_reader.rb'
6
+ require 'yajl/gzip/stream_writer.rb'
@@ -0,0 +1,28 @@
1
+ # encoding: UTF-8
2
+ module Yajl
3
+ module Gzip
4
+ # This is a wrapper around Zlib::GzipReader to allow it's #read method to adhere
5
+ # to the IO spec, allowing for two parameters (length, and buffer)
6
+ class StreamReader < ::Zlib::GzipReader
7
+
8
+ # Wrapper method to allow use similar to IO#read
9
+ def read(len=nil, buffer=nil)
10
+ unless buffer.nil?
11
+ buffer.replace super(len)
12
+ return buffer
13
+ end
14
+ super(len)
15
+ end
16
+
17
+ # Helper method for one-off parsing from a gzip-compressed stream
18
+ #
19
+ # See Yajl::Parser#parse for parameter documentation
20
+ def self.parse(input, options={}, buffer_size=nil, &block)
21
+ if input.is_a?(String)
22
+ input = StringIO.new(input)
23
+ end
24
+ Yajl::Parser.new(options).parse(new(input), buffer_size, &block)
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,14 @@
1
+ # encoding: UTF-8
2
+ module Yajl
3
+ module Gzip
4
+ # Wraper around the Zlib::GzipWriter class
5
+ class StreamWriter < ::Zlib::GzipWriter
6
+ # A helper method for one-off encoding to a gzip-compressed stream
7
+ #
8
+ # Look up Yajl::Encoder#encode for parameter documentation
9
+ def self.encode(obj, io)
10
+ Yajl::Encoder.new.encode(obj, new(io))
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,150 @@
1
+ # encoding: UTF-8
2
+ require 'socket' unless defined?(Socket)
3
+ require 'yajl' unless defined?(Yajl::Parser)
4
+ require 'uri' unless defined?(URI)
5
+
6
+ module Yajl
7
+ # This module is for making HTTP requests to which the response bodies (and possibly requests in the near future)
8
+ # are streamed directly into Yajl.
9
+ class HttpStream
10
+
11
+ # This Exception is thrown when an HTTP response isn't in ALLOWED_MIME_TYPES
12
+ # and therefore cannot be parsed.
13
+ class InvalidContentType < Exception; end
14
+
15
+ # The mime-type we expect the response to be. If it's anything else, we can't parse it
16
+ # and an InvalidContentType is raised.
17
+ ALLOWED_MIME_TYPES = ["application/json", "text/plain"]
18
+
19
+ # Makes a basic HTTP GET request to the URI provided
20
+ # 1. a raw socket is opened to the server/host provided
21
+ # 2. the request is made using HTTP/1.0, Accept-encoding: gzip (deflate support coming soon, too)
22
+ # 3. the response is read until the end of the headers
23
+ # 4. the _socket itself_ is passed directly to Yajl, for direct parsing off the stream; As it's being received over the wire!
24
+ def self.get(uri, opts = {}, &block)
25
+ user_agent = opts.has_key?('User-Agent') ? opts.delete(['User-Agent']) : "Yajl::HttpStream #{Yajl::VERSION}"
26
+
27
+ Thread.current[:yajl_socket] = socket = TCPSocket.new(uri.host, uri.port)
28
+ request = "GET #{uri.path}#{uri.query ? "?"+uri.query : nil} HTTP/1.1\r\n"
29
+ request << "Host: #{uri.host}\r\n"
30
+ request << "Authorization: Basic #{[uri.userinfo].pack('m').chomp}\r\n" unless uri.userinfo.nil?
31
+ request << "User-Agent: #{user_agent}\r\n"
32
+ request << "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n"
33
+ request << "Connection: close\r\n"
34
+ encodings = []
35
+ encodings << "bzip2" if defined?(Yajl::Bzip2)
36
+ encodings << "gzip" if defined?(Yajl::Gzip)
37
+ encodings << "deflate" if defined?(Yajl::Deflate)
38
+ request << "Accept-Encoding: #{encodings.join(',')}\r\n" if encodings.any?
39
+ request << "Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7\r\n"
40
+ request << "\r\n\r\n"
41
+ socket.write(request)
42
+
43
+ handle_response(socket, opts, &block)
44
+ end
45
+
46
+ # Makes a basic HTTP POST request to the URI provided
47
+ # 1. a raw socket is opened to the server/host provided
48
+ # 2. the request is made using HTTP/1.0, Accept-encoding: gzip (deflate support coming soon, too)
49
+ # 3. the response is read until the end of the headers
50
+ # 4. the _socket itself_ is passed directly to Yajl, for direct parsing off the stream; As it's being received over the wire!
51
+ def self.post(uri, body={}, opts = {}, &block)
52
+ content = body.keys.collect {|param| "#{URI.escape(param.to_s)}=#{URI.escape(body[param].to_s)}"}.join('&')
53
+
54
+ user_agent = opts.has_key?('User-Agent') ? opts.delete(['User-Agent']) : "Yajl::HttpStream #{Yajl::VERSION}"
55
+
56
+ Thread.current[:yajl_socket] = socket = TCPSocket.new(uri.host, uri.port)
57
+ request = "POST #{uri.path}#{uri.query ? "?"+uri.query : nil} HTTP/1.1\r\n"
58
+ request << "Host: #{uri.host}\r\n"
59
+ request << "Authorization: Basic #{[uri.userinfo].pack('m').chomp}\r\n" unless uri.userinfo.nil?
60
+ request << "User-Agent: #{user_agent}\r\n"
61
+ request << "Accept: */*\r\n"
62
+ encodings = []
63
+ encodings << "bzip2" if defined?(Yajl::Bzip2)
64
+ encodings << "gzip" if defined?(Yajl::Gzip)
65
+ encodings << "deflate" if defined?(Yajl::Deflate)
66
+ request << "Accept-Encoding: #{encodings.join(',')}\r\n" if encodings.any?
67
+ request << "Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7\r\n"
68
+ request << "Content-Type: application/x-www-form-urlencoded\r\n"
69
+ request << "Content-Length: #{content.length}"
70
+ request << "\r\n\r\n"
71
+ request << content
72
+ socket.write(request)
73
+
74
+ handle_response(socket, opts, &block)
75
+ end
76
+
77
+ # Terminate a running HTTPStream instance given the thread it's running in
78
+ def self.terminate(thread)
79
+ thread[:yajl_intentional_termination] = true
80
+ thread[:yajl_socket].close
81
+ end
82
+
83
+ private
84
+
85
+ def self.handle_response(socket, opts, &block)
86
+ response_head = {}
87
+ response_head[:headers] = {}
88
+
89
+ socket.each_line do |line|
90
+ if line == "\r\n" # end of the headers
91
+ break
92
+ else
93
+ header = line.split(": ")
94
+ if header.size == 1
95
+ header = header[0].split(" ")
96
+ response_head[:version] = header[0]
97
+ response_head[:code] = header[1].to_i
98
+ response_head[:msg] = header[2]
99
+ # this is the response code line
100
+ else
101
+ response_head[:headers][header[0]] = header[1].strip
102
+ end
103
+ end
104
+ end
105
+ parser = Yajl::Parser.new(opts)
106
+ if response_head[:headers]["Transfer-Encoding"] == 'chunked'
107
+ if block_given?
108
+ parser.on_parse_complete = block
109
+ chunkLeft = 0
110
+ while !socket.eof? && (size = socket.gets.hex)
111
+ next if size == 0
112
+ json = socket.read(size)
113
+ chunkLeft = size-json.size
114
+ if chunkLeft == 0
115
+ parser << json
116
+ else
117
+ # received only part of the chunk, grab the rest
118
+ parser << socket.read(chunkLeft)
119
+ end
120
+ end
121
+ else
122
+ raise Exception, "Chunked responses detected, but no block given to handle the chunks."
123
+ end
124
+ else
125
+ content_type = response_head[:headers]["Content-Type"].split(';')
126
+ content_type = content_type.first
127
+ if ALLOWED_MIME_TYPES.include?(content_type)
128
+ case response_head[:headers]["Content-Encoding"]
129
+ when "gzip"
130
+ return Yajl::Gzip::StreamReader.parse(socket, opts)
131
+ when "deflate"
132
+ return Yajl::Deflate::StreamReader.parse(socket, opts.merge({:deflate_options => -Zlib::MAX_WBITS}))
133
+ when "bzip2"
134
+ return Yajl::Bzip2::StreamReader.parse(socket, opts)
135
+ else
136
+ return Yajl::Parser.new(opts).parse(socket)
137
+ end
138
+ else
139
+ raise InvalidContentType, "The response MIME type #{content_type}"
140
+ end
141
+ end
142
+ rescue IOError => e
143
+ raise e unless Thread.current[:yajl_intentional_termination]
144
+ Thread.current[:yajl_intentional_termination] = nil
145
+ ensure
146
+ Thread.current[:yajl_socket] = nil
147
+ socket.close unless socket.closed?
148
+ end
149
+ end
150
+ end