From 122a65a0fc880a8ca6d647d22a66c2e62da89329 Mon Sep 17 00:00:00 2001 From: Ashley Primo Date: Mon, 17 Jan 2022 01:54:30 +0000 Subject: [PATCH] First Commit --- README.md | 32 ++++- documentation/documentation.go | 57 +++++++++ go.mod | 13 +- go.sum | 133 ++++++++++++++++++++ initialize/flags.go | 45 +++++++ initialize/global.go | 16 +++ main.go | 217 ++++----------------------------- metrics.go | 22 ++++ qr/engine.go | 152 +++++++++++++++++++++++ qr/metrics.go | 27 ++++ 10 files changed, 520 insertions(+), 194 deletions(-) create mode 100644 documentation/documentation.go create mode 100644 initialize/flags.go create mode 100644 initialize/global.go create mode 100644 metrics.go create mode 100644 qr/engine.go create mode 100644 qr/metrics.go diff --git a/README.md b/README.md index 48577f9..19d3b75 100644 --- a/README.md +++ b/README.md @@ -10,11 +10,13 @@ **Public Endpoint**: https://qr-api.ashleyprimo.com/docs ``` -Usage of go-qr-generator: +Usage of ./go-qr-generator: -address string The address to listen on for HTTP requests. (default "0.0.0.0") - -defualtQRSize string - Default QR Image Size, if unspecified by end user. (default "250") + -enable.docs + Enable documentation (/docs) endpoint. (default true) + -enable.metrics + Enable metrics (/metrics) endpoint. (default true) -https Enable, or Disable HTTPS -log.conn @@ -23,16 +25,40 @@ Usage of go-qr-generator: The level of logs to log (default "info") -port string The port number to listen on for HTTP requests. (default "8080") + -qr.default.size int + Default QR Image Size, if unspecified by end user. (default 250) + -qr.endpoint string + QR API endpoint location (default "/") + -qr.max.size int + Maximum QR Image Size (default 1000) -server_crt string Certificate file (default "server.crt") -server_key string Certificate key file. (default "server.key") + -v Outputs package version ``` ## Endpoints * `/` -> API endpoint. * `/docs` -> Simple API documentation * `/health` -> Simple Health Check +* `/metrics` -> [Prometheus](https://prometheus.io/) Metrics Endpoint + +## Metrics +Current available metrics +``` +# HELP qr_generator_requests Completed requests to QR API endpoint. +# TYPE qr_generator_requests counter +qr_generator_requests{type="2xx"} 0 +qr_generator_requests{type="4xx"} 0 +qr_generator_requests{type="5xx"} 0 +# HELP qr_generator_requests_total Total number of requests to QR API endpoint. +# TYPE qr_generator_requests_total counter +qr_generator_requests_total 0 +# HELP qr_generator_version current running version +# TYPE qr_generator_version gauge +qr_generator_version{version="1.1.0"} 0 +``` ## Docker You can get started with docker quickly, by using the `docker pull ashleyprimo/go-qr-generator:latest` or alternatively you can build your own image using `docker build ./ -t go-qr-generator:latest` diff --git a/documentation/documentation.go b/documentation/documentation.go new file mode 100644 index 0000000..23f4baf --- /dev/null +++ b/documentation/documentation.go @@ -0,0 +1,57 @@ +package docs + +import ( + "fmt" + "strconv" + "net/http" + + "github.com/ashleyprimo/go-qr-generator/initialize" +) + +func Landing(w http.ResponseWriter, r *http.Request) { + const ( + documentationPage = ` + + + %[1]s API documentation + + + + + + + +

%[1]s API documentation

+

How to make a request

+

It's very simple, there are two possible parameters currently, size (which is optional) and data (which is mandatory)!

+

Please Note: The requested size must be no more than %[4]sx%[4]s pixels

+

Example Request: http://%[2]s%[3]s?size=%[5]s&data=This%%20is%%20an%%20example

+

This above example, will generate a QR code with the text "This is an example", with a size of %[5]sx%[5]s pixels- it's that easy.

+ + + + ` + ) + w.Write([]byte(fmt.Sprintf(documentationPage, initialize.ApplicationName, r.Host, *initialize.QREndpoint, strconv.Itoa(*initialize.MaxSize), strconv.Itoa(*initialize.DefaultSize)))) +} + diff --git a/go.mod b/go.mod index 11698f9..489201a 100644 --- a/go.mod +++ b/go.mod @@ -7,4 +7,15 @@ require ( github.com/sirupsen/logrus v1.8.1 ) -require golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 // indirect +require ( + github.com/beorn7/perks v1.0.1 // indirect + github.com/cespare/xxhash/v2 v2.1.1 // indirect + github.com/golang/protobuf v1.4.3 // indirect + github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect + github.com/prometheus/client_golang v1.11.0 // indirect + github.com/prometheus/client_model v0.2.0 // indirect + github.com/prometheus/common v0.26.0 // indirect + github.com/prometheus/procfs v0.6.0 // indirect + golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40 // indirect + google.golang.org/protobuf v1.26.0-rc.1 // indirect +) diff --git a/go.sum b/go.sum index 1f6263d..7adc7a9 100644 --- a/go.sum +++ b/go.sum @@ -1,12 +1,145 @@ +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/boombuler/barcode v1.0.1 h1:NDBbPmhS+EqABEs5Kg3n/5ZNjy73Pz7SIV+KCeqyXcs= github.com/boombuler/barcode v1.0.1/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= +github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= +github.com/prometheus/client_golang v1.11.0 h1:HNkLOAEQMIDv/K+04rukrLx6ch7msSRwf3/SASFAGtQ= +github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M= +github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= +github.com/prometheus/common v0.26.0 h1:iMAkS2TDoNWnKM+Kopnx/8tnEStIfpYA0ur0xQzzhMQ= +github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/prometheus/procfs v0.6.0 h1:mxy4L2jP6qMonqmq+aTtOx1ifVWUgG/TAmntgbh3xv4= +github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 h1:YyJpGZS1sBuBCzLAR1VEpK193GlqGZbnPFnPV/5Rsb4= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40 h1:JWgyZ1qgdTaF3N3oxC+MdTV7qvEEgHo3otj+HB5CM7Q= +golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.26.0-rc.1 h1:7QnIQpGRHE5RnLKnESfDoxm2dTapTZua5a0kS0A+VXQ= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/initialize/flags.go b/initialize/flags.go new file mode 100644 index 0000000..36d5078 --- /dev/null +++ b/initialize/flags.go @@ -0,0 +1,45 @@ +package initialize + +import ( + "flag" + + log "github.com/sirupsen/logrus" +) + +var ( + // Webserver Flags + PortNumber = flag.String("port", "8080", "The port number to listen on for HTTP requests.") + Address = flag.String("address", "0.0.0.0", "The address to listen on for HTTP requests.") + + Https = flag.Bool("https", false, "Enable, or Disable HTTPS") + Server_crt = flag.String("server_crt", "server.crt", "Certificate file") + Server_key = flag.String("server_key", "server.key", "Certificate key file.") + + // QR Setup Flags + QREndpoint = flag.String("qr.endpoint", "/", "QR API endpoint location") + + DefaultSize = flag.Int("qr.default.size", 250, "Default QR Image Size, if unspecified by end user.") + MaxSize = flag.Int("qr.max.size", 1000, "Maximum QR Image Size") + + // Logging Flags + LogLevel = flag.String("log.level", "info", "The level of logs to log") + LogConn = flag.Bool("log.conn", true, "Log connections to API") + + // Documentation Flags + EnableDocs = flag.Bool("enable.docs", true, "Enable documentation (/docs) endpoint.") + + // Metrics Flagd + EnableMetrics = flag.Bool("enable.metrics", true, "Enable metrics (/metrics) endpoint.") + + // Misc Flags + VersionFlag = flag.Bool("v", false, "Outputs package version") +) + +func Flags() { + flag.Parse() + + // Flag Sanity Check + if *DefaultSize > *MaxSize { + log.Fatalf("-qr.default.size cannot be bigger than -qr.max.size!") + } +} diff --git a/initialize/global.go b/initialize/global.go new file mode 100644 index 0000000..510681a --- /dev/null +++ b/initialize/global.go @@ -0,0 +1,16 @@ +package initialize + +import ( + "os" +) + +const ( + Version string = "1.1.0" + MetricNamespace string = "qr_generator" +) + +var ( + ApplicationName string = os.Args[0] + +) + diff --git a/main.go b/main.go index 17d390f..f440f0b 100644 --- a/main.go +++ b/main.go @@ -1,47 +1,17 @@ package main import ( - "flag" - "bytes" "fmt" - "image/png" - "net" - "net/url" - "strconv" "net/http" "os" log "github.com/sirupsen/logrus" - "github.com/boombuler/barcode" - "github.com/boombuler/barcode/qr" -) - -const ( - version = "1.0.0" -) - -var ( - // Webserver Flags - portNumber = flag.String("port", "8080", "The port number to listen on for HTTP requests.") - address = flag.String("address", "0.0.0.0", "The address to listen on for HTTP requests.") - - https = flag.Bool("https", false, "Enable, or Disable HTTPS") - server_crt = flag.String("server_crt", "server.crt", "Certificate file") - server_key = flag.String("server_key", "server.key", "Certificate key file.") - - // QR Setup Flags - defaultSize = flag.String("defualtQRSize", "250", "Default QR Image Size, if unspecified by end user.") - // Logging Flags - logLevel = flag.String("log.level", "info", "The level of logs to log") - logConn = flag.Bool("log.conn", true, "Log connections to API") - - // Misc Flags - versionFlag = flag.Bool("v", false, "Outputs package version") - - // Useful Vars - applicationName = os.Args[0] + "github.com/ashleyprimo/go-qr-generator/initialize" + "github.com/ashleyprimo/go-qr-generator/qr" + "github.com/ashleyprimo/go-qr-generator/documentation" + "github.com/prometheus/client_golang/prometheus/promhttp" ) func loglevel(opt string) { @@ -59,177 +29,44 @@ func loglevel(opt string) { } } -func InternalServerError(w http.ResponseWriter, r *http.Request, l string) { - log.Errorf(l) - w.WriteHeader(http.StatusInternalServerError) - w.Write([]byte("Sorry, we were unable to serve your request due an internal server error!")) - return -} - -func BadReqeustError(w http.ResponseWriter, r *http.Request, l string) { - // This is a user error, log as debug. - log.Debugf(l) - - // Write Output - w.WriteHeader(http.StatusBadRequest) - w.Write([]byte(fmt.Sprintf("Invalid user input; please refer to %s/docs", r.Host))) - return -} - -func logConnection (r *http.Request) { - if *logConn { - source, _, _ := net.SplitHostPort(r.RemoteAddr) - log.Infof("Request for %s from %s", r.RequestURI, source) - } -} - -func qrEngine(w http.ResponseWriter, r *http.Request) { - logConnection(r) - - // Collect Parameter(s) - parameters := r.URL.Query() - - // dataString (data to be encoded) - if _, present := parameters["data"]; present == false { - BadReqeustError(w, r, "No 'data' string provided.") - return - } - dataString := parameters["data"][0] - log.Debugf("Data String: %s", dataString) - - // sizeString (size of QR code) - var sizeString string - if _, present := parameters["size"]; present == false { - log.Debugf("No 'size' string provided; will default to %s", *defaultSize) - sizeString = *defaultSize - } else { - sizeString = parameters["size"][0] - } - - log.Debugf("Size String: %s", sizeString) - - // Convert sizeString to sizeInt - sizeInt, err := strconv.Atoi(sizeString) - if err != nil { - InternalServerError(w, r, "Unable to convert sizeString to sizeInt") - } - - // - dataStringUnescaped, err := url.QueryUnescape(dataString) - if err != nil { - InternalServerError(w, r, "Unable to 'Unescape' data string provided...") - } else { - log.Debugf("Data String Unescaped: %s", dataStringUnescaped,) - } - - // Generate QR - code, err := qr.Encode(dataStringUnescaped, qr.L, qr.Auto) - if err != nil { - InternalServerError(w, r, "Failed to generate QR code") - } else { - log.Debugf("Generated QR Code: %s", code,) - } - - // Scale the barcode to the appropriate size - code, err = barcode.Scale(code, sizeInt, sizeInt) - if err != nil { - InternalServerError(w, r, "Unable to scale QR code.") - } else { - log.Debugf("Generated QR Code: %s", code,) - } - - // Encode PNG - buffer := new(bytes.Buffer) - if err := png.Encode(buffer, code); err != nil { - InternalServerError(w, r, "Unable to encode PNG from code buffer.") - } - - // Output QR - w.Header().Set("Content-Type", "image/png") - w.Header().Set("Content-Length", strconv.Itoa(len(buffer.Bytes()))) - - if _, err := w.Write(buffer.Bytes()); err != nil { - InternalServerError(w, r, "Unable to write/output QR code.") - } - - return -} - - -func documentation(w http.ResponseWriter, r *http.Request) { - logConnection(r) - - const ( - documentationPage = ` - - - %[1]s API documentation - - - - - - - -

%[1]s API documentation

-

How to make a request

-

It's very simple, there are two possible parameters currently, size (which is optional) and data (which is mandatory)!

-

Example Request: http://%[2]s/?size=200&data=This%%20is%%20an%%20example

-

This above example, will generate a QR code with the text "This is an example", with a size of 200x200 pixels- it's that easy.

- - - - ` - ) - w.Write([]byte(fmt.Sprintf(documentationPage, applicationName, r.Host))) -} - func health(w http.ResponseWriter, r *http.Request) { - logConnection(r) w.WriteHeader(http.StatusOK) w.Write([]byte("Ping.")) } func main() { - flag.Parse() + initialize.Flags() - if *versionFlag { - fmt.Printf("%s v%s", applicationName, version) + if *initialize.VersionFlag { + fmt.Printf("%s v%s", initialize.ApplicationName, initialize.Version) os.Exit(0) } - loglevel(*logLevel) + loglevel(*initialize.LogLevel) + + // QR Engine API Endpoint + http.HandleFunc(*initialize.QREndpoint, qr.Engine) - http.HandleFunc("/", qrEngine) - http.HandleFunc("/docs", documentation) + // Documentation Endpoint + if *initialize.EnableDocs { + log.Debugf("Documentation Endpoint Enabled") + http.HandleFunc("/docs", docs.Landing) + } + + // Health Check Endpoint http.HandleFunc("/health", health) + // Metrics Endpoint + if *initialize.EnableMetrics { + log.Debugf("Metrics Endpoint Enabled") + http.Handle("/metrics", promhttp.Handler()) + } - log.Infof("Listening for requests on %s:%s", *address, *portNumber) + log.Infof("Listening for requests on %s:%s", *initialize.Address, *initialize.PortNumber) - if *https { - log.Fatalf("Failed to start web server with TLS: %s", http.ListenAndServeTLS(fmt.Sprintf("%s:%s", *address, *portNumber), *server_crt, *server_key, nil)) + if *initialize.Https { + log.Fatalf("Failed to start web server with TLS: %s", http.ListenAndServeTLS(fmt.Sprintf("%s:%s", *initialize.Address, *initialize.PortNumber), *initialize.Server_crt, *initialize.Server_key, nil)) } else { - log.Fatalf("Failed to start web server: %s", http.ListenAndServe(fmt.Sprintf("%s:%s", *address, *portNumber), nil)) + log.Fatalf("Failed to start web server: %s", http.ListenAndServe(fmt.Sprintf("%s:%s", *initialize.Address, *initialize.PortNumber), nil)) } } diff --git a/metrics.go b/metrics.go new file mode 100644 index 0000000..6c7289a --- /dev/null +++ b/metrics.go @@ -0,0 +1,22 @@ +package main + +import ( + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promauto" + + "github.com/ashleyprimo/go-qr-generator/initialize" +) + +var ( + versionExport = promauto.NewGauge( + prometheus.GaugeOpts{ + Namespace: initialize.MetricNamespace, + Name: "version", + Help: "current running version", + ConstLabels: map[string]string{ + "version": initialize.Version, + }, + }, + ) +) + diff --git a/qr/engine.go b/qr/engine.go new file mode 100644 index 0000000..e7bdf52 --- /dev/null +++ b/qr/engine.go @@ -0,0 +1,152 @@ +package qr + +import ( + "fmt" + "net" +// "flag" + "bytes" + "image/png" + "net/url" + "strconv" + "net/http" + + "github.com/ashleyprimo/go-qr-generator/initialize" + + log "github.com/sirupsen/logrus" + "github.com/boombuler/barcode" + "github.com/boombuler/barcode/qr" +) + +func InternalServerError(w http.ResponseWriter, r *http.Request, l string) { + requests.WithLabelValues("5xx").Inc() + + // Log Error (server side) + log.Errorf(l) + + // Write Output + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte("Sorry, we were unable to serve your request due an internal server error!")) +} + +func BadReqeustError(w http.ResponseWriter, r *http.Request, l string) { + requests.WithLabelValues("4xx").Inc() + + // This is a user error/failure, log as debug. + log.Debugf(l) + + // Write Output + w.WriteHeader(http.StatusBadRequest) + + w.Header().Set("Content-Type", "text/html") + w.Write([]byte("Bad Request!")) + if *initialize.EnableDocs { + w.Write([]byte(fmt.Sprintf(" Maybe try looking at our documentation at %[1]s/docs", r.Host))) + } +} + +func logConnection (r *http.Request) { + if *initialize.LogConn { + source, _, _ := net.SplitHostPort(r.RemoteAddr) + log.Infof("Request for %s from %s", r.RequestURI, source) + } +} + +func Engine(w http.ResponseWriter, r *http.Request) { + initialize.Flags() + + numRequests.Inc() + logConnection(r) + + // Collect Parameter(s) + parameters := r.URL.Query() + + // dataString (data to be encoded) + if _, present := parameters["data"]; present == false { + BadReqeustError(w, r, "No 'data' string provided.") + return + } + dataString := parameters["data"][0] + log.Debugf("Data String: %s", dataString) + + // sizeString (size of QR code) + var sizeString string + if _, present := parameters["size"]; present == false { + defaultSize := strconv.Itoa(*initialize.DefaultSize) + log.Debugf("No 'size' string provided; will default to %s", defaultSize) + sizeString = defaultSize + } else { + sizeString = parameters["size"][0] + } + + log.Debugf("Size String: %s", sizeString) + + // Convert sizeString to sizeInt + sizeInt, err := strconv.Atoi(sizeString) + if err != nil { + InternalServerError(w, r, "Unable to convert sizeString to sizeInt") + return + } + + // Ensure requested size is within set limits + if sizeInt > *initialize.MaxSize { + BadReqeustError(w, r, "Requested image size exceeds maximum set") + return + } + + //Unescape Data Input + dataStringUnescaped, err := url.QueryUnescape(dataString) + if err != nil { + InternalServerError(w, r, "Unable to 'Unescape' data string provided...") + return + } else { + log.Debugf("Data String Unescaped: %s", dataStringUnescaped,) + } + + // Generate QR + code, err := qr.Encode(dataStringUnescaped, qr.L, qr.Auto) + if err != nil { + InternalServerError(w, r, "Failed to generate QR code") + return + } else { + log.Debugf("Generated QR Code: %s", code,) + } + + // Ensure QR scale is possible + codeBounds := code.Bounds() + codeHeight := codeBounds.Max.Y - codeBounds.Min.Y + if sizeInt < codeHeight { + BadReqeustError(w, r, "Requested image size is smaller than actual QR minimum size") + return + } + + // Scale the barcode to the appropriate size + code, err = barcode.Scale(code, sizeInt, sizeInt) + if err != nil { + InternalServerError(w, r, "Unable to scale QR code.") + return + } else { + log.Debugf("Generated QR Code: %s", code,) + } + + // Encode PNG + buffer := new(bytes.Buffer) + if err := png.Encode(buffer, code); err != nil { + InternalServerError(w, r, "Unable to encode PNG from code buffer.") + return + } + + // Output QR + w.Header().Set("Content-Type", "image/png") + w.Header().Set("Content-Length", strconv.Itoa(len(buffer.Bytes()))) + + if _, err := w.Write(buffer.Bytes()); err != nil { + InternalServerError(w, r, "Unable to write/output QR code.") + return + } else { + // Log Success + requests.WithLabelValues("2xx").Inc() + log.Debugf("Successfully wrote QR code") + } + + return +} diff --git a/qr/metrics.go b/qr/metrics.go new file mode 100644 index 0000000..9c4827c --- /dev/null +++ b/qr/metrics.go @@ -0,0 +1,27 @@ +package qr + +import ( + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promauto" + + "github.com/ashleyprimo/go-qr-generator/initialize" +) + +var ( + numRequests = promauto.NewCounter( + prometheus.CounterOpts{ + Namespace: initialize.MetricNamespace, + Name: "requests_total", + Help: "Total number of requests to QR API endpoint.", + }, + ) + + requests = promauto.NewCounterVec( + prometheus.CounterOpts{ + Namespace: initialize.MetricNamespace, + Name: "requests", + Help: "Completed requests to QR API endpoint.", + }, + []string{"type"}, + ) +)