[Groonga-commit] groonga/grnci at afb20ed [master] Partially support HTTP.

Back to archive index

Susumu Yata null+****@clear*****
Fri Jul 1 16:29:54 JST 2016


Susumu Yata	2016-07-01 16:29:54 +0900 (Fri, 01 Jul 2016)

  New Revision: afb20ed28e6febdfabdf868412a27dc08a9e98ef
  https://github.com/groonga/grnci/commit/afb20ed28e6febdfabdf868412a27dc08a9e98ef

  Message:
    Partially support HTTP.
    
    GitHub: #29

  Modified files:
    db.go

  Modified: db.go (+151 -5)
===================================================================
--- db.go    2016-04-10 16:51:56 +0900 (0e57a95)
+++ db.go    2016-07-01 16:29:54 +0900 (fa39fff)
@@ -8,13 +8,17 @@ import "C"
 
 import (
 	"bytes"
+	"encoding/json"
 	"fmt"
+	"io/ioutil"
+	"net/http"
+	"net/url"
+	"path"
 	"strings"
 	"sync"
 	"unsafe"
 )
 
-// FIXME: utility function.
 // joinErrors joins errors.
 func joinErrors(errs []error) error {
 	if len(errs) == 1 {
@@ -32,6 +36,7 @@ const (
 	InvalidDB = DBMode(iota) // Invalid instance
 	LocalDB                  // Handle to a local Groonga DB
 	GQTPClient               // Connection to a GQTP Groonga server
+	HTTPClient               // Connection to an HTTP Groonga server
 )
 
 // localDB is a handle to a local Groonga DB.
@@ -73,7 +78,10 @@ func (db *localDB) fin() error {
 
 func (db *localDB) errorf(format string, args ...interface{}) error {
 	msg := fmt.Sprintf(format, args...)
-	if (db == nil) || (db.ctx == nil) || (db.ctx.rc == C.GRN_SUCCESS) {
+	if db == nil {
+		return fmt.Errorf("%s", msg)
+	}
+	if (db.ctx == nil) || (db.ctx.rc == C.GRN_SUCCESS) {
 		return fmt.Errorf("%s: path = \"%s\"", msg, db.path)
 	}
 	ctxMsg := C.GoString(&db.ctx.errbuf[0])
@@ -320,7 +328,10 @@ func (db *gqtpClient) fin() error {
 
 func (db *gqtpClient) errorf(format string, args ...interface{}) error {
 	msg := fmt.Sprintf(format, args...)
-	if (db == nil) || (db.ctx == nil) || (db.ctx.rc == C.GRN_SUCCESS) {
+	if db == nil {
+		return fmt.Errorf("%s", msg)
+	}
+	if (db.ctx == nil) || (db.ctx.rc == C.GRN_SUCCESS) {
 		return fmt.Errorf("%s: host = \"%s\", port = %d", msg, db.host, db.port)
 	}
 	ctxMsg := C.GoString(&db.ctx.errbuf[0])
@@ -436,6 +447,99 @@ func (db *gqtpClient) query(name string, args []cmdArg, data []byte) ([]byte, er
 	return db.recv()
 }
 
+// httpClient is a connection to an HTTP Groonga server.
+type httpClient struct {
+	client *http.Client // HTTP client
+	addr   string       // Server address, e.g. http://localhost:10041
+}
+
+func newHTTPClient() *httpClient {
+	return &httpClient{}
+}
+
+func (db *httpClient) fin() error {
+	if db == nil {
+		return fmt.Errorf("db is nil")
+	}
+	return nil
+}
+
+func (db *httpClient) errorf(format string, args ...interface{}) error {
+	msg := fmt.Sprintf(format, args...)
+	if db == nil {
+		return fmt.Errorf("%s", msg)
+	}
+	return fmt.Errorf("%s: addr = \"%s\"", msg, db.addr)
+}
+
+func (db *httpClient) check() error {
+	if db == nil {
+		return fmt.Errorf("db = nil")
+	}
+	if db.client == nil {
+		return fmt.Errorf("client = nil")
+	}
+	return nil
+}
+
+func openHTTPClient(addr string, client *http.Client) (*httpClient, error) {
+	return &httpClient{
+		client: client,
+		addr:   addr,
+	}, nil
+}
+
+// query sends a command and receives the response.
+func (db *httpClient) query(name string, args []cmdArg, data []byte) ([]byte, error) {
+	u, err := url.Parse(db.addr)
+	if err != nil {
+		return nil, db.errorf("url.Parse failed: %v", err)
+	}
+	u.Path = path.Join(u.Path, "select")
+	if len(args) != 0 {
+		q := u.Query()
+		for _, arg := range args {
+			q.Set(arg.key, arg.val)
+		}
+		u.RawQuery = q.Encode()
+	}
+	addr := u.String()
+
+	var respBytes []byte
+	if len(data) == 0 {
+		resp, err := db.client.Get(addr)
+		if err != nil {
+			return nil, db.errorf("db.client.Get failed: %v", err)
+		}
+		defer resp.Body.Close()
+		if respBytes, err = ioutil.ReadAll(resp.Body); err != nil {
+			return nil, db.errorf("ioutil.ReadAll failed: %v", err)
+		}
+	} else {
+		resp, err := db.client.Post(addr, "application/json",
+			bytes.NewReader(data))
+		if err != nil {
+			return nil, db.errorf("db.client.Post failed: %v", err)
+		}
+		defer resp.Body.Close()
+		if respBytes, err = ioutil.ReadAll(resp.Body); err != nil {
+			return nil, db.errorf("ioutil.ReadAll failed: %v", err)
+		}
+	}
+	var rawMsgs []json.RawMessage
+	if err := json.Unmarshal(respBytes, &rawMsgs); err != nil {
+		return nil, db.errorf("json.Unmarshal failed: %v", err)
+	}
+	switch len(rawMsgs) {
+	case 0:
+		return nil, db.errorf("failed")
+	case 2:
+		return rawMsgs[1], nil
+	default:
+		return nil, db.errorf("failed: %s", rawMsgs[0])
+	}
+}
+
 // DB is a handle to a Groonga DB or a connection to a Groonga server.
 //
 // Note that DB is not thread-safe.
@@ -444,6 +548,7 @@ type DB struct {
 	mode       DBMode      // Mode
 	localDB    *localDB    // Handle to a local Groonga DB
 	gqtpClient *gqtpClient // Connection to a GQTP Groonga server
+	httpClient *httpClient // Connection to an HTTP Groonga server
 }
 
 // Mode returns the DB mode.
@@ -508,6 +613,8 @@ func (db *DB) check() error {
 		return db.localDB.check()
 	case GQTPClient:
 		return db.gqtpClient.check()
+	case HTTPClient:
+		return db.httpClient.check()
 	default:
 		return fmt.Errorf("undefined mode: mode = %d", db.mode)
 	}
@@ -537,6 +644,10 @@ func (db *DB) fin() error {
 		if err := db.gqtpClient.fin(); err != nil {
 			errs = append(errs, err)
 		}
+	case HTTPClient:
+		if err := db.httpClient.fin(); err != nil {
+			errs = append(errs, err)
+		}
 	}
 	if err := grnFin(); err != nil {
 		errs = append(errs, err)
@@ -588,7 +699,7 @@ func Open(path string) (*DB, error) {
 	return db, nil
 }
 
-// Connect establishes a connection to a server.
+// Connect establishes a connection to a GQTP Groonga server.
 // The connection must be closed by DB.Close.
 func Connect(host string, port int) (*DB, error) {
 	if host == "" {
@@ -610,6 +721,31 @@ func Connect(host string, port int) (*DB, error) {
 	return db, nil
 }
 
+// Connect establishes a connection to an HTTP Groonga server.
+// The connection must be closed by DB.Close.
+func ConnectHTTP(addr string, client *http.Client) (*DB, error) {
+	if addr == "" {
+		return nil, fmt.Errorf("addr = \"\"")
+	}
+	if client == nil {
+		client = http.DefaultClient
+	}
+	db, err := newDB()
+	if err != nil {
+		return nil, err
+	}
+	var errs []error
+	if db.httpClient, err = openHTTPClient(addr, client); err != nil {
+		errs = append(errs, err)
+		if err := db.fin(); err != nil {
+			errs = append(errs, err)
+		}
+		return nil, joinErrors(errs)
+	}
+	db.mode = HTTPClient
+	return db, nil
+}
+
 // Dup duplicates a DB instance.
 // The instance must be closed by DB.Close.
 func (db *DB) Dup() (*DB, error) {
@@ -634,8 +770,12 @@ func (db *DB) Dup() (*DB, error) {
 		}
 		dupDB.mode = db.mode
 		return dupDB, nil
-	default:
+	case GQTPClient:
 		return Connect(db.Host(), db.Port())
+	case HTTPClient:
+		return ConnectHTTP(db.httpClient.addr, db.httpClient.client)
+	default:
+		return nil, fmt.Errorf("undefined mode: %v", db.mode)
 	}
 }
 
@@ -654,6 +794,8 @@ func (db *DB) errorf(format string, args ...interface{}) error {
 		return db.localDB.errorf(format, args...)
 	case GQTPClient:
 		return db.gqtpClient.errorf(format, args...)
+	case HTTPClient:
+		return db.httpClient.errorf(format, args...)
 	default:
 		return fmt.Errorf(format, args...)
 	}
@@ -674,6 +816,8 @@ func (db *DB) exec(data []byte) ([]byte, error) {
 			return resp, err
 		}
 		return db.gqtpClient.recv()
+	case HTTPClient:
+		return nil, fmt.Errorf("httpClient does not support exec.")
 	default:
 		return nil, fmt.Errorf("invalid mode: %d", db.mode)
 	}
@@ -686,6 +830,8 @@ func (db *DB) query(name string, args []cmdArg, data []byte) ([]byte, error) {
 		return db.localDB.query(name, args, data)
 	case GQTPClient:
 		return db.gqtpClient.query(name, args, data)
+	case HTTPClient:
+		return db.httpClient.query(name, args, data)
 	default:
 		return nil, fmt.Errorf("invalid mode: %d", db.mode)
 	}
-------------- next part --------------
HTML����������������������������...
下载 



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