[Groonga-commit] groonga/grnci at 09787f8 [master] Initial commit for v2.

Back to archive index

Susumu Yata null+****@clear*****
Thu May 11 19:51:22 JST 2017


Susumu Yata	2017-05-11 19:51:22 +0900 (Thu, 11 May 2017)

  New Revision: 09787f817d387454f5929d7e6c0f06e85c250d80
  https://github.com/groonga/grnci/commit/09787f817d387454f5929d7e6c0f06e85c250d80

  Message:
    Initial commit for v2.

  Added files:
    v2/address.go
    v2/argument.go
    v2/client.go
    v2/gqtp.go
    v2/http.go
    v2/request.go
    v2/respponse.go

  Added: v2/address.go (+208 -0) 100644
===================================================================
--- /dev/null
+++ v2/address.go    2017-05-11 19:51:22 +0900 (6ff2d8d)
@@ -0,0 +1,208 @@
+package grnci
+
+import (
+	"fmt"
+	"net"
+	"strconv"
+	"strings"
+)
+
+// Address represents a parsed address.
+// The expected address format is
+// [scheme://][username[:password]@]host[:port][path][?query][#fragment].
+type Address struct {
+	Raw      string
+	Scheme   string
+	Username string
+	Password string
+	Host     string
+	Port     int
+	Path     string
+	Query    string
+	Fragment string
+}
+
+// String reassembles the address fields except Raw into an address string.
+func (a *Address) String() string {
+	var url string
+	if a.Scheme != "" {
+		url += a.Scheme + "://"
+	}
+	if a.Password != "" {
+		url += a.Username + ":" + a.Password + "@"
+	} else if a.Username != "" {
+		url += a.Username + "@"
+	}
+	url += a.Host
+	if a.Port != 0 {
+		url += ":" + strconv.Itoa(a.Port)
+	}
+	url += a.Path
+	if a.Query != "" {
+		url += "?" + a.Query
+	}
+	if a.Fragment != "" {
+		url += "#" + a.Fragment
+	}
+	return url
+}
+
+const (
+	gqtpScheme      = "gqtp"
+	gqtpDefaultHost = "localhost"
+	gqtpDefaultPort = 10043
+)
+
+// fillGQTP checks fields and fills missing fields in a GQTP address.
+func (a *Address) fillGQTP() error {
+	if a.Username != "" {
+		return fmt.Errorf("invalid username: raw = %s", a.Raw)
+	}
+	if a.Password != "" {
+		return fmt.Errorf("invalid password: raw = %s", a.Raw)
+	}
+	if a.Host == "" {
+		a.Host = gqtpDefaultHost
+	}
+	if a.Port == 0 {
+		a.Port = gqtpDefaultPort
+	}
+	if a.Path != "" {
+		return fmt.Errorf("invalid path: raw = %s", a.Raw)
+	}
+	if a.Query != "" {
+		return fmt.Errorf("invalid query: raw = %s", a.Raw)
+	}
+	if a.Fragment != "" {
+		return fmt.Errorf("invalid fragment: raw = %s", a.Raw)
+	}
+	return nil
+}
+
+const (
+	httpScheme      = "http"
+	httpsScheme     = "https"
+	httpDefaultHost = "localhost"
+	httpDefaultPort = 10041
+	httpDefaultPath = "/d/"
+)
+
+// fillHTTP checks fields and fills missing fields in an HTTP address.
+func (a *Address) fillHTTP() error {
+	if a.Host == "" {
+		a.Host = httpDefaultHost
+	}
+	if a.Port == 0 {
+		a.Port = httpDefaultPort
+	}
+	if a.Path == "" {
+		a.Path = httpDefaultPath
+	}
+	if a.Query != "" {
+		return fmt.Errorf("invalid query: raw = %s", a.Raw)
+	}
+	if a.Fragment != "" {
+		return fmt.Errorf("invalid fragment: raw = %s", a.Raw)
+	}
+	return nil
+}
+
+const (
+	defaultScheme = gqtpScheme
+)
+
+// fill checks fields and fills missing fields.
+func (a *Address) fill() error {
+	if a.Scheme == "" {
+		a.Scheme = defaultScheme
+	} else {
+		a.Scheme = strings.ToLower(a.Scheme)
+	}
+	switch a.Scheme {
+	case gqtpScheme:
+		if err := a.fillGQTP(); err != nil {
+			return err
+		}
+	case httpScheme, httpsScheme:
+		if err := a.fillHTTP(); err != nil {
+			return err
+		}
+	default:
+		return fmt.Errorf("invalid scheme: raw = %s", a.Raw)
+	}
+	return nil
+}
+
+// ParseAddress parses an address.
+// The expected address format is
+// [scheme://][username[:password]@]host[:port][path][?query][#fragment].
+func ParseAddress(s string) (*Address, error) {
+	a := &Address{Raw: s}
+	if i := strings.IndexByte(s, '#'); i != -1 {
+		a.Fragment = s[i+1:]
+		s = s[:i]
+	}
+	if i := strings.IndexByte(s, '?'); i != -1 {
+		a.Query = s[i+1:]
+		s = s[:i]
+	}
+	if i := strings.Index(s, "://"); i != -1 {
+		a.Scheme = s[:i]
+		s = s[i+len("://"):]
+	}
+	if i := strings.IndexByte(s, '/'); i != -1 {
+		a.Path = s[i:]
+		s = s[:i]
+	}
+	if i := strings.IndexByte(s, '@'); i != -1 {
+		auth := s[:i]
+		if j := strings.IndexByte(auth, ':'); j != -1 {
+			a.Username = auth[:j]
+			a.Password = auth[j+1:]
+		} else {
+			a.Username = auth
+			a.Password = ""
+		}
+		s = s[i+1:]
+	}
+	if s == "" {
+		return a, nil
+	}
+
+	portStr := ""
+	if s[0] == '[' {
+		i := strings.IndexByte(s, ']')
+		if i == -1 {
+			return nil, fmt.Errorf("missing ']': s = %s", s)
+		}
+		a.Host = s[:i+1]
+		rest := s[i+1:]
+		if rest == "" {
+			return a, nil
+		}
+		if rest[0] != ':' {
+			return nil, fmt.Errorf("missing ':' after ']': s = %s", s)
+		}
+		portStr = rest[1:]
+	} else {
+		i := strings.LastIndexByte(s, ':')
+		if i == -1 {
+			a.Host = s
+			return a, nil
+		}
+		a.Host = s[:i]
+		portStr = s[i+1:]
+	}
+	if portStr != "" {
+		port, err := net.LookupPort("tcp", portStr)
+		if err != nil {
+			return nil, fmt.Errorf("net.LookupPort failed: %v", err)
+		}
+		a.Port = port
+	}
+
+	if err := a.fill(); err != nil {
+		return nil, err
+	}
+	return a, nil
+}

  Added: v2/argument.go (+53 -0) 100644
===================================================================
--- /dev/null
+++ v2/argument.go    2017-05-11 19:51:22 +0900 (96d9742)
@@ -0,0 +1,53 @@
+package grnci
+
+import (
+	"errors"
+	"fmt"
+)
+
+// Argument represents a named argument of a request.
+type Argument struct {
+	Key   string
+	Value string
+}
+
+// isDigit checks if c is a digit.
+func isDigit(c byte) bool {
+	return c >= '0' && c <= '9'
+}
+
+// isAlpha checks if c is an alphabet.
+func isAlpha(c byte) bool {
+	return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')
+}
+
+// isAlnum checks if c is a digit or alphabet.
+func isAlnum(c byte) bool {
+	return isDigit(c) || isAlpha(c)
+}
+
+// checkArgumentKey checks if s is valid as an argument key.
+func checkArgumentKey(s string) error {
+	if s == "" {
+		return errors.New("invalid format: s = ")
+	}
+	for i := 0; i < len(s); i++ {
+		if isAlnum(s[i]) {
+			continue
+		}
+		switch s[i] {
+		case '#', '@', '-', '_':
+		default:
+			return fmt.Errorf("invalid format: s = %s", s)
+		}
+	}
+	return nil
+}
+
+// Check checks if arg is valid.
+func (arg *Argument) Check() error {
+	if err := checkArgumentKey(arg.Key); err != nil {
+		return fmt.Errorf("checkArgumentKey failed: %v", err)
+	}
+	return nil
+}

  Added: v2/client.go (+78 -0) 100644
===================================================================
--- /dev/null
+++ v2/client.go    2017-05-11 19:51:22 +0900 (254bcb5)
@@ -0,0 +1,78 @@
+package grnci
+
+import (
+	"fmt"
+	"net"
+	"net/http"
+)
+
+// iClient is a Groonga client interface.
+type iClient interface {
+	Close() error
+	Query(*Request) (*Response, error)
+}
+
+// Client is a Groonga client.
+type Client struct {
+	iClient
+}
+
+// NewClient returns a new Client using an existing client.
+func NewClient(c iClient) *Client {
+	return &Client{iClient: c}
+}
+
+// NewClientByAddress returns a new Client to access a Groonga server.
+func NewClientByAddress(addr string) (*Client, error) {
+	a, err := ParseAddress(addr)
+	if err != nil {
+		return nil, err
+	}
+	switch a.Scheme {
+	case gqtpScheme:
+		c, err := dialGQTP(a)
+		if err != nil {
+			return nil, err
+		}
+		return NewClient(c), nil
+	case httpScheme, httpsScheme:
+		c, err := newHTTPClient(a, nil)
+		if err != nil {
+			return nil, err
+		}
+		return NewClient(c), nil
+	default:
+		return nil, fmt.Errorf("invalid scheme: raw = %s", a.Raw)
+	}
+}
+
+// NewGQTPClient returns a new Client using an existing connection.
+func NewGQTPClient(conn net.Conn) (*Client, error) {
+	c, err := newGQTPClient(conn)
+	if err != nil {
+		return nil, err
+	}
+	return NewClient(c), nil
+}
+
+// NewHTTPClient returns a new Client using an existing HTTP client.
+// If client is nil, NewHTTPClient uses http.DefaultClient.
+func NewHTTPClient(addr string, client *http.Client) (*Client, error) {
+	a, err := ParseAddress(addr)
+	if err != nil {
+		return nil, err
+	}
+	if client == nil {
+		client = http.DefaultClient
+	}
+	switch a.Scheme {
+	case httpScheme, httpsScheme:
+	default:
+		return nil, fmt.Errorf("invalid scheme: raw = %s", a.Raw)
+	}
+	c, err := newHTTPClient(a, client)
+	if err != nil {
+		return nil, err
+	}
+	return NewClient(c), nil
+}

  Added: v2/gqtp.go (+39 -0) 100644
===================================================================
--- /dev/null
+++ v2/gqtp.go    2017-05-11 19:51:22 +0900 (3e846f9)
@@ -0,0 +1,39 @@
+package grnci
+
+import (
+	"fmt"
+	"net"
+)
+
+// gqtpClient is a GQTP client.
+type gqtpClient struct {
+	conn net.Conn
+}
+
+// dialGQTP returns a new gqtpClient connected to a GQTP server.
+func dialGQTP(a *Address) (*gqtpClient, error) {
+	conn, err := net.Dial("tcp", fmt.Sprintf("%s:%d", a.Host, a.Port))
+	if err != nil {
+		return nil, err
+	}
+	return newGQTPClient(conn)
+}
+
+// newGQTPClient returns a new gqtpClient using an existing connection.
+func newGQTPClient(conn net.Conn) (*gqtpClient, error) {
+	return &gqtpClient{conn: conn}, nil
+}
+
+// Close closes the connection.
+func (c *gqtpClient) Close() error {
+	if err := c.conn.Close(); err != nil {
+		return err
+	}
+	return nil
+}
+
+// Query sends a request and receives a response.
+func (c *gqtpClient) Query(req *Request) (*Response, error) {
+	// TODO
+	return nil, nil
+}

  Added: v2/http.go (+39 -0) 100644
===================================================================
--- /dev/null
+++ v2/http.go    2017-05-11 19:51:22 +0900 (e342b31)
@@ -0,0 +1,39 @@
+package grnci
+
+import (
+	"fmt"
+	"net/http"
+	"net/url"
+)
+
+// httpClient is an HTTP client.
+type httpClient struct {
+	url    *url.URL
+	client *http.Client
+}
+
+// newHTTPClient returns a new httpClient.
+func newHTTPClient(a *Address, client *http.Client) (*httpClient, error) {
+	url, err := url.Parse(a.String())
+	if err != nil {
+		return nil, fmt.Errorf("url.Parse failed: %v", err)
+	}
+	if client == nil {
+		client = http.DefaultClient
+	}
+	return &httpClient{
+		url:    url,
+		client: client,
+	}, nil
+}
+
+// Close closes a client.
+func (c *httpClient) Close() error {
+	return nil
+}
+
+// Query sends a request and receives a response.
+func (c *httpClient) Query(req *Request) (*Response, error) {
+	// TODO
+	return nil, nil
+}

  Added: v2/request.go (+43 -0) 100644
===================================================================
--- /dev/null
+++ v2/request.go    2017-05-11 19:51:22 +0900 (dcd2fb4)
@@ -0,0 +1,43 @@
+package grnci
+
+import (
+	"errors"
+	"fmt"
+	"io"
+)
+
+// Request stores a Groonga command with arguments.
+type Request struct {
+	Cmd  string
+	Args []Argument
+	Body io.Reader
+}
+
+// checkCmd checks if s is valid as a command name.
+func checkCmd(s string) error {
+	if s == "" {
+		return errors.New("invalid name: s = ")
+	}
+	if s[0] == '_' {
+		return fmt.Errorf("invalid name: s = %s", s)
+	}
+	for i := 0; i < len(s); i++ {
+		if !(s[i] >= 'a' && s[i] <= 'z') && s[i] != '_' {
+			return fmt.Errorf("invalid name: s = %s", s)
+		}
+	}
+	return nil
+}
+
+// Check checks if req is valid.
+func (req *Request) Check() error {
+	if err := checkCmd(req.Cmd); err != nil {
+		return fmt.Errorf("CheckCmd failed: %v", err)
+	}
+	for _, arg := range req.Args {
+		if err := arg.Check(); err != nil {
+			return fmt.Errorf("arg.Check failed: %v", err)
+		}
+	}
+	return nil
+}

  Added: v2/respponse.go (+12 -0) 100644
===================================================================
--- /dev/null
+++ v2/respponse.go    2017-05-11 19:51:22 +0900 (437d876)
@@ -0,0 +1,12 @@
+package grnci
+
+import (
+	"time"
+)
+
+type Response struct {
+	Bytes   []byte
+	Error   error
+	Time    time.Time
+	Elapsed time.Duration
+}
-------------- next part --------------
HTML����������������������������...
Download 



More information about the Groonga-commit mailing list
Back to archive index