Skip to content

Commit

Permalink
conditional redirect to GET (close #40)
Browse files Browse the repository at this point in the history
If POST receives a small number of names, redirect
the request to GET.
  • Loading branch information
dimus committed Mar 20, 2021
1 parent 74e38b2 commit ae2d400
Show file tree
Hide file tree
Showing 6 changed files with 164 additions and 58 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Changelog

## [v0.2.3]

- Add [#40]: redirect to GET if names number is small.

## [v0.2.2]

- Add [#39]: add Wikispecies and Plazi to web-interface.
Expand Down Expand Up @@ -40,6 +44,7 @@

This document follows [changelog guidelines]

[v0.2.3]: https://github.com/gnames/gnverify/compare/v0.2.2...v0.2.3
[v0.2.2]: https://github.com/gnames/gnverify/compare/v0.2.1...v0.2.2
[v0.2.1]: https://github.com/gnames/gnverify/compare/v0.2.0...v0.2.1
[v0.2.0]: https://github.com/gnames/gnverify/compare/v0.1.0...v0.2.0
Expand Down
21 changes: 17 additions & 4 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,16 +29,21 @@ type Config struct {
// Batch is the size of the string slices fed into input channel for
// verification.
Batch int

// NamesNumThreshold the number of names after which POST gets redirected
// to GET.
NamesNumThreshold int
}

// New is a Config constructor that takes external options to
// update default values to external ones.
func New(opts ...Option) Config {
cnf := Config{
Format: gnfmt.CSV,
VerifierURL: "https://verifier.globalnames.org/api/v1/",
Batch: 5000,
Jobs: 4,
Format: gnfmt.CSV,
VerifierURL: "https://verifier.globalnames.org/api/v1/",
Batch: 5000,
Jobs: 4,
NamesNumThreshold: 20,
}
for _, opt := range opts {
opt(&cnf)
Expand Down Expand Up @@ -84,3 +89,11 @@ func OptVerifierURL(s string) Option {
cnf.VerifierURL = s
}
}

// OptNamesNumThreshold sets number of names after which there is a redirect
// from POST to GET.
func OptNamesNumThreshold(i int) Option {
return func(cnf *Config) {
cnf.NamesNumThreshold = i
}
}
155 changes: 103 additions & 52 deletions io/web/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"embed"
"fmt"
"net/http"
"net/url"
"strconv"
"strings"
"time"
Expand All @@ -17,7 +18,14 @@ import (
"github.com/labstack/echo/v4/middleware"
)

const withLogs = true
const withLogs = false

type formInput struct {
Names string `query:"names" form:"names"`
Format string `query:"format" form:"format"`
PreferredOnly string `query:"preferred_only" form:"preferred_only"`
DS []int `query:"ds" form:"ds"`
}

//go:embed static
var static embed.FS
Expand All @@ -38,7 +46,7 @@ func Run(gnv gnverify.GNVerify, port int) {
e.Logger.Fatal(err)
}

e.GET("/", homeGET())
e.GET("/", homeGET(gnv))
e.POST("/", homePOST(gnv))
e.GET("/data_sources", dataSources(gnv))
e.GET("/data_sources/:id", dataSource(gnv))
Expand Down Expand Up @@ -110,74 +118,48 @@ func dataSource(gnv gnverify.GNVerify) func(echo.Context) error {
}
}

func homeGET() func(echo.Context) error {
func homeGET(gnv gnverify.GNVerify) func(echo.Context) error {
return func(c echo.Context) error {
data := Data{Page: "home"}
return c.Render(http.StatusOK, "layout", data)
data := Data{Page: "home", Format: "html"}

inp := new(formInput)
err := c.Bind(inp)
if err != nil {
return err
}

if strings.TrimSpace(inp.Names) == "" {
return c.Render(http.StatusOK, "layout", data)
}

return verificationResults(c, gnv, inp, data)
}
}

func homePOST(gnv gnverify.GNVerify) func(echo.Context) error {
type input struct {
Names string `form:"names"`
Format string `form:"format"`
PreferredOnly string `form:"preferred_only"`
DS []int `form:"ds"`
}

return func(c echo.Context) error {
inp := new(input)
inp := new(formInput)
data := Data{Page: "home", Format: "html"}
var names []string

err := c.Bind(inp)
if err != nil {
return err
}

prefOnly := inp.PreferredOnly == "on"

data.Input = inp.Names
data.Preferred = inp.DS
format := inp.Format
if format == "csv" || format == "json" {
data.Format = format
if strings.TrimSpace(inp.Names) == "" {
return c.Redirect(http.StatusFound, "")
}

if data.Input != "" {
split := strings.Split(data.Input, "\n")
if len(split) > 5_000 {
split = split[0:5_000]
}
names = make([]string, len(split))
for i := range split {
names[i] = strings.TrimSpace(split[i])
}

opts := []config.Option{config.OptPreferredSources(data.Preferred)}
gnv.ChangeConfig(opts...)

data.Verified = gnv.VerifyBatch(names)
if prefOnly {
for i := range data.Verified {
data.Verified[i].BestResult = nil
}
}
split := strings.Split(inp.Names, "\n")
if len(split) > 5_000 {
split = split[0:5_000]
}

switch data.Format {
case "json":
return c.JSON(http.StatusOK, data.Verified)
case "csv":
res := make([]string, len(data.Verified)+1)
res[0] = output.CSVHeader()
for i, v := range data.Verified {
res[i+1] = output.Output(v, gnfmt.CSV, prefOnly)
}
return c.String(http.StatusOK, strings.Join(res, "\n"))
default:
return c.Render(http.StatusOK, "layout", data)
if len(split) < gnv.Config().NamesNumThreshold {
return redirectToHomeGET(c, inp)
}

return verificationResults(c, gnv, inp, data)
}
}

Expand All @@ -196,3 +178,72 @@ func getPreferredSources(ds []string) []int {
}
return res
}

func redirectToHomeGET(c echo.Context, inp *formInput) error {
prefOnly := inp.PreferredOnly == "on"
q := make(url.Values)
q.Set("names", inp.Names)
q.Set("format", inp.Format)
if prefOnly {
q.Set("preferred_only", inp.PreferredOnly)
}
for i := range inp.DS {
q.Add("ds", strconv.Itoa(inp.DS[i]))
}
url := fmt.Sprintf("/?%s", q.Encode())
return c.Redirect(http.StatusFound, url)
}

func verificationResults(
c echo.Context,
gnv gnverify.GNVerify,
inp *formInput,
data Data,
) error {
var names []string
prefOnly := inp.PreferredOnly == "on"

data.Input = inp.Names
data.Preferred = inp.DS
format := inp.Format
if format == "csv" || format == "json" {
data.Format = format
}

if data.Input != "" {
split := strings.Split(data.Input, "\n")
if len(split) > 5_000 {
split = split[0:5_000]
}

names = make([]string, len(split))
for i := range split {
names[i] = strings.TrimSpace(split[i])
}

opts := []config.Option{config.OptPreferredSources(data.Preferred)}
gnv.ChangeConfig(opts...)

data.Verified = gnv.VerifyBatch(names)
if prefOnly {
for i := range data.Verified {
data.Verified[i].BestResult = nil
}
}
}
inp = new(formInput)

switch data.Format {
case "json":
return c.JSON(http.StatusOK, data.Verified)
case "csv":
res := make([]string, len(data.Verified)+1)
res[0] = output.CSVHeader()
for i, v := range data.Verified {
res[i+1] = output.Output(v, gnfmt.CSV, prefOnly)
}
return c.String(http.StatusOK, strings.Join(res, "\n"))
default:
return c.Render(http.StatusOK, "layout", data)
}
}
40 changes: 38 additions & 2 deletions io/web/server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,13 @@ func TestAPI(t *testing.T) {
func TestHomeGET(t *testing.T) {
c, rec := handlerGET("/", t)

assert.Nil(t, homeGET()(c))
verifs := verifications(t)
cfg := config.New()
vfr := new(vtest.FakeVerifier)
vfr.VerifyReturns(verifs)
gnv := gnverify.New(cfg, vfr)

assert.Nil(t, homeGET(gnv)(c))
assert.Equal(t, rec.Code, http.StatusOK)
assert.Contains(t, rec.Body.String(), "Global Names Verifier")
assert.Contains(t, rec.Body.String(), "Advanced Options")
Expand All @@ -74,14 +80,44 @@ func TestHomePOST(t *testing.T) {
assert.Nil(t, err)
c := e.NewContext(req, rec)

cfg := config.New()
cfg := config.New(config.OptNamesNumThreshold(2))
vfr := new(vtest.FakeVerifier)
vfr.VerifyReturns(verifs)
gnv := gnverify.New(cfg, vfr)
assert.Nil(t, homePOST(gnv)(c))
assert.Equal(t, rec.Code, http.StatusOK)
assert.Contains(t, rec.Body.String(), "Bubo (genus)")
}

func TestHomePostGet(t *testing.T) {
var err error
verifs := verifications(t)
f := make(url.Values)
f.Set("names", "Bubo bubo\nPomatomus saltator\nNotName")
f.Set("format", "html")

req := httptest.NewRequest(
http.MethodPost,
"/",
strings.NewReader(f.Encode()),
)
rec := httptest.NewRecorder()
req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationForm)
e := echo.New()
e.Renderer, err = NewTemplate()
assert.Nil(t, err)
c := e.NewContext(req, rec)

cfg := config.New(config.OptNamesNumThreshold(20))
vfr := new(vtest.FakeVerifier)
vfr.VerifyReturns(verifs)
gnv := gnverify.New(cfg, vfr)
assert.Nil(t, homePOST(gnv)(c))
// redirect to GET
assert.Equal(t, rec.Code, http.StatusFound)
assert.NotContains(t, rec.Body.String(), "Bubo (genus)")
}

func verifications(t *testing.T) []vlib.Verification {
c := cassette.New("dss")
data, err := os.ReadFile("../verifrest/fixtures/names.yaml")
Expand Down
Binary file added io/web/static/images/favicon.ico
Binary file not shown.
1 change: 1 addition & 0 deletions io/web/templates/layout.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
<head>
<title>Global Names Verifier</title>
<link href="/static/styles/screen.css" media="screen" rel="stylesheet" type="text/css" />
<link href='/static/images/favicon.ico' rel='icon' type='image/x-icon'>
<script src="/static/js/jquery-3.6.0.min.js"></script>
{{ if eq .Page "home" }}
<script src="/static/js/home.js"></script>
Expand Down

0 comments on commit ae2d400

Please sign in to comment.