You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@qpid.apache.org by Rafael Schloming <rh...@alum.mit.edu> on 2015/03/01 20:17:34 UTC

Re: qpid-proton git commit: PROTON-827: Start of work on Go binding - Go wrapper for pn_url_t type.

Hi Alan,

It's nice to see work starting in this area.

I'm wondering what your plan is in terms of timeline. I'm thinking if you
aren't explicitly targeting this for the 0.9 release, then maybe a feature
branch would be in order, just until we branch for the release? Otherwise
we may end up with non-building code in the release, which is obviously not
great.

--Rafael


On Sat, Feb 28, 2015 at 10:56 PM, <ac...@apache.org> wrote:

> Repository: qpid-proton
> Updated Branches:
>   refs/heads/master 8da4069cf -> 4b5796df4
>
>
> PROTON-827: Start of work on Go binding - Go wrapper for pn_url_t type.
>
> This commit creates a Go workspace with a package apache.org/proton
> containing
> a Url type that uses cgo to wrap the pn_url_t functions. It is a
> placeholder for
> future Go work. There is a README.md outlining the current thinking.
>
> The go directory is not yet linked to the cmake system, it will be
> eventually.
>
>
> Project: http://git-wip-us.apache.org/repos/asf/qpid-proton/repo
> Commit: http://git-wip-us.apache.org/repos/asf/qpid-proton/commit/4b5796df
> Tree: http://git-wip-us.apache.org/repos/asf/qpid-proton/tree/4b5796df
> Diff: http://git-wip-us.apache.org/repos/asf/qpid-proton/diff/4b5796df
>
> Branch: refs/heads/master
> Commit: 4b5796df46c68a82cb3ee90e3e6e9fa17b17364b
> Parents: 8da4069
> Author: Alan Conway <ac...@redhat.com>
> Authored: Sat Feb 28 22:54:34 2015 -0500
> Committer: Alan Conway <ac...@redhat.com>
> Committed: Sat Feb 28 22:55:08 2015 -0500
>
> ----------------------------------------------------------------------
>  proton-c/bindings/go/README.md                  |  98 ++++++++++++
>  .../bindings/go/src/apache.org/proton/doc.go    |  11 ++
>  .../bindings/go/src/apache.org/proton/url.go    | 149 +++++++++++++++++++
>  .../go/src/apache.org/proton/url_test.go        | 119 +++++++++++++++
>  4 files changed, 377 insertions(+)
> ----------------------------------------------------------------------
>
>
>
> http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/4b5796df/proton-c/bindings/go/README.md
> ----------------------------------------------------------------------
> diff --git a/proton-c/bindings/go/README.md
> b/proton-c/bindings/go/README.md
> new file mode 100644
> index 0000000..d2c8efe
> --- /dev/null
> +++ b/proton-c/bindings/go/README.md
> @@ -0,0 +1,98 @@
> +# Go binding for proton
> +
> +This is the (very early) beginning of a go binding for proton.
> +
> +## Layout
> +
> +The plan is to adopt standard Go tools and practices, see the <
> http://golang.org>
> +for what that means.
> +
> +This directory is layered out as a [Go work-space](
> http://golang.org/doc/code.html)
> +Only the `src/` directory is committed. Tools like `go build` will create
> output
> +directories such as `pkg` and `bin`, they must not be committed.
> +
> +Set `GOPATH=<this directory>[:<other directories>]` in your environment
> for the go tools.
> +
> +We will eventually have a cmake integration to drive the go tool-chain so
> that:
> +
> +- Users who don't know/care about Go can build, test & install everything
> with a single tool (cmake)
> +- Go developers can contribute using familiar Go tools.
> +
> +## New to Go?
> +
> +If you are new to Go (like me) then these are a good place to start:
> +
> +- [A Tour of Go](http://tour.golang.org)
> +- [Effective Go](http://golang.org/doc/effective_go.html)
> +
> +Then look at the tools and library docs at <http://golang.org> as you
> need them.
> +
> +The docs are short, readable and informative.  Even the language
> specification
> +<http://golang.org/ref/spec> is a readable and useful reference.
> +
> +My first impression is that Go is a little odd but approachable and has
> some
> +very interesting features. I look forward to writing some real code.
> +
> +Go has simple but effective tools for building, testing, documenting,
> +formatting, installing etc. So far I have used `go build`, `go run`, `go
> test`
> +and `godoc`. They all Just Worked with little or no head-scratching.  For
> +example, within 5 minutes of discovering `godoc` I had a searchable web
> site on
> +my laptop serving all the standard Go docs and the docs for the fledgling
> +proton package.
> +
> +"Simple but effective" seems to be a big part of the Go design philosophy.
> +
> +## Design of the binding
> +
> +The API will be based on the design of the reactive python API, some key
> features to preserve are:
> +
> +- client and server development.
> +- reactive and synchronous programming styles.
> +- incremental composition of functionality via handlers.
> +- default handlers to make simple tasks simple.
> +- deep access to AMQP protocol events when that is required.
> +
> +Go is it's own  language, and we have two goals to balance:
> +
> +- avoid needless deviation from existing APIs to lower the barrier to
> move between languages.
> +- present an idiomatic, easy-to-use, unsurprising Go API that takes full
> advantage of Go's features.
> +
> +The implementation will use `cgo` to call into the proton C reactor and
> other
> +proton C APIs. `cgo` is simpler than Swig, requires only knowledge of Go
> and C
> +and is effortlessly integrated into the Go tools.
> +
> +Go's use of channels for synchronization present interesting
> opportunities. Go
> +also supports traditional locking, so we could adopt locking strategies
> similar
> +to our other bindings, but we should investigate the Go-like
> alternatives. There
> +are anaolgies between Go channels and AMQP links that we will probably
> exploit.
> +
> +## State of the implementation
> +
> +So far we have a wrapper for `pn_url_t` with unit tests and docs. This
> just
> +gives us an initial workspace and exercies for the tools and establishes
> the
> +basics of using cgo to call into proton code.
> +
> +Here are some things you can do. First set your environment:
> +
> +    export GOPATH=<path to this directory>
> +
> +To see the docs as text:
> +
> +    godoc apache.org/proton
> +
> +To see them in your browser run this in the background and open
> +http://localhost:6060 in your browser:
> +
> +    godoc -http=:6060 -index=true&
> +
> +This gives you the full standard Go library documentation plus the proton
> +docs. The index takes a few minutes to generate so search may not work
> +immediately, but you can click "Packages" and "proton" to go straight to
> the
> +proton docs.
> +
> +To run the unit tests:
> +
> +    go test apache.org/proton
> +
> +All feedback most gratefully received especially from experience Go
> programmers
> +on issues of style, use of the language or use of the tools.
>
>
> http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/4b5796df/proton-c/bindings/go/src/apache.org/proton/doc.go
> ----------------------------------------------------------------------
> diff --git a/proton-c/bindings/go/src/apache.org/proton/doc.go
> b/proton-c/bindings/go/src/apache.org/proton/doc.go
> new file mode 100644
> index 0000000..f633d3c
> --- /dev/null
> +++ b/proton-c/bindings/go/src/apache.org/proton/doc.go
> @@ -0,0 +1,11 @@
> +/*
> +Package proton is a Go binding for the proton AMQP protocol engine.
> +
> +It alows you to construct and parse AMQP messages, and to implement AMQP
> +clients, servers and intermediaries that can exchange messages with any
> +AMQP 1.0 compliant endpoint.
> +
> +*/
> +package proton
> +
> +// This file is just for the package comment.
>
>
> http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/4b5796df/proton-c/bindings/go/src/apache.org/proton/url.go
> ----------------------------------------------------------------------
> diff --git a/proton-c/bindings/go/src/apache.org/proton/url.go
> b/proton-c/bindings/go/src/apache.org/proton/url.go
> new file mode 100644
> index 0000000..2304814
> --- /dev/null
> +++ b/proton-c/bindings/go/src/apache.org/proton/url.go
> @@ -0,0 +1,149 @@
> +package proton
> +
> +/*
> +#cgo LDFLAGS: -lqpid-proton
> +#include <stdlib.h>
> +#include <string.h>
> +#include <proton/url.h>
> +
> +// Helper function for setting URL fields.
> +typedef void (*setter_fn)(pn_url_t* url, const char* value);
> +inline void    set(pn_url_t *url, setter_fn s, const char* value) {
> +  s(url, value);
> +}
> +*/
> +import "C"
> +
> +import (
> +       "fmt"
> +       "net"
> +       "unsafe"
> +)
> +
> +// Url holds the fields of a parsed AMQP URL.
> +//
> +// An AMQP URL contains information
> +// to connect to a server (like any URL) and a path string that is
> interpreted
> +// by the remote server to identify an AMQP node such as a queue or topic.
> +//
> +// The fields are:
> +//
> +//  Scheme: "amqp" or "amqps" for AMQP over SSL
> +//  Username: user name for authentication
> +//  Password: password for authentication
> +//  Host: Host address, can be a DNS name or an IP address literal (v4 or
> v6)
> +//  Port: Note this is a string, not a number. It an be a numeric value
> like "5672" or a service name like "amqp"
> +//  Path: Interpeted by the remote end as an AMQP node address.
> +//  PortInt: int value of port, set after calling Validate()
> +//
> +// A URL string has the form "scheme://username:password@host:port/path".
> +// Partial URLs such as "host", "host:port", ":port" or "host/path" are
> allowed.
> +//
> +type Url struct {
> +       Scheme, Username, Password, Host, Port, Path string
> +       PortInt                                      int
> +}
> +
> +// urlFromC creates a Url and copies the fields from the C pn_url_t pnUrl
> +func urlFromC(pnUrl *C.pn_url_t) Url {
> +       return Url{
> +               C.GoString(C.pn_url_get_scheme(pnUrl)),
> +               C.GoString(C.pn_url_get_username(pnUrl)),
> +               C.GoString(C.pn_url_get_password(pnUrl)),
> +               C.GoString(C.pn_url_get_host(pnUrl)),
> +               C.GoString(C.pn_url_get_port(pnUrl)),
> +               C.GoString(C.pn_url_get_path(pnUrl)),
> +               0,
> +       }
> +}
> +
> +// ParseUrl parses a URL string and returns a Url and an error if it is
> not valid.
> +//
> +// For a partial URL string, missing components will be filled in with
> default values.
> +//
> +func ParseUrl(s string) (u Url, err error) {
> +       u, err = ParseUrlRaw(s)
> +       if err == nil {
> +               err = u.Validate()
> +       }
> +       if err != nil {
> +               return Url{}, err
> +       }
> +       return
> +}
> +
> +// ParseUrlRaw parses a URL string and returns a Url and an error if it
> is not valid.
> +//
> +// For a partial URL string, missing components will be empty strings ""
> in the Url struct.
> +// Error checking is limited, more errors are checked in Validate()
> +//
> +func ParseUrlRaw(s string) (u Url, err error) {
> +       cstr := C.CString(s)
> +       defer C.free(unsafe.Pointer(cstr))
> +       pnUrl := C.pn_url_parse(cstr)
> +       if pnUrl == nil {
> +               return Url{}, fmt.Errorf("proton: Invalid Url '%v'", s)
> +       }
> +       defer C.pn_url_free(pnUrl)
> +       u = urlFromC(pnUrl)
> +       return
> +}
> +
> +// newPnUrl creates a C pn_url_t and populate it with the fields from url.
> +func newPnUrl(url Url) *C.pn_url_t {
> +       pnUrl := C.pn_url()
> +       set := func(setter unsafe.Pointer, value string) {
> +               if value != "" {
> +                       cstr := C.CString(value)
> +                       defer C.free(unsafe.Pointer(cstr))
> +                       C.set(pnUrl, C.setter_fn(setter), cstr)
> +               }
> +       }
> +       set(C.pn_url_set_scheme, url.Scheme)
> +       set(C.pn_url_set_username, url.Username)
> +       set(C.pn_url_set_password, url.Password)
> +       set(C.pn_url_set_host, url.Host)
> +       set(C.pn_url_set_port, url.Port)
> +       set(C.pn_url_set_path, url.Path)
> +       return pnUrl
> +}
> +
> +// String generates a representation of the Url.
> +func (u Url) String() string {
> +       pnUrl := newPnUrl(u)
> +       defer C.pn_url_free(pnUrl)
> +       return C.GoString(C.pn_url_str(pnUrl))
> +}
> +
> +// Validate supplies defaults for Scheme, Host and Port and does
> additional error checks.
> +//
> +// Fills in the value of PortInt from Port.
> +//
> +func (u *Url) Validate() error {
> +       if u.Scheme == "" {
> +               u.Scheme = "amqp"
> +       }
> +       if u.Host == "" {
> +               u.Host = "127.0.0.1"
> +       }
> +       if u.Port == "" {
> +               if u.Scheme == "amqps" {
> +                       u.Port = "amqps"
> +               } else {
> +                       u.Port = "amqp"
> +               }
> +       }
> +       port, err := net.LookupPort("", u.Port)
> +       u.PortInt = port
> +       if u.PortInt == 0 || err != nil {
> +               return fmt.Errorf("proton: Invalid Url '%v' (bad port
> '%v')", u, u.Port)
> +       }
> +       return nil
> +}
> +
> +// Equals tests for equality between two Urls.
> +func (u Url) Equals(x Url) bool {
> +       return (&u == &x) || (u.Scheme == x.Scheme &&
> +               u.Username == x.Username && u.Password == x.Password &&
> +               u.Host == x.Host && u.Port == x.Port && u.Path == x.Path)
> +}
>
>
> http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/4b5796df/proton-c/bindings/go/src/apache.org/proton/url_test.go
> ----------------------------------------------------------------------
> diff --git a/proton-c/bindings/go/src/apache.org/proton/url_test.go
> b/proton-c/bindings/go/src/apache.org/proton/url_test.go
> new file mode 100644
> index 0000000..1c3128a
> --- /dev/null
> +++ b/proton-c/bindings/go/src/apache.org/proton/url_test.go
> @@ -0,0 +1,119 @@
> +package proton
> +
> +import (
> +       "fmt"
> +       "reflect"
> +       "testing"
> +)
> +
> +func checkEqual(t *testing.T, a, b interface{}) string {
> +       if !reflect.DeepEqual(a, b) {
> +               return fmt.Sprintf("%#v != %#v", a, b)
> +       }
> +       return ""
> +}
> +
> +// Verify URL matches fields, check round trip compose/parse
> +func checkUrl(t *testing.T, u Url, scheme, username, password, host,
> port, path string) string {
> +       if msg := checkEqual(t, u.Scheme, scheme); msg != "" {
> +               return msg
> +       }
> +       if msg := checkEqual(t, u.Username, username); msg != "" {
> +               return msg
> +       }
> +       if msg := checkEqual(t, u.Password, password); msg != "" {
> +               return msg
> +       }
> +       if msg := checkEqual(t, u.Host, host); msg != "" {
> +               return msg
> +       }
> +       if msg := checkEqual(t, u.Port, port); msg != "" {
> +               return msg
> +       }
> +       if msg := checkEqual(t, u.Path, path); msg != "" {
> +               return msg
> +       }
> +       u2, _ := ParseUrlRaw(u.String()) // Round trip compose/parse
> +       if msg := checkEqual(t, u.String(), u2.String()); msg != "" {
> +               return msg
> +       }
> +       return ""
> +}
> +
> +func TestParse(t *testing.T) {
> +       s := "amqp://me:secret@myhost:1234/foobar"
> +       u, _ := ParseUrl(s)
> +       if msg := checkEqual(t, u.String(), s); msg != "" {
> +               t.Error(msg)
> +       }
> +       if msg := checkUrl(t, u, "amqp", "me", "secret", "myhost", "1234",
> "foobar"); msg != "" {
> +               t.Error(msg)
> +       }
> +}
> +
> +// Test URL's with missing elements.
> +func TestMissing(t *testing.T) {
> +       u := Url{Username: "me", Password: "secret", Host: "myhost", Path:
> "foobar"}
> +       if msg := checkUrl(t, u, "", "me", "secret", "myhost", "",
> "foobar"); msg != "" {
> +               t.Error(msg)
> +       }
> +       if msg := checkEqual(t, "me:secret@myhost/foobar", u.String());
> msg != "" {
> +               t.Error(msg)
> +       }
> +       u, _ = ParseUrlRaw("myhost/")
> +       if msg := checkUrl(t, u, "", "", "", "myhost", "", ""); msg != "" {
> +               t.Error(msg)
> +       }
> +}
> +
> +func TestDefaults(t *testing.T) {
> +       for pre, post := range map[string]string{
> +               "foo":      "amqp://foo:amqp",
> +               ":1234":    "amqp://127.0.0.1:1234",
> +               "/path":    "amqp://127.0.0.1:amqp/path",
> +               "amqp://":  "amqp://127.0.0.1:amqp",
> +               "amqps://": "amqps://127.0.0.1:amqps",
> +       } {
> +               url, _ := ParseUrl(pre)
> +               if msg := checkEqual(t, url.String(), post); msg != "" {
> +                       t.Error(msg)
> +               }
> +       }
> +}
> +
> +func ExampleParseUrl_1parse() {
> +       url, _ := ParseUrl("amqp://username:password@host:1234/path")
> +       fmt.Printf("%#v\n", url) // Show the struct fields.
> +       fmt.Println(url)         // url.String() shows the string form
> +       // Output:
> +       // proton.Url{Scheme:"amqp", Username:"username",
> Password:"password", Host:"host", Port:"1234", Path:"path", PortInt:1234}
> +       // amqp://username:password@host:1234/path
> +}
> +
> +func ExampleParseUrl_2defaults() {
> +       // Default settings for partial URLs
> +       url, _ := ParseUrl(":5672")
> +       fmt.Println(url)
> +       url, _ = ParseUrl("host/path")
> +       fmt.Println(url)
> +       url, _ = ParseUrl("amqp://")
> +       fmt.Printf("%v port=%v\n", url, url.PortInt)
> +       url, _ = ParseUrl("amqps://")
> +       fmt.Printf("%v port=%v\n", url, url.PortInt)
> +       // Output:
> +       // amqp://127.0.0.1:5672
> +       // amqp://host:amqp/path
> +       // amqp://127.0.0.1:amqp port=5672
> +       // amqps://127.0.0.1:amqps port=5671
> +}
> +
> +func ExampleParseUrl_3invalid() {
> +       // Invalid URLs
> +       _, err := ParseUrl("")
> +       fmt.Println(err)
> +       _, err = ParseUrl(":foobar")
> +       fmt.Println(err)
> +       // Output:
> +       // proton: Invalid Url ''
> +       // proton: Invalid Url 'amqp://127.0.0.1:foobar' (bad port
> 'foobar')
> +}
>
>
> ---------------------------------------------------------------------
> To unsubscribe, e-mail: commits-unsubscribe@qpid.apache.org
> For additional commands, e-mail: commits-help@qpid.apache.org
>
>