Quellcode durchsuchen

Working Web API using config, as 'kurzd serve api'.

Frederic G. MARAND vor 5 Jahren
Ursprung
Commit
545a8d9798

+ 22 - 2
api/doc.go → api/api.go

@@ -2,7 +2,7 @@
 The Kurz Web API exposes these routes:
 
   - GET "/<short>" : resolve a short URL
-    - Handler: HandleGetShort()
+    - Handler: handleGetShort()
     - Success: 307 to matching target URL
     - Client request incorrect: 400
     - Short not yet defined: 404 no matching target
@@ -10,7 +10,7 @@ The Kurz Web API exposes these routes:
     - Target legally censored: 451
     - Server failure: 50*
   - POST "/" with <target>: create a short URL from a target URL
-    - Handler: HandlePostTarget()
+    - Handler: handlePostTarget()
     - Success: 201 with <new short>
     - Already existing short: 409 with <existing short>
     - Target blocked for permission reasons: 403
@@ -23,6 +23,12 @@ mechanisms.
 */
 package api
 
+import (
+	"net/http"
+
+	"github.com/gorilla/mux"
+)
+
 // Short is the type of responses provided by the Web API from a target.
 type Short struct {
 	Short string `json:"short"`
@@ -32,3 +38,17 @@ type Short struct {
 type Target struct {
 	Target string `json:"target"`
 }
+
+func ListenAndServe(addr string) error {
+	router := mux.NewRouter()
+	router.HandleFunc("/{short}", handleGetShort).
+		Methods("GET", "HEAD").
+		Name("kurz.get_short")
+	router.HandleFunc("/", handlePostTarget).
+		HeadersRegexp("Content-Type", "^application/json$").
+		Methods("POST").
+		Name("kurd.post_target")
+	http.Handle("/", router)
+	err := http.ListenAndServe(addr, router)
+	return err
+}

+ 2 - 2
api/get.go

@@ -7,8 +7,8 @@ import (
 	"github.com/gorilla/mux"
 )
 
-// HandleGetShort() handles GET /<short>.
-func HandleGetShort(w http.ResponseWriter, r *http.Request) {
+// handleGetShort() handles GET /<short>.
+func handleGetShort(w http.ResponseWriter, r *http.Request) {
 	short, ok := mux.Vars(r)["short"]
 	if !ok {
 		w.WriteHeader(http.StatusBadRequest)

+ 1 - 1
api/get_test.go

@@ -12,7 +12,7 @@ import (
 
 func setupGet() (*httptest.Server, *http.Client) {
 	router := mux.NewRouter()
-	router.HandleFunc("/{short}", HandleGetShort)
+	router.HandleFunc("/{short}", handleGetShort)
 	ts := httptest.NewServer(router)
 
 	c := ts.Client()

+ 2 - 2
api/post.go

@@ -18,8 +18,8 @@ func jsonFromString(s string) []byte {
 	return j
 }
 
-// HandlePostTarget() handles "POST /" with { "target": "some target" }.
-func HandlePostTarget(w http.ResponseWriter, r *http.Request) {
+// handlePostTarget() handles "POST /" with { "target": "some target" }.
+func handlePostTarget(w http.ResponseWriter, r *http.Request) {
 	payload, err := ioutil.ReadAll(r.Body)
 	if err != nil {
 		w.WriteHeader(http.StatusBadRequest)

+ 1 - 1
api/post_test.go

@@ -31,7 +31,7 @@ func subTest(t *testing.T, seed bool, targetURL string, expectedStatus int, erro
 	}
 	domain.RegisterRepositories(domain.MockShortRepo{}, tr)
 
-	ts := httptest.NewServer(http.HandlerFunc(HandlePostTarget))
+	ts := httptest.NewServer(http.HandlerFunc(handlePostTarget))
 	defer ts.Close()
 
 	c := ts.Client()

+ 2 - 0
cmd/kurzd/dist.config.yml

@@ -3,3 +3,5 @@ database:
   driver: mysql
   dsn: <user>:<pass>@[<server>]/<database>
   test_dsn: <user>:<pass>@[<server>]/<database>
+api:
+  address: ":8000"

+ 16 - 0
cmd/kurzd/serve.go

@@ -0,0 +1,16 @@
+package main
+
+import (
+	"github.com/spf13/cobra"
+)
+
+var cmdServe = &cobra.Command{
+	Args:  cobra.NoArgs,
+	Long:  "Start HTTP Server",
+	Short: "Top-level command for HTTP Serving.",
+	Use:   "serve",
+}
+
+func init() {
+	cmd.AddCommand(cmdServe)
+}

+ 50 - 0
cmd/kurzd/serve_api.go

@@ -0,0 +1,50 @@
+package main
+
+import (
+	"database/sql"
+	"fmt"
+
+	"code.osinet.fr/fgm/kurz/api"
+	"code.osinet.fr/fgm/kurz/domain"
+	"code.osinet.fr/fgm/kurz/infrastructure"
+	_ "code.osinet.fr/fgm/kurz/migrations"
+	"github.com/spf13/cobra"
+	"github.com/spf13/viper"
+)
+
+var cmdServeAPI = &cobra.Command{
+	Args:  cobra.NoArgs,
+	Long:  "Serve Kurz as a JSON web API.",
+	Run:   serveAPIHandler,
+	Short: "Serve Kurz API",
+	Use:   "api",
+}
+
+func init() {
+	cmdServe.AddCommand(cmdServeAPI)
+}
+
+func registerInfrastructure() *sql.DB {
+	dbDriver, dbDsn := infrastructure.ParseDbCred()
+	db, err := infrastructure.DbDial(dbDriver, dbDsn)
+	if err != nil {
+		panic(err)
+	}
+
+	domain.RegisterRepositories(
+		infrastructure.MySQLShortURLRepository{DB: db},
+		infrastructure.MySQLTargetURLRepository{DB: db},
+	)
+
+	return db
+}
+
+// Set up infrastructure and listen on specified address.
+func serveAPIHandler(_ *cobra.Command, args []string) {
+	db := registerInfrastructure()
+	defer db.Close()
+
+	address := viper.Get("api.address").(string)
+	err := api.ListenAndServe(address)
+	fmt.Println(err)
+}

+ 1 - 1
infrastructure/infratructure.go

@@ -9,7 +9,7 @@ import (
 
 const exampleValidHTTPURL = "https://example.com"
 
-// DbDial is almost a proxy to sql.Open but guarantees that db will be nil if err is not nil.
+// DbDial is almost a proxy to sql.Open but guarantees that DB will be nil if err is not nil.
 func DbDial(dbDriver, dbDsn string) (*sql.DB, error) {
 	db, err := sql.Open(dbDriver, dbDsn)
 	if err != nil {

+ 8 - 6
infrastructure/mysql.go

@@ -1,21 +1,23 @@
 package infrastructure
 
 import (
-	"code.osinet.fr/fgm/kurz/domain"
 	"database/sql"
+
+	"code.osinet.fr/fgm/kurz/domain"
+	_ "github.com/go-sql-driver/mysql"
 )
 
 type MySQLShortURLRepository struct {
-	db *sql.DB
+	DB *sql.DB
 }
 
 type MySQLTargetURLRepository struct {
-	db *sql.DB
+	DB *sql.DB
 }
 
 func (sr MySQLShortURLRepository) GetTarget(su domain.ShortURL) (domain.TargetURL, error) {
 	var tu domain.TargetURL
-	row := sr.db.QueryRow(`
+	row := sr.DB.QueryRow(`
 SELECT map.url
 FROM map
 WHERE map.hash = ?
@@ -36,7 +38,7 @@ func (tr MySQLTargetURLRepository) GetShort(tu domain.TargetURL) (su domain.Shor
 	// TODO future versions may have multiple shorts for a target, and choose a
 	// specific short based on the domain and Kurz user. For now just ensure we
 	// don't get more than one.
-	row := tr.db.QueryRow(`
+	row := tr.DB.QueryRow(`
 SELECT map.hash
 FROM map
 LIMIT 1
@@ -50,7 +52,7 @@ LIMIT 1
 			// Creation failed.
 			return su, false, err
 		}
-		_, err = tr.db.Exec(`
+		_, err = tr.DB.Exec(`
 INSERT INTO map(hash, url, date1, date2, date3, refcount)
 VALUES (?, ?, CURRENT_TIMESTAMP(), CURRENT_TIMESTAMP(), CURRENT_TIMESTAMP(), 0)
 `, su.URL, tu.URL)

+ 0 - 2
infrastructure/mysql_test.go

@@ -9,8 +9,6 @@ import (
 	"code.osinet.fr/fgm/kurz/domain"
 	"github.com/pressly/goose"
 	"github.com/spf13/viper"
-
-	_ "github.com/go-sql-driver/mysql"
 )
 
 func TestMySQLEmptyRepo(test *testing.T) {