Skip to content

Commit

Permalink
Merge pull request #394 from goby-lang/feature/http-upgrades
Browse files Browse the repository at this point in the history
Feature/http upgrades
  • Loading branch information
st0012 authored Sep 18, 2017
2 parents e5e8015 + 9d74ed6 commit a8f7d17
Show file tree
Hide file tree
Showing 7 changed files with 512 additions and 22 deletions.
10 changes: 10 additions & 0 deletions samples/http-client.gb
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
require "net/http"

c = Net::HTTP::Client.new

res = c.send do |req|
req.url = "https://google.com"
req.method = "GET"
end

puts res.body
2 changes: 1 addition & 1 deletion vm/error.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ func (vm *VM) initErrorObject(errorType, format string, args ...interface{}) *Er
}

func (vm *VM) initErrorClasses() {
errTypes := []string{errors.InternalError, errors.ArgumentError, errors.NameError, errors.TypeError, errors.UndefinedMethodError, errors.UnsupportedMethodError, errors.ConstantAlreadyInitializedError}
errTypes := []string{errors.InternalError, errors.ArgumentError, errors.NameError, errors.TypeError, errors.UndefinedMethodError, errors.UnsupportedMethodError, errors.ConstantAlreadyInitializedError, errors.HTTPError}

for _, errType := range errTypes {
c := vm.initializeClass(errType, false)
Expand Down
2 changes: 2 additions & 0 deletions vm/errors/error.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ const (
UnsupportedMethodError = "UnsupportedMethodError"
// ConstantAlreadyInitializedError means user re-declares twice
ConstantAlreadyInitializedError = "ConstantAlreadyInitializedError"
// HTTPError is returned when when a request fails to return a proper response
HTTPError = "HTTPError"
)

/*
Expand Down
117 changes: 99 additions & 18 deletions vm/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,36 +14,44 @@ import (
var (
httpRequestClass *RClass
httpResponseClass *RClass
httpClientClass *RClass
)

// Class methods --------------------------------------------------------
func builtinHTTPClassMethods() []*BuiltinMethodObject {
return []*BuiltinMethodObject{
{
// Sends a GET request to the target and returns the HTTP response as a string.
// Sends a GET request to the target and returns the HTTP response as a string. Will error on non-200 responses, for more control over http requests look at the `start` method.
Name: "get",
Fn: func(receiver Object) builtinMethodBody {
return func(t *thread, args []Object, blockFrame *callFrame) Object {
arg0, ok := args[0].(*StringObject)
if !ok {
return t.vm.initErrorObject(errors.ArgumentError, "Expect argument 0 to be string, got: %s", args[0].Class().Name)
}

uri, err := url.Parse(args[0].(*StringObject).value)
uri, err := url.Parse(arg0.value)

if len(args) > 1 {
var arr []string

for _, v := range args[1:] {
arr = append(arr, v.(*StringObject).value)
for i, v := range args[1:] {
argn, ok := v.(*StringObject)
if !ok {
return t.vm.initErrorObject(errors.ArgumentError, "Splat arguments must be a string, got: %s for argument %d", v.Class().Name, i)
}
arr = append(arr, argn.value)
}

uri.Path = path.Join(arr...)
}

resp, err := http.Get(uri.String())

if err != nil {
return t.vm.initErrorObject(errors.InternalError, err.Error())
return t.vm.initErrorObject(errors.HTTPError, "Could not complete request, %s", err)
}
if resp.StatusCode != http.StatusOK {
return t.vm.initErrorObject(errors.InternalError, resp.Status)
return t.vm.initErrorObject(errors.HTTPError, "Non-200 response, %s (%d)", resp.Status, resp.StatusCode)
}

content, err := ioutil.ReadAll(resp.Body)
Expand All @@ -57,30 +65,38 @@ func builtinHTTPClassMethods() []*BuiltinMethodObject {
}
},
}, {
// Sends a POST request to the target with type header and body. Returns the HTTP response as a string.
// Sends a POST request to the target with type header and body. Returns the HTTP response as a string. Will error on non-200 responses, for more control over http requests look at the `start` method.
Name: "post",
Fn: func(receiver Object) builtinMethodBody {
return func(t *thread, args []Object, blockFrame *callFrame) Object {
if len(args) != 3 {
return t.vm.initErrorObject(errors.ArgumentError, "Expect 3 arguments. got=%v", strconv.Itoa(len(args)))
return t.vm.initErrorObject(errors.ArgumentError, errors.WrongNumberOfArgumentFormat, 3, len(args))
}

uri, err := url.Parse(args[0].(*StringObject).value)
if err != nil {
return t.vm.initErrorObject(errors.ArgumentError, err.Error())
arg0, ok := args[0].(*StringObject)
if !ok {
return t.vm.initErrorObject(errors.ArgumentError, "Expect argument 0 to be string, got: %s", args[0].Class().Name)
}
host := arg0.value

contentType := args[1].(*StringObject).value

body := args[2].(*StringObject).value
arg1, ok := args[1].(*StringObject)
if !ok {
return t.vm.initErrorObject(errors.ArgumentError, "Expect argument 1 to be string, got: %s", args[0].Class().Name)
}
contentType := arg1.value

resp, err := http.Post(uri.String(), contentType, strings.NewReader(body))
arg2, ok := args[2].(*StringObject)
if !ok {
return t.vm.initErrorObject(errors.ArgumentError, "Expect argument 2 to be string, got: %s", args[0].Class().Name)
}
body := arg2.value

resp, err := http.Post(host, contentType, strings.NewReader(body))
if err != nil {
return t.vm.initErrorObject(errors.InternalError, err.Error())
return t.vm.initErrorObject(errors.HTTPError, "Could not complete request, %s", err)
}
if resp.StatusCode != http.StatusOK {
return t.vm.initErrorObject(errors.InternalError, resp.Status)
return t.vm.initErrorObject(errors.HTTPError, "Non-200 response, %s (%d)", resp.Status, resp.StatusCode)
}

content, err := ioutil.ReadAll(resp.Body)
Expand All @@ -93,6 +109,70 @@ func builtinHTTPClassMethods() []*BuiltinMethodObject {
return t.vm.initStringObject(string(content))
}
},
}, {
// Sends a HEAD request to the target with type header and body. Returns the HTTP headers as a map[string]string. Will error on non-200 responses, for more control over http requests look at the `start` method.
Name: "head",
Fn: func(receiver Object) builtinMethodBody {
return func(t *thread, args []Object, blockFrame *callFrame) Object {
arg0, ok := args[0].(*StringObject)
if !ok {
return t.vm.initErrorObject(errors.ArgumentError, "Expect argument 0 to be string, got: %s", args[0].Class().Name)
}

uri, err := url.Parse(arg0.value)

if len(args) > 1 {
var arr []string

for i, v := range args[1:] {
argn, ok := v.(*StringObject)
if !ok {
return t.vm.initErrorObject(errors.ArgumentError, "Splat arguments must be a string, got: %s for argument %d", v.Class().Name, i)
}
arr = append(arr, argn.value)
}

uri.Path = path.Join(arr...)
}

resp, err := http.Head(uri.String())
if err != nil {
return t.vm.initErrorObject(errors.HTTPError, "Could not complete request, %s", err)
}
if resp.StatusCode != http.StatusOK {
return t.vm.initErrorObject(errors.HTTPError, "Non-200 response, %s (%d)", resp.Status, resp.StatusCode)
}

ret := t.vm.initHashObject(map[string]Object{})

for k, v := range resp.Header {
ret.Pairs[k] = t.vm.initStringObject(strings.Join(v, " "))
}

return ret
}
},
}, {
// Starts an HTTP client. This method requires a block which takes a Net::HTTP::Client object. The return value of this method is the last evaluated value of the provided block.
Name: "start",
Fn: func(receiver Object) builtinMethodBody {
return func(t *thread, args []Object, blockFrame *callFrame) Object {

if len(args) != 0 {
return t.vm.initErrorObject(errors.ArgumentError, "Expect 0 arguments. got=%v", strconv.Itoa(len(args)))
}

gobyClient := httpClientClass.initializeInstance()

result := t.builtinMethodYield(blockFrame, gobyClient)

if err, ok := result.Target.(*Error); ok {
return err //an Error object
}

return result.Target
}
},
},
}
}
Expand All @@ -107,6 +187,7 @@ func initHTTPClass(vm *VM) {
http.setBuiltinMethods(builtinHTTPClassMethods(), true)
initRequestClass(vm, http)
initResponseClass(vm, http)
initClientClass(vm, http)

net.setClassConstant(http)

Expand Down
Loading

0 comments on commit a8f7d17

Please sign in to comment.