├── .gitignore ├── Gemfile ├── LICENSE ├── README.md ├── Rakefile ├── ext └── gohttp │ ├── extconf.rb │ ├── gohttp.go │ └── wrapper.go ├── gohttp.gemspec ├── lib ├── gohttp.rb └── gohttp │ └── version.rb └── test.rb /.gitignore: -------------------------------------------------------------------------------- 1 | /.bundle/ 2 | /.yardoc 3 | /Gemfile.lock 4 | /_yardoc/ 5 | /coverage/ 6 | /doc/ 7 | /pkg/ 8 | /spec/reports/ 9 | /tmp/ 10 | *.bundle 11 | *.so 12 | *.swp 13 | *.o 14 | *.a 15 | mkmf.log 16 | gohttp.h 17 | gohttp.so 18 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gemspec 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (C) 2015 NARUSE, Yui. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions 5 | are met: 6 | 1. Redistributions of source code must retain the above copyright 7 | notice, this list of conditions and the following disclaimer. 8 | 2. Redistributions in binary form must reproduce the above copyright 9 | notice, this list of conditions and the following disclaimer in the 10 | documentation and/or other materials provided with the distribution. 11 | 12 | THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 13 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 14 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 15 | ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 16 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 17 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 18 | OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 19 | HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 20 | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 21 | OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 22 | SUCH DAMAGE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Gohttp 2 | 3 | A demo to implement a extension library with Go. 4 | 5 | ## Installation 6 | 7 | Add this line to your application's Gemfile: 8 | 9 | ```ruby 10 | gem 'gohttp' 11 | ``` 12 | 13 | And then execute: 14 | 15 | $ bundle 16 | 17 | Or install it yourself as: 18 | 19 | $ gem install gohttp 20 | 21 | ## Usage 22 | 23 | ```ruby 24 | require 'gohttp' 25 | r = Gohttp.get('https://example.com/index.html') 26 | puts r.body.read 27 | ``` 28 | 29 | ## Contributing 30 | 31 | 1. Fork it ( https://github.com/nurse/gohttp/fork ) 32 | 2. Create your feature branch (`git checkout -b my-new-feature`) 33 | 3. Commit your changes (`git commit -am 'Add some feature'`) 34 | 4. Push to the branch (`git push origin my-new-feature`) 35 | 5. Create a new Pull Request 36 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'bundler' 2 | Bundler::GemHelper.install_tasks 3 | require 'rspec/core/rake_task' 4 | require 'rake/extensiontask' 5 | 6 | RSpec::Core::RakeTask.new(:spec) 7 | task :default => [:compile, :spec] 8 | 9 | spec = eval File.read('gohttp.gemspec') 10 | Rake::ExtensionTask.new('gohttp', spec) do |ext| 11 | ext.ext_dir = 'ext/gohttp' 12 | ext.lib_dir = File.join(*['lib', 'gohttp', ENV['FAT_DIR']].compact) 13 | ext.source_pattern = "*.{c,cpp,go}" 14 | end 15 | 16 | RSpec::Core::RakeTask.new :spec do |spec| 17 | spec.pattern = 'spec/**/*_spec.rb' 18 | end 19 | -------------------------------------------------------------------------------- /ext/gohttp/extconf.rb: -------------------------------------------------------------------------------- 1 | require 'mkmf' 2 | find_executable('go') 3 | $objs = [] 4 | def $objs.empty?; false ;end 5 | create_makefile("gohttp/gohttp") 6 | case `#{CONFIG['CC']} --version` 7 | when /Free Software Foundation/ 8 | ldflags = '-Wl,--unresolved-symbols=ignore-all' 9 | when /clang/ 10 | ldflags = '-undefined dynamic_lookup' 11 | end 12 | File.open('Makefile', 'a') do |f| 13 | f.write < 5 | 6 | VALUE NewGoStruct(VALUE klass, void *p); 7 | void *GetGoStruct(VALUE obj); 8 | 9 | VALUE gohttp_hello(VALUE); 10 | VALUE gohttpGet(VALUE,VALUE); 11 | VALUE gohttpHead(VALUE,VALUE); 12 | int hash_of_ary_of_str2values_i(VALUE,VALUE,VALUE); 13 | VALUE gohttpPostForm(VALUE,VALUE,VALUE); 14 | VALUE gohttp_get_url(VALUE,VALUE); 15 | VALUE gourlQueryEscape(VALUE,VALUE); 16 | VALUE gourlQueryUnescape(VALUE,VALUE); 17 | 18 | VALUE goio_to_s(VALUE); 19 | VALUE goioRead(VALUE); 20 | VALUE goioClose(VALUE); 21 | 22 | VALUE reqMethod(VALUE); 23 | VALUE reqURL(VALUE); 24 | VALUE reqProto(VALUE); 25 | VALUE reqHeader(VALUE); 26 | VALUE reqBody(VALUE); 27 | VALUE reqContentLength(VALUE); 28 | VALUE reqTransferEncoding(VALUE); 29 | VALUE reqHost(VALUE); 30 | VALUE reqForm(VALUE); 31 | VALUE reqForm(VALUE); 32 | VALUE reqForm(VALUE); 33 | VALUE reqForm(VALUE); 34 | VALUE reqRemoteAddr(VALUE); 35 | VALUE reqRequestURI(VALUE); 36 | 37 | VALUE respContentLength(VALUE); 38 | VALUE respStatus(VALUE); 39 | VALUE respStatusCode(VALUE); 40 | VALUE respProto(VALUE); 41 | VALUE respTransferEncoding(VALUE); 42 | VALUE respHeader(VALUE); 43 | VALUE respBody(VALUE); 44 | VALUE respRequest(VALUE); 45 | */ 46 | import "C" 47 | 48 | import "fmt" 49 | import "io" 50 | import "io/ioutil" 51 | import "net/http" 52 | import "net/url" 53 | import "reflect" 54 | import "unsafe" 55 | 56 | func main() { 57 | } 58 | 59 | var rb_cGoIO C.VALUE 60 | var rb_cGohttp C.VALUE 61 | var rb_cGourl C.VALUE 62 | var rb_cRequest C.VALUE 63 | var rb_cResponse C.VALUE 64 | 65 | //export goobj_retain 66 | func goobj_retain(obj unsafe.Pointer) { 67 | objects[obj] = true 68 | } 69 | 70 | //export goobj_free 71 | func goobj_free(obj unsafe.Pointer) { 72 | delete(objects, obj) 73 | } 74 | 75 | func goioNew(klass C.VALUE, r interface{}) C.VALUE { 76 | v := reflect.ValueOf(r) 77 | return C.NewGoStruct(klass, unsafe.Pointer(&v)) 78 | } 79 | 80 | //export goio_to_s 81 | func goio_to_s(self C.VALUE) C.VALUE { 82 | v := (*reflect.Value)(C.GetGoStruct(self)) 83 | switch v.Kind() { 84 | case reflect.Struct: 85 | return RbString(fmt.Sprintf("#", v.Type().String())) 86 | case reflect.Chan, reflect.Map, reflect.Ptr, reflect.UnsafePointer: 87 | return RbString(fmt.Sprintf("#", v.Type().String(), v.Pointer())) 88 | default: 89 | return RbString(fmt.Sprintf("#", v.Type().String())) 90 | } 91 | } 92 | 93 | //export goioRead 94 | func goioRead(self C.VALUE) C.VALUE { 95 | v := (*reflect.Value)(C.GetGoStruct(self)) 96 | r := v.Interface().(io.Reader) 97 | body, err := ioutil.ReadAll(r) 98 | if err != nil { 99 | rb_raise(C.rb_eArgError, "'%s'", err) 100 | } 101 | return RbBytes(body) 102 | } 103 | 104 | //export goioClose 105 | func goioClose(self C.VALUE) C.VALUE { 106 | v := (*reflect.Value)(C.GetGoStruct(self)) 107 | if c, ok := v.Interface().(io.Closer); ok { 108 | c.Close() 109 | return C.Qtrue 110 | } 111 | return C.Qfalse 112 | } 113 | 114 | func reqNew(klass C.VALUE, r *http.Request) C.VALUE { 115 | return C.NewGoStruct(klass, unsafe.Pointer(r)) 116 | } 117 | 118 | //export reqMethod 119 | func reqMethod(self C.VALUE) C.VALUE { 120 | req := (*http.Request)(C.GetGoStruct(self)) 121 | return RbString(req.Method) 122 | } 123 | 124 | //export reqProto 125 | func reqProto(self C.VALUE) C.VALUE { 126 | req := (*http.Request)(C.GetGoStruct(self)) 127 | return RbString(req.Proto) 128 | } 129 | 130 | //export reqHeader 131 | func reqHeader(self C.VALUE) C.VALUE { 132 | req := (*http.Request)(C.GetGoStruct(self)) 133 | h := C.rb_hash_new() 134 | for key, value := range req.Header { 135 | C.rb_hash_aset(h, RbString(key), StrSlice2RbArray(value)) 136 | } 137 | return h 138 | } 139 | 140 | //export reqBody 141 | func reqBody(self C.VALUE) C.VALUE { 142 | req := (*http.Request)(C.GetGoStruct(self)) 143 | if req.Body == nil { 144 | return C.Qnil 145 | } 146 | return goioNew(rb_cGoIO, req.Body) 147 | } 148 | 149 | //export reqContentLength 150 | func reqContentLength(self C.VALUE) C.VALUE { 151 | req := (*http.Request)(C.GetGoStruct(self)) 152 | return INT64toNUM(req.ContentLength) 153 | } 154 | 155 | //export reqTransferEncoding 156 | func reqTransferEncoding(self C.VALUE) C.VALUE { 157 | req := (*http.Request)(C.GetGoStruct(self)) 158 | return StrSlice2RbArray(req.TransferEncoding) 159 | } 160 | 161 | //export reqHost 162 | func reqHost(self C.VALUE) C.VALUE { 163 | req := (*http.Request)(C.GetGoStruct(self)) 164 | return RbString(req.Host) 165 | } 166 | 167 | //export reqRemoteAddr 168 | func reqRemoteAddr(self C.VALUE) C.VALUE { 169 | req := (*http.Request)(C.GetGoStruct(self)) 170 | return RbString(req.RemoteAddr) 171 | } 172 | 173 | //export reqRequestURI 174 | func reqRequestURI(self C.VALUE) C.VALUE { 175 | req := (*http.Request)(C.GetGoStruct(self)) 176 | return RbString(req.RequestURI) 177 | } 178 | 179 | func respNew(klass C.VALUE, r *http.Response) C.VALUE { 180 | return C.NewGoStruct(klass, unsafe.Pointer(r)) 181 | } 182 | 183 | //export respContentLength 184 | func respContentLength(self C.VALUE) C.VALUE { 185 | resp := (*http.Response)(C.GetGoStruct(self)) 186 | return INT64toNUM(resp.ContentLength) 187 | } 188 | 189 | //export respHeader 190 | func respHeader(self C.VALUE) C.VALUE { 191 | resp := (*http.Response)(C.GetGoStruct(self)) 192 | h := C.rb_hash_new() 193 | for key, value := range resp.Header { 194 | C.rb_hash_aset(h, RbString(key), StrSlice2RbArray(value)) 195 | } 196 | return h 197 | } 198 | 199 | //export respStatus 200 | func respStatus(self C.VALUE) C.VALUE { 201 | resp := (*http.Response)(C.GetGoStruct(self)) 202 | return RbString(resp.Status) 203 | } 204 | 205 | //export respStatusCode 206 | func respStatusCode(self C.VALUE) C.VALUE { 207 | resp := (*http.Response)(C.GetGoStruct(self)) 208 | return INT2NUM(resp.StatusCode) 209 | } 210 | 211 | //export respProto 212 | func respProto(self C.VALUE) C.VALUE { 213 | resp := (*http.Response)(C.GetGoStruct(self)) 214 | return RbString(resp.Proto) 215 | } 216 | 217 | //export respTransferEncoding 218 | func respTransferEncoding(self C.VALUE) C.VALUE { 219 | resp := (*http.Response)(C.GetGoStruct(self)) 220 | return StrSlice2RbArray(resp.TransferEncoding) 221 | } 222 | 223 | //export respBody 224 | func respBody(self C.VALUE) C.VALUE { 225 | resp := (*http.Response)(C.GetGoStruct(self)) 226 | if resp.Body == nil { 227 | return C.Qnil 228 | } 229 | return goioNew(rb_cGoIO, resp.Body) 230 | } 231 | 232 | //export respRequest 233 | func respRequest(self C.VALUE) C.VALUE { 234 | resp := (*http.Response)(C.GetGoStruct(self)) 235 | return reqNew(rb_cRequest, resp.Request) 236 | } 237 | 238 | //export gohttp_hello 239 | func gohttp_hello(dummy C.VALUE) C.VALUE { 240 | fmt.Printf("hello, world\n") 241 | return C.Qnil 242 | } 243 | 244 | //export gohttpGet 245 | func gohttpGet(dummy C.VALUE, urlstr C.VALUE) C.VALUE { 246 | str := RbGoString(urlstr) 247 | resp, err := http.Get(str) 248 | if err != nil { 249 | rb_raise(C.rb_eArgError, "'%s'", err) 250 | } 251 | return respNew(rb_cResponse, resp) 252 | } 253 | 254 | //export gohttpHead 255 | func gohttpHead(dummy C.VALUE, urlstr C.VALUE) C.VALUE { 256 | str := RbGoString(urlstr) 257 | resp, err := http.Head(str) 258 | if err != nil { 259 | rb_raise(C.rb_eArgError, "'%s'", err) 260 | } 261 | return respNew(rb_cResponse, resp) 262 | } 263 | 264 | //export hash_of_ary_of_str2values_i 265 | func hash_of_ary_of_str2values_i(key C.VALUE, ary C.VALUE, arg C.VALUE) C.int { 266 | values := (*url.Values)(unsafe.Pointer(uintptr(arg))) 267 | values.Add(RbGoString(key), RbGoString(ary)) 268 | return C.ST_CONTINUE 269 | } 270 | 271 | //export gohttpPostForm 272 | func gohttpPostForm(dummy C.VALUE, urlstr C.VALUE, data C.VALUE) C.VALUE { 273 | str := RbGoString(urlstr) 274 | val := url.Values{} 275 | C.rb_hash_foreach(data, (*[0]byte)(C.hash_of_ary_of_str2values_i), C.VALUE(uintptr(unsafe.Pointer(&val)))) 276 | resp, err := http.PostForm(str, val) 277 | if err != nil { 278 | rb_raise(C.rb_eArgError, "'%s'", err) 279 | } 280 | return respNew(rb_cResponse, resp) 281 | } 282 | 283 | //export gohttp_get_url 284 | func gohttp_get_url(dummy C.VALUE, urlstr C.VALUE) C.VALUE { 285 | u, err := url.Parse(RbGoString(urlstr)) 286 | if err != nil { 287 | return C.Qnil 288 | } 289 | return RbString(u.String()) 290 | } 291 | 292 | //export gourlQueryEscape 293 | func gourlQueryEscape(dummy C.VALUE, s C.VALUE) C.VALUE { 294 | return RbString(url.QueryEscape(RbGoString(s))) 295 | } 296 | 297 | //export gourlQueryUnescape 298 | func gourlQueryUnescape(dummy C.VALUE, s C.VALUE) C.VALUE { 299 | src := RbGoString(s) 300 | str, err := url.QueryUnescape(src) 301 | if err != nil { 302 | rb_raise(C.rb_eArgError, "'%s' is not valid pct-encoded", src) 303 | } 304 | return RbString(str) 305 | } 306 | 307 | //export Init_gohttp 308 | func Init_gohttp() { 309 | sNew := "new" 310 | str_new := (*C.char)(unsafe.Pointer(&(*(*[]byte)(unsafe.Pointer(&sNew)))[0])) 311 | 312 | rb_cGoIO = rb_define_class("GoIO", C.rb_cIO) 313 | C.rb_undef_alloc_func(rb_cGoIO) 314 | C.rb_undef_method(C.rb_class_of(rb_cGoIO), str_new) 315 | rb_define_method(rb_cGoIO, "to_s", C.goio_to_s, 0) 316 | rb_define_method(rb_cGoIO, "inspect", C.goio_to_s, 0) 317 | rb_define_method(rb_cGoIO, "read", C.goioRead, 0) 318 | rb_define_method(rb_cGoIO, "close", C.goioClose, 0) 319 | 320 | rb_cRequest = rb_define_class("Request", C.rb_cObject) 321 | C.rb_undef_alloc_func(rb_cRequest) 322 | C.rb_undef_method(C.rb_class_of(rb_cRequest), str_new) 323 | rb_define_method(rb_cRequest, "method", C.reqMethod, 0) 324 | //rb_define_method(rb_cRequest, "url", C.reqURL, 0) 325 | rb_define_method(rb_cRequest, "proto", C.reqProto, 0) 326 | rb_define_method(rb_cRequest, "header", C.reqHeader, 0) 327 | rb_define_method(rb_cRequest, "body", C.reqBody, 0) 328 | rb_define_method(rb_cRequest, "content_length", C.reqContentLength, 0) 329 | rb_define_method(rb_cRequest, "transfer_encoding", C.reqTransferEncoding, 0) 330 | rb_define_method(rb_cRequest, "host", C.reqHost, 0) 331 | //rb_define_method(rb_cRequest, "form", C.reqForm, 0) 332 | //rb_define_method(rb_cRequest, "post_form", C.reqForm, 0) 333 | //rb_define_method(rb_cRequest, "multipart_form", C.reqForm, 0) 334 | //rb_define_method(rb_cRequest, "trailer", C.reqForm, 0) 335 | rb_define_method(rb_cRequest, "remote_addr", C.reqRemoteAddr, 0) 336 | rb_define_method(rb_cRequest, "request_uri", C.reqRequestURI, 0) 337 | 338 | rb_cResponse = rb_define_class("Response", C.rb_cObject) 339 | C.rb_undef_alloc_func(rb_cResponse) 340 | C.rb_undef_method(C.rb_class_of(rb_cResponse), str_new) 341 | rb_define_method(rb_cResponse, "size", C.respContentLength, 0) 342 | rb_define_method(rb_cResponse, "status", C.respStatus, 0) 343 | rb_define_method(rb_cResponse, "status_code", C.respStatusCode, 0) 344 | rb_define_method(rb_cResponse, "proto", C.respProto, 0) 345 | rb_define_method(rb_cResponse, "transfer_encoding", C.respTransferEncoding, 0) 346 | rb_define_method(rb_cResponse, "header", C.respHeader, 0) 347 | rb_define_method(rb_cResponse, "body", C.respBody, 0) 348 | rb_define_method(rb_cResponse, "request", C.respRequest, 0) 349 | 350 | rb_cGohttp = rb_define_class("Gohttp", C.rb_cObject) 351 | rb_define_singleton_method(rb_cGohttp, "hello", C.gohttp_hello, 0) 352 | rb_define_singleton_method(rb_cGohttp, "get", C.gohttpGet, 1) 353 | rb_define_singleton_method(rb_cGohttp, "head", C.gohttpHead, 1) 354 | //rb_define_singleton_method(rb_cGohttp, "post", C.gohttpPost, 1) 355 | rb_define_singleton_method(rb_cGohttp, "post_form", C.gohttpPostForm, 2) 356 | //rb_define_method(rb_cGohttp, "initialize", C.gohttp_initialize, 1) 357 | rb_define_method(rb_cGohttp, "get_url", C.gohttp_get_url, 1) 358 | 359 | rb_cGourl = rb_define_class("Gourl", C.rb_cObject) 360 | rb_define_singleton_method(rb_cGourl, "query_escape", C.gourlQueryEscape, 1) 361 | rb_define_singleton_method(rb_cGourl, "query_unescape", C.gourlQueryUnescape, 1) 362 | } 363 | -------------------------------------------------------------------------------- /ext/gohttp/wrapper.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | /* 4 | #include 5 | 6 | const char * 7 | rstring_ptr(VALUE str) { 8 | return RSTRING_PTR(str); 9 | } 10 | int 11 | rstring_len(VALUE str) { 12 | return RSTRING_LENINT(str); 13 | } 14 | void 15 | rb_raise2(VALUE exc, const char *str) { 16 | rb_raise(exc, "%s", str); 17 | } 18 | 19 | void goobj_retain(void *); 20 | void goobj_free(void *); 21 | 22 | static const rb_data_type_t go_type = { 23 | "GoStruct", 24 | {NULL, goobj_free, NULL}, 25 | 0, 0, RUBY_TYPED_FREE_IMMEDIATELY|RUBY_TYPED_WB_PROTECTED 26 | }; 27 | 28 | VALUE 29 | NewGoStruct(VALUE klass, void *p) 30 | { 31 | goobj_retain(p); 32 | return TypedData_Wrap_Struct((klass), &go_type, p); 33 | } 34 | 35 | void * 36 | GetGoStruct(VALUE obj) 37 | { 38 | void *val; 39 | return TypedData_Get_Struct((obj), void *, &go_type, (val)); 40 | } 41 | */ 42 | import "C" 43 | import "unsafe" 44 | import "fmt" 45 | 46 | var objects = make(map[interface{}]bool) 47 | 48 | func rb_define_method(klass C.VALUE, name string, fun unsafe.Pointer, args int) { 49 | cname := (*C.char)(unsafe.Pointer(&(*(*[]byte)(unsafe.Pointer(&name)))[0])) 50 | C.rb_define_method(klass, cname, (*[0]byte)(fun), C.int(args)) 51 | } 52 | 53 | func rb_define_singleton_method(klass C.VALUE, name string, fun unsafe.Pointer, args int) { 54 | cname := (*C.char)(unsafe.Pointer(&(*(*[]byte)(unsafe.Pointer(&name)))[0])) 55 | C.rb_define_singleton_method(klass, cname, (*[0]byte)(fun), C.int(args)) 56 | } 57 | 58 | func RbGoString(str C.VALUE) string { 59 | C.rb_string_value(&str) 60 | return C.GoStringN(C.rstring_ptr(str), C.rstring_len(str)) 61 | } 62 | 63 | func RbBytes(bytes []byte) C.VALUE { 64 | if len(bytes) == 0 { 65 | return C.rb_str_new(nil, C.long(0)) 66 | } 67 | cstr := (*C.char)(unsafe.Pointer(&bytes[0])) 68 | return C.rb_str_new(cstr, C.long(len(bytes))) 69 | } 70 | 71 | func RbString(str string) C.VALUE { 72 | if len(str) == 0 { 73 | return C.rb_utf8_str_new(nil, C.long(0)) 74 | } 75 | cstr := (*C.char)(unsafe.Pointer(&(*(*[]byte)(unsafe.Pointer(&str)))[0])) 76 | return C.rb_utf8_str_new(cstr, C.long(len(str))) 77 | } 78 | 79 | func rb_define_class(name string, parent C.VALUE) C.VALUE { 80 | cname := (*C.char)(unsafe.Pointer(&(*(*[]byte)(unsafe.Pointer(&name)))[0])) 81 | v := C.rb_define_class(cname, parent) 82 | return v 83 | } 84 | 85 | func rb_raise(exc C.VALUE, format string, a ...interface{}) { 86 | str := fmt.Sprintf(format, a...) 87 | cstr := (*C.char)(unsafe.Pointer(&(*(*[]byte)(unsafe.Pointer(&str)))[0])) 88 | C.rb_raise2(exc, cstr) 89 | } 90 | 91 | func INT2NUM(n int) C.VALUE { 92 | return C.rb_int2inum(C.long(n)) 93 | } 94 | 95 | func INT64toNUM(n int64) C.VALUE { 96 | return C.rb_ll2inum(C.longlong(n)) 97 | } 98 | 99 | func StrSlice2RbArray(slice []string) C.VALUE { 100 | ary := C.rb_ary_new_capa(C.long(len(slice))) 101 | for _, val := range slice { 102 | C.rb_ary_push(ary, RbString(val)) 103 | } 104 | return ary 105 | } 106 | -------------------------------------------------------------------------------- /gohttp.gemspec: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | lib = File.expand_path('../lib', __FILE__) 3 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 4 | require 'gohttp/version' 5 | 6 | Gem::Specification.new do |spec| 7 | spec.name = "gohttp" 8 | spec.version = Strptime::VERSION 9 | spec.authors = ["NARUSE, Yui"] 10 | spec.email = ["naruse@airemix.jp"] 11 | 12 | spec.summary = %q{an http library with go.} 13 | spec.description = %q{an http library with go.} 14 | spec.homepage = "https://github.com/nurse/gohttp" 15 | spec.license = "BSD-2-Clause" 16 | 17 | spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) } 18 | spec.bindir = "exe" 19 | spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } 20 | spec.require_paths = ["lib"] 21 | spec.extensions = ["ext/gohttp/extconf.rb"] 22 | spec.required_ruby_version = '~> 2.0' 23 | 24 | spec.add_development_dependency "bundler" 25 | spec.add_development_dependency "rake" 26 | spec.add_development_dependency "rake-compiler" 27 | spec.add_development_dependency "rspec" 28 | spec.add_development_dependency 'yard' 29 | end 30 | -------------------------------------------------------------------------------- /lib/gohttp.rb: -------------------------------------------------------------------------------- 1 | require "gohttp/version" 2 | begin 3 | require "gohttp/#{RUBY_VERSION[/\d+\.\d+/]}/gohttp" 4 | rescue LoadError 5 | require "gohttp/gohttp" 6 | end 7 | 8 | class Gohttp 9 | # Your code goes here... 10 | end 11 | -------------------------------------------------------------------------------- /lib/gohttp/version.rb: -------------------------------------------------------------------------------- 1 | class Strptime 2 | VERSION = "0.1.0" 3 | end 4 | -------------------------------------------------------------------------------- /test.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require 'gohttp' 4 | require 'minitest/autorun' 5 | 6 | class TestGohttp < Minitest::Test 7 | def test_hello 8 | assert_output("hello, world\n") do 9 | Gohttp.hello 10 | end 11 | end 12 | 13 | def test_hello 14 | o = Object.new 15 | def o.to_str; "https://例え.テスト/ファイル.html"; end 16 | g = Gohttp.new 17 | assert_equal "https://%E4%BE%8B%E3%81%88.%E3%83%86%E3%82%B9%E3%83%88/%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB.html", g.get_url(o) 18 | end 19 | 20 | def test_get 21 | r = Gohttp.get('https://example.com/index.html') 22 | assert_kind_of Response, r 23 | assert_equal '200 OK', r.status 24 | assert_equal 200, r.status_code 25 | assert_equal 'HTTP/1.1', r.proto 26 | assert_equal 1270, r.size 27 | assert_equal [], r.transfer_encoding 28 | hdr = r.header 29 | assert_kind_of Hash, hdr 30 | assert_equal %w[max-age=604800], hdr["Cache-Control"] 31 | assert_equal %w[text/html], hdr["Content-Type"] 32 | b = r.body 33 | assert_match /\A#/, b.to_s 34 | assert_match /Example Domain/, b.read 35 | assert_equal true, b.close 36 | req = r.request 37 | assert_equal 'GET', req.method 38 | assert_equal 'HTTP/1.1', req.proto 39 | assert_equal Hash[], req.header 40 | assert_nil req.body 41 | assert_equal 0, req.content_length 42 | assert_equal [], req.transfer_encoding 43 | assert_equal 'example.com', req.host 44 | assert_equal '', req.remote_addr 45 | assert_equal '', req.request_uri 46 | end 47 | 48 | def test_head 49 | r = Gohttp.head('https://example.com/index.html') 50 | assert_kind_of Response, r 51 | assert_equal '200 OK', r.status 52 | assert_equal 200, r.status_code 53 | assert_equal 'HTTP/1.1', r.proto 54 | assert_equal 1270, r.size 55 | assert_equal [], r.transfer_encoding 56 | hdr = r.header 57 | assert_kind_of Hash, hdr 58 | assert_equal %w[max-age=604800], hdr["Cache-Control"] 59 | assert_equal %w[text/html], hdr["Content-Type"] 60 | b = r.body 61 | assert_match /\A#/, b.to_s 62 | assert_match '', b.read 63 | assert_equal true, b.close 64 | req = r.request 65 | assert_equal 'HEAD', req.method 66 | assert_equal 'HTTP/1.1', req.proto 67 | assert_equal Hash[], req.header 68 | assert_nil req.body 69 | assert_equal 0, req.content_length 70 | assert_equal [], req.transfer_encoding 71 | assert_equal 'example.com', req.host 72 | assert_equal '', req.remote_addr 73 | assert_equal '', req.request_uri 74 | end 75 | 76 | def test_head 77 | r = Gohttp.post_form('http://nalsh.jp/env.cgi', {'foo' => 'bar'}) 78 | assert_kind_of Response, r 79 | assert_equal '200 OK', r.status 80 | assert_equal 200, r.status_code 81 | assert_equal 'HTTP/1.1', r.proto 82 | assert_equal -1, r.size 83 | assert_equal %w[chunked], r.transfer_encoding 84 | hdr = r.header 85 | assert_kind_of Hash, hdr 86 | assert_equal nil, hdr["Cache-Control"] 87 | assert_equal %w[text/html], hdr["Content-Type"] 88 | b = r.body 89 | assert_match /\A#/, b.to_s 90 | assert_match '', b.read 91 | assert_equal true, b.close 92 | req = r.request 93 | assert_equal 'POST', req.method 94 | assert_equal 'HTTP/1.1', req.proto 95 | assert_equal Hash["Content-Type"=>["application/x-www-form-urlencoded"]], req.header 96 | assert_match /\A#\z/, req.body.to_s 97 | assert_equal 7, req.content_length 98 | assert_equal [], req.transfer_encoding 99 | assert_equal 'nalsh.jp', req.host 100 | assert_equal '', req.remote_addr 101 | assert_equal '', req.request_uri 102 | end 103 | end 104 | 105 | class TestGourl < Minitest::Test 106 | def test_escape 107 | assert_equal '%E3%81%82+%2B', Gourl.query_escape('あ +') 108 | end 109 | 110 | def test_escape 111 | assert_equal 'あ ', Gourl.query_unescape('%E3%81%82%20+') 112 | assert_raises(ArgumentError){ Gourl.query_unescape('%%81%82%20+') } 113 | end 114 | end 115 | --------------------------------------------------------------------------------