Skip to content

Commit

Permalink
feat(h2non#32): initial seed implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
h2non committed Nov 3, 2015
1 parent 46b78f6 commit 818c0c5
Show file tree
Hide file tree
Showing 9 changed files with 215 additions and 65 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ ui/static/application.min.js
ui/dist/

*.swp
/imaginary
bin/imaginary

## ignore vendor packages
vendor/
7 changes: 7 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
OK_COLOR=\033[32;01m
NO_COLOR=\033[0m

build:
@echo "$(OK_COLOR)==> Compiling binary$(NO_COLOR)"
go build -o bin/imaginary

test:
go test

Expand Down
8 changes: 6 additions & 2 deletions controllers.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,9 @@ package main

import (
"fmt"
"gopkg.in/h2non/bimg.v0"
"net/http"
"time"

"gopkg.in/h2non/bimg.v0"
)

func indexController(w http.ResponseWriter, r *http.Request) {
Expand All @@ -22,6 +21,11 @@ func formController(w http.ResponseWriter, r *http.Request) {
w.Write([]byte(htmlForm()))
}

func healthController(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.Write([]byte("{}"))
}

func imageControllerDispatcher(o ServerOptions, operation Operation) func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
var buf []byte
Expand Down
16 changes: 16 additions & 0 deletions fixtures/server.crt
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
-----BEGIN CERTIFICATE-----
MIICjzCCAfgCCQCYQsNXSKvPPTANBgkqhkiG9w0BAQUFADCBizELMAkGA1UEBhMC
SUUxDzANBgNVBAgTBkR1YmxpbjEPMA0GA1UEBxMGRHVibGluMSEwHwYDVQQKExhJ
bnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQxFTATBgNVBAMTDGltYWdpbmFyeS5pbzEg
MB4GCSqGSIb3DQEJARYRdG9tYXNAYXBhcmljaW8ubWUwHhcNMTUwNzExMTk0MTM2
WhcNMjcwNjIzMTk0MTM2WjCBizELMAkGA1UEBhMCSUUxDzANBgNVBAgTBkR1Ymxp
bjEPMA0GA1UEBxMGRHVibGluMSEwHwYDVQQKExhJbnRlcm5ldCBXaWRnaXRzIFB0
eSBMdGQxFTATBgNVBAMTDGltYWdpbmFyeS5pbzEgMB4GCSqGSIb3DQEJARYRdG9t
YXNAYXBhcmljaW8ubWUwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAKv846TF
luaJi9lSpQxk3lwfU32gCpaZHysGmAtEkYzLCWKXV212AcKzW6v07lKD7w7mJ7Fr
RTMNT++tcBDNL4RAVdyLhYhHIabmWOh85cPaWwM+6tE9JxlQKQi6qYE2P7sE4D9f
EjIGi7wnBOsXNHCWpQExmkY1g3GYiCsBa3QTAgMBAAEwDQYJKoZIhvcNAQEFBQAD
gYEAHxWFoEQh4/bzKc9ByGLdPubfRkck7mnA37leJO/ilooS7ZL22BW/yjzlP3dM
LzCMFmBBNqHwPQMfnFWqoIAaHFa6FPgCZExZZ+xYfRxfatnVI2t11lQXZOUe8Dxf
pQcjzecXFSMlhSXNQPYyZZzUjCUOgZDs0HSTvvRAStQjENU=
-----END CERTIFICATE-----
15 changes: 15 additions & 0 deletions fixtures/server.key
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
-----BEGIN RSA PRIVATE KEY-----
MIICXAIBAAKBgQCr/OOkxZbmiYvZUqUMZN5cH1N9oAqWmR8rBpgLRJGMywlil1dt
dgHCs1ur9O5Sg+8O5iexa0UzDU/vrXAQzS+EQFXci4WIRyGm5ljofOXD2lsDPurR
PScZUCkIuqmBNj+7BOA/XxIyBou8JwTrFzRwlqUBMZpGNYNxmIgrAWt0EwIDAQAB
AoGAXmSYiDmN3Y+GOst6HHhL9iGXUC6DQS5fBd1Dm4ORosVYrEzFxiTrSHHqEVGH
b7BLh1DYXi6ytxdKVRBKnl4PAk9NUsdWdSFSvOv6wUM/uLMpKLYEdPRHcaHNSIca
sz5ryRwA9zogS76Ke20tgPC6IklTVaEkbvfxG+ob7Vp53rECQQDacScoyDVsadlS
8oQqogO+XuTwgyk4UDd7K6KmMq0etS00SpPtlp1XvP90kqa+1OCAwjP8aUW8Bj+Q
hbNa6yD5AkEAyY8H12He86EXFatKyrIRaQ71aMtgTOQ4cnH0l8EYKuDpKLA8mUdT
skzu4EKxyt/6pg4yGwGzK+ruGH6J96EMawJAB82E7ZMBPYcmaS0ahX9WDOXM3b6B
qW5MHQ04+SDUSEWGgNitIg6APlMU+PAIHsbx4geN3dVQ1V+Pw7TS7Et72QJAHepT
sJz/GUvcgEPXKvR47w3gULh2x5LL6fiN5AQt0Rdmo7pclCdo/bq7bZ+YgdLygbjz
qNx8ulT5F7uYQJ+vlwJBANb7kNO0bFFErtIFRuGpwJvzglUeWNz+Y2JQ/IeGm+wy
lxIvpYYbJArqZjUochqaqoNBkBXYAOToTuYSLH/rAYE=
-----END RSA PRIVATE KEY-----
98 changes: 52 additions & 46 deletions imaginary.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,62 +3,66 @@ package main
import (
"flag"
"fmt"
. "github.com/tj/go-debug"
"os"
"runtime"
d "runtime/debug"
"strconv"
"time"

. "github.com/tj/go-debug"
)

var debug = Debug("imaginary")

var (
aAddr = flag.String("a", "", "bind address")
aPort = flag.Int("p", 8088, "port to listen")
aVers = flag.Bool("v", false, "")
aVersl = flag.Bool("version", false, "")
aHelp = flag.Bool("h", false, "")
aHelpl = flag.Bool("help", false, "")
aCors = flag.Bool("cors", false, "")
aGzip = flag.Bool("gzip", false, "")
aKey = flag.String("key", "", "")
aMount = flag.String("mount", "", "")
aCertFile = flag.String("certfile", "", "")
aKeyFile = flag.String("keyfile", "", "")
aHttpCacheTtl = flag.Int("http-cache-ttl", -1, "The TTL in seconds")
aConcurrency = flag.Int("concurrency", 0, "")
aBurst = flag.Int("burst", 100, "")
aMRelease = flag.Int("mrelease", 30, "")
aCpus = flag.Int("cpus", runtime.GOMAXPROCS(-1), "")
aAddr = flag.String("a", "", "bind address")
aPort = flag.Int("p", 8088, "port to listen")
aVers = flag.Bool("v", false, "")
aVersl = flag.Bool("version", false, "")
aHelp = flag.Bool("h", false, "")
aHelpl = flag.Bool("help", false, "")
aCors = flag.Bool("cors", false, "")
aGzip = flag.Bool("gzip", false, "")
aKey = flag.String("key", "", "")
aMount = flag.String("mount", "", "")
aCertFile = flag.String("certfile", "", "")
aKeyFile = flag.String("keyfile", "", "")
aHttpCacheTtl = flag.Int("http-cache-ttl", -1, "The TTL in seconds")
aReadTimeout = flag.Int("http-read-timeout", 30, "HTTP read timeout in seconds")
aWriteTimeout = flag.Int("http-write-timeout", 30, "HTTP write timeout in seconds")
aConcurrency = flag.Int("concurrency", 0, "")
aBurst = flag.Int("burst", 100, "")
aMRelease = flag.Int("mrelease", 30, "")
aCpus = flag.Int("cpus", runtime.GOMAXPROCS(-1), "")
)

const usage = `imaginary server %s
Usage:
imaginary -p 80
imaginary -cors -gzip
imaginary -concurrency 10
imaginary -h | -help
imaginary -v | -version
Options:
-a <addr> bind address [default: *]
-p <port> bind port [default: 8088]
-h, -help output help
-v, -version output version
-cors Enable CORS support [default: false]
-gzip Enable gzip compression [default: false]
-key <key> Define API key for authorization
-mount <path> Mount server directory
-http-cache-ttl <num> The TTL in seconds. Adds caching headers to locally served files.
-certfile <path> TLS certificate file path
-keyfile <path> TLS key file path
-concurreny <num> Throttle concurrency limit per second [default: disabled]
-burst <num> Throttle burst max cache size [default: 100]
-mrelease <num> Force OS memory release inverval in seconds [default: 30]
-cpus <num> Number of used cpu cores.
(default for current machine is %d cores)
-a <addr> bind address [default: *]
-p <port> bind port [default: 8088]
-h, -help output help
-v, -version output version
-cors Enable CORS support [default: false]
-gzip Enable gzip compression [default: false]
-key <key> Define API key for authorization
-mount <path> Mount server directory
-http-cache-ttl <num> The TTL in seconds. Adds caching headers to locally served files.
-http-read-timeout <num> HTTP read timeout in seconds [default: 30]
-http-write-timeout <num> HTTP read timeout in seconds [default: 30]
-certfile <path> TLS certificate file path
-keyfile <path> TLS key file path
-concurreny <num> Throttle concurrency limit per second [default: disabled]
-burst <num> Throttle burst max cache size [default: 100]
-mrelease <num> Force OS memory release inverval in seconds [default: 30]
-cpus <num> Number of used cpu cores.
(default for current machine is %d cores)
`

func main() {
Expand All @@ -79,17 +83,19 @@ func main() {

port := getPort(*aPort)
opts := ServerOptions{
Port: port,
Address: *aAddr,
Gzip: *aGzip,
CORS: *aCors,
ApiKey: *aKey,
Concurrency: *aConcurrency,
Burst: *aBurst,
Mount: *aMount,
CertFile: *aCertFile,
KeyFile: *aKeyFile,
HttpCacheTtl: *aHttpCacheTtl,
Port: port,
Address: *aAddr,
Gzip: *aGzip,
CORS: *aCors,
ApiKey: *aKey,
Concurrency: *aConcurrency,
Burst: *aBurst,
Mount: *aMount,
CertFile: *aCertFile,
KeyFile: *aKeyFile,
HttpCacheTtl: *aHttpCacheTtl,
HttpReadTimeout: *aReadTimeout,
HttpWriteTimeout: *aWriteTimeout,
}

// Create a memory release goroutine
Expand Down
34 changes: 18 additions & 16 deletions server.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,29 +8,31 @@ import (
)

type ServerOptions struct {
Port int
CORS bool
Gzip bool
Address string
ApiKey string
Mount string
CertFile string
KeyFile string
Burst int
Concurrency int
HttpCacheTtl int
Port int
CORS bool
Gzip bool
Address string
ApiKey string
Mount string
CertFile string
KeyFile string
Burst int
Concurrency int
HttpCacheTtl int
HttpReadTimeout int
HttpWriteTimeout int
}

func Server(o ServerOptions) error {
addr := o.Address + ":" + strconv.Itoa(o.Port)
handler := NewLog(NewServerMux(o), os.Stdout)

server := &http.Server{
Addr: addr,
Handler: handler,
ReadTimeout: 60 * time.Second,
WriteTimeout: 60 * time.Second,
MaxHeaderBytes: 1 << 20,
Addr: addr,
Handler: handler,
ReadTimeout: time.Duration(o.HttpReadTimeout) * time.Second,
WriteTimeout: time.Duration(o.HttpWriteTimeout) * time.Second,
MaxHeaderBytes: 1 << 20,
}

return listenAndServe(server, o)
Expand Down
36 changes: 36 additions & 0 deletions source.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package main

import (
"fmt"
"net/http"
"os"
)

type ImageSourceType string
type ImageSourceFactoryFunction func(*SourceConfig) ImageSource

type SourceConfig struct {
Type ImageSourceType
Directory string
}

var (
imageSourceTypeToFactoryFunctionMap = make(map[ImageSourceType]ImageSourceFactoryFunction)
)

type ImageSource interface {
GetImage(*http.Request) ([]byte, error)
}

func RegisterSource(sourceType ImageSourceType, factory ImageSourceFactoryFunction) {
imageSourceTypeToFactoryFunctionMap[sourceType] = factory
}

func NewImageSourceWithConfig(config *SourceConfig) ImageSource {
factory := imageSourceTypeToFactoryFunctionMap[config.Type]
if factory == nil {
fmt.Fprintf(os.Stderr, "Unknown image source type: %s\n", config.Type)
os.Exit(1)
}
return factory(config)
}
64 changes: 64 additions & 0 deletions source_http.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package main

import (
"fmt"
"io"
"io/ioutil"
"net/http"
"net/url"
)

const (
ImageSourceTypeHttp ImageSourceType = "http"
)

type HttpImageSource struct {
Config *SourceConfig
}

func NewHttpImageSourceWithConfig(config *SourceConfig) ImageSource {
return &HttpImageSource{config}
}

func GetImageFromReader(buffer io.Reader) ([]byte, error) {
return ioutil.ReadAll(buffer)
}

func (s *HttpImageSource) GetImage(request *http.Request) ([]byte, error) {
URL, err := s.parseURL(request)
if err != nil {
return nil, err
}

httpRequest := s.newHttpRequest(URL)
httpResponse, err := http.DefaultClient.Do(httpRequest)
defer httpResponse.Body.Close()
if err != nil {
return nil, fmt.Errorf("Error downloading image: %v", err)
}
if httpResponse.StatusCode != 200 {
return nil, fmt.Errorf("Error downloading image (url=%s)", httpRequest.URL.RequestURI())
}

body, err := GetImageFromReader(httpResponse.Body)
if err != nil {
return nil, fmt.Errorf("Unable to create image from response body: %v (url=%s)", string(body), httpRequest.URL.RequestURI())
}

return body, nil
}

func (s *HttpImageSource) parseURL(request *http.Request) (*url.URL, error) {
queryUrl := request.URL.Query().Get("url")
return url.Parse(queryUrl)
}

func (s *HttpImageSource) newHttpRequest(url *url.URL) *http.Request {
httpRequest, _ := http.NewRequest("GET", url.RequestURI(), nil)
httpRequest.URL = url
return httpRequest
}

func init() {
RegisterSource(ImageSourceTypeHttp, NewHttpImageSourceWithConfig)
}

0 comments on commit 818c0c5

Please sign in to comment.