|
@@ -2,61 +2,139 @@ package web
|
|
|
|
|
|
|
|
import (
|
|
import (
|
|
|
"bytes"
|
|
"bytes"
|
|
|
|
|
+ "encoding/base64"
|
|
|
"io"
|
|
"io"
|
|
|
"mime/multipart"
|
|
"mime/multipart"
|
|
|
"net/http"
|
|
"net/http"
|
|
|
|
|
|
|
|
p "github.com/ajswis/go-pkparse-server/pokemon"
|
|
p "github.com/ajswis/go-pkparse-server/pokemon"
|
|
|
- "github.com/ajswis/go-pkparse-server/pokemon-parsing"
|
|
|
|
|
|
|
+ pokemonparsing "github.com/ajswis/go-pkparse-server/pokemon-parsing"
|
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/gin-gonic/gin"
|
|
|
)
|
|
)
|
|
|
|
|
|
|
|
/*
|
|
/*
|
|
|
|
|
+ File uploads:
|
|
|
curl -X POST http://localhost:8080/parse \
|
|
curl -X POST http://localhost:8080/parse \
|
|
|
- -F "pkmn=@/path/to/test/file1" \
|
|
|
|
|
- -F "pkmn=@/path/to/test/file2" \
|
|
|
|
|
- -H "Content-Type: multipart/form-data"
|
|
|
|
|
|
|
+ -H "Content-Type: multipart/form-data" \
|
|
|
|
|
+ --form "pokemon=@/path/to/test/file1" \
|
|
|
|
|
+ --form "pokemon=@/path/to/test/file2"
|
|
|
|
|
+
|
|
|
|
|
+ JSON POST:
|
|
|
|
|
+ curl -X POST http://localhost:8080/parse \
|
|
|
|
|
+ -H "Content-Type: application/json" \
|
|
|
|
|
+ -d '{"pokemon": ["base64_encoded_pokemon_file", "another_base64_encoded_pokemon_file"]}'
|
|
|
|
|
+
|
|
|
|
|
+ Form POST:
|
|
|
|
|
+ curl -X POST http://localhost:8080/parse \
|
|
|
|
|
+ -H "Content-Type: multipart/form-data" \
|
|
|
|
|
+ --form 'pokemon=base64_encoded_pokemon_file' \
|
|
|
|
|
+ --form 'pokemon=another_base64_encoded_pokemon_file'
|
|
|
|
|
+
|
|
|
|
|
+ Form POST + File upload:
|
|
|
|
|
+ curl -X POST http://localhost:8080/parse \
|
|
|
|
|
+ -H "Content-Type: multipart/form-data" \
|
|
|
|
|
+ --form "pokemon=@/path/to/test/file1" \
|
|
|
|
|
+ --form "pokemon=@/path/to/test/file2" \
|
|
|
|
|
+ --form 'pokemon=base64_encoded_pokemon_file' \
|
|
|
*/
|
|
*/
|
|
|
|
|
|
|
|
|
|
+type pokemonParseRequest struct {
|
|
|
|
|
+ Pokemon []string `json:"pokemon" form:"pokemon" binding:"required"`
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
// parse grabs multipart file uploads under the key `pkmn`, aggregates them, and
|
|
// parse grabs multipart file uploads under the key `pkmn`, aggregates them, and
|
|
|
// delegates work to the parser.
|
|
// delegates work to the parser.
|
|
|
-// NOTE: This can probably be improved by 1) parallelizing upload reads, or 2)
|
|
|
|
|
-// not accepting file uploads, preferring to service binary data as encoded
|
|
|
|
|
-// strings
|
|
|
|
|
func (s *Server) parse(c *gin.Context) {
|
|
func (s *Server) parse(c *gin.Context) {
|
|
|
- form, err := c.MultipartForm()
|
|
|
|
|
|
|
+ var rawPokemon []p.RawPokemon
|
|
|
|
|
+
|
|
|
|
|
+ // If the POST is `multipart/form-data`, check for file uploads and read them
|
|
|
|
|
+ if c.ContentType() == "multipart/form-data" {
|
|
|
|
|
+ fileHeaders, err := uploadedPokemonFiles(c)
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ render(c, http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if len(fileHeaders) > 0 {
|
|
|
|
|
+ err = pokemonFromFileUpload(fileHeaders, &rawPokemon)
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ render(c, http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // For non-file-upload form submissions or all other `Content-Type`s
|
|
|
|
|
+ err := pokemonFromEncodedUpload(c, &rawPokemon)
|
|
|
if err != nil {
|
|
if err != nil {
|
|
|
render(c, http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
render(c, http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
|
return
|
|
return
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- var rawPokemon []p.RawPokemon
|
|
|
|
|
- var files []*multipart.FileHeader = form.File["pkmn[]"]
|
|
|
|
|
|
|
+ pkmn, err := pokemonparsing.ParseAll(rawPokemon)
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ render(c, http.StatusUnprocessableEntity, gin.H{"error": err.Error()})
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ render(c, http.StatusOK, pkmn)
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// uploadedPokemonFiles extracts any existing FileHeaders from file uploads
|
|
|
|
|
+func uploadedPokemonFiles(c *gin.Context) ([]*multipart.FileHeader, error) {
|
|
|
|
|
+ form, err := c.MultipartForm()
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ return nil, err
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return form.File["pokemon"], nil
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// pokemonFromFileUpload reads data from uploaded files and returns the raw
|
|
|
|
|
+// pokemon
|
|
|
|
|
+func pokemonFromFileUpload(files []*multipart.FileHeader, rawPokemon *[]p.RawPokemon) error {
|
|
|
for _, file := range files {
|
|
for _, file := range files {
|
|
|
var err error
|
|
var err error
|
|
|
var src multipart.File
|
|
var src multipart.File
|
|
|
|
|
|
|
|
src, err = file.Open()
|
|
src, err = file.Open()
|
|
|
if err != nil {
|
|
if err != nil {
|
|
|
- render(c, http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
|
|
|
- return
|
|
|
|
|
|
|
+ return err
|
|
|
}
|
|
}
|
|
|
defer src.Close()
|
|
defer src.Close()
|
|
|
|
|
|
|
|
buf := bytes.NewBuffer(nil)
|
|
buf := bytes.NewBuffer(nil)
|
|
|
if _, err = io.Copy(buf, src); err != nil {
|
|
if _, err = io.Copy(buf, src); err != nil {
|
|
|
- render(c, http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
|
|
|
- return
|
|
|
|
|
|
|
+ return err
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- rawPokemon = append(rawPokemon, buf.Bytes())
|
|
|
|
|
|
|
+ *rawPokemon = append(*rawPokemon, buf.Bytes())
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- pkmn, err := pokemonparsing.ParseAll(rawPokemon)
|
|
|
|
|
- if err != nil {
|
|
|
|
|
- render(c, http.StatusUnprocessableEntity, gin.H{"error": err.Error()})
|
|
|
|
|
- return
|
|
|
|
|
|
|
+ return nil
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// pokemonFromEncodedUpload binds the POST data to a `pokemonParseRequest` and
|
|
|
|
|
+// converts each base64 string to a `[]byte` slice.
|
|
|
|
|
+func pokemonFromEncodedUpload(c *gin.Context, rawPokemon *[]p.RawPokemon) error {
|
|
|
|
|
+ var request pokemonParseRequest
|
|
|
|
|
+
|
|
|
|
|
+ if err := c.ShouldBind(&request); err != nil {
|
|
|
|
|
+ return err
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- render(c, http.StatusOK, pkmn)
|
|
|
|
|
|
|
+ for _, pkmn := range request.Pokemon {
|
|
|
|
|
+ var err error
|
|
|
|
|
+ var decoded []byte
|
|
|
|
|
+
|
|
|
|
|
+ decoded, err = base64.StdEncoding.DecodeString(pkmn)
|
|
|
|
|
+
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ return err
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ *rawPokemon = append(*rawPokemon, decoded)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return nil
|
|
|
}
|
|
}
|