qpid-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From acon...@apache.org
Subject qpid-proton git commit: PROTON-827: Start of work on Go binding - Go wrapper for pn_url_t type.
Date Sun, 01 Mar 2015 03:56:35 GMT
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 <aconway@redhat.com>
Authored: Sat Feb 28 22:54:34 2015 -0500
Committer: Alan Conway <aconway@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


Mime
View raw message