Skip to content

Commit

Permalink
Merge pull request #14 from chialab/metrics
Browse files Browse the repository at this point in the history
Metrics
  • Loading branch information
le0m authored Sep 13, 2024
2 parents 514e343 + ef46e79 commit 4b1bd5b
Show file tree
Hide file tree
Showing 15 changed files with 519 additions and 302 deletions.
17 changes: 11 additions & 6 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,14 +50,15 @@ jobs:

- name: Build
env:
TAG: ${{ github.ref_name }}
CGO_ENABLED: 0
run: |
GOOS=linux GOARCH=amd64 go build -C ${{ matrix.module }} -ldflags '-s' -o ../build/print2pdf-${{ matrix.module }}-linux-amd64
GOOS=linux GOARCH=arm64 go build -C ${{ matrix.module }} -ldflags '-s' -o ../build/print2pdf-${{ matrix.module }}-linux-arm64
GOOS=darwin GOARCH=amd64 go build -C ${{ matrix.module }} -ldflags '-s' -o ../build/print2pdf-${{ matrix.module }}-darwin-amd64
GOOS=darwin GOARCH=arm64 go build -C ${{ matrix.module }} -ldflags '-s' -o ../build/print2pdf-${{ matrix.module }}-darwin-arm64
GOOS=windows GOARCH=amd64 go build -C ${{ matrix.module }} -ldflags '-s' -o ../build/print2pdf-${{ matrix.module }}-windows-amd64
GOOS=windows GOARCH=arm64 go build -C ${{ matrix.module }} -ldflags '-s' -o ../build/print2pdf-${{ matrix.module }}-windows-arm64
GOOS=linux GOARCH=amd64 go build -C ${{ matrix.module }} -ldflags "-s -X main.Version=${TAG#v}" -o ../build/print2pdf-${{ matrix.module }}-linux-amd64
GOOS=linux GOARCH=arm64 go build -C ${{ matrix.module }} -ldflags "-s -X main.Version=${TAG#v}" -o ../build/print2pdf-${{ matrix.module }}-linux-arm64
GOOS=darwin GOARCH=amd64 go build -C ${{ matrix.module }} -ldflags "-s -X main.Version=${TAG#v}" -o ../build/print2pdf-${{ matrix.module }}-darwin-amd64
GOOS=darwin GOARCH=arm64 go build -C ${{ matrix.module }} -ldflags "-s -X main.Version=${TAG#v}" -o ../build/print2pdf-${{ matrix.module }}-darwin-arm64
GOOS=windows GOARCH=amd64 go build -C ${{ matrix.module }} -ldflags "-s -X main.Version=${TAG#v}" -o ../build/print2pdf-${{ matrix.module }}-windows-amd64
GOOS=windows GOARCH=arm64 go build -C ${{ matrix.module }} -ldflags "-s -X main.Version=${TAG#v}" -o ../build/print2pdf-${{ matrix.module }}-windows-arm64
- name: Release
uses: softprops/action-gh-release@v2
Expand Down Expand Up @@ -111,10 +112,14 @@ jobs:

- name: Build and push
uses: docker/build-push-action@v6
env:
TAG: ${{ github.ref_name }}
with:
context: ${{ matrix.module }}
file: ${{ matrix.module }}/Dockerfile
platforms: linux/amd64,linux/arm64
build-args: |
VERSION=${TAG#v}
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ IMAGE_NAME ?= print2pdf
IMAGE_TAG ?= dev

bin-plain:
CGO_ENABLED=0 go build -ldflags '-s' -o build/print2pdf-plain plain/main.go
CGO_ENABLED=0 go build -C plain/ -ldflags '-s' -o ../build/print2pdf-plain

bin-lambda:
CGO_ENABLED=0 go build -ldflags '-s' -tags 'lambda.norpc' -o build/print2pdf-lambda lambda/main.go
CGO_ENABLED=0 go build -C lambda/ -ldflags '-s' -tags 'lambda.norpc' -o ../build/print2pdf-lambda

docker-plain:
docker build -t $(IMAGE_NAME):$(IMAGE_TAG) --file plain/Dockerfile plain/
Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,10 @@ for the supported methods of providing the credentials.

Launch the binary to start the webserver at `http://localhost:3000`.

The webserver provides two endpoints:
The webserver provides three endpoints:
- `/v1/print` stores the generated PDF in an AWS S3 bucket
- `/v2/print` streams the generated PDF as the response
- `/metrics` exports metrics in the Prometheus format

Both endpoints accept `POST` requests with the following body parameters:
- `url` (**required**) the URL of the page to print as PDF
Expand Down
30 changes: 30 additions & 0 deletions go.work.sum
Original file line number Diff line number Diff line change
@@ -1,7 +1,37 @@
github.com/alecthomas/kingpin/v2 v2.4.0/go.mod h1:0gyi0zQnjuFk8xrkNKamJoyUo382HRL7ATRpFZCw6tE=
github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE=
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/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/go-kit/log v0.2.1/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0=
github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/xhit/go-str2duration/v2 v2.1.0/go.mod h1:ohY8p+0f07DiV6Em5LKB0s2YpLtXVyJfNt1+BlmyAsU=
golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg=
golang.org/x/oauth2 v0.22.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
3 changes: 2 additions & 1 deletion lambda/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,15 @@
FROM --platform=$BUILDPLATFORM golang:1.22-alpine AS builder
ARG TARGETOS
ARG TARGETARCH
ARG VERSION=development

WORKDIR /app

COPY go.mod go.sum /app/
RUN go mod download

COPY *.go /app/
RUN CGO_ENABLED=0 GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -ldflags '-s' -tags 'lambda.norpc' -o build/print2pdf
RUN CGO_ENABLED=0 GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -ldflags "-s -X main.Version=${VERSION}" -tags 'lambda.norpc' -o build/print2pdf

###
# Download chromium for AWS Lambda
Expand Down
1 change: 1 addition & 0 deletions lambda/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -36,5 +36,6 @@ require (
github.com/google/uuid v1.6.0 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/stretchr/testify v1.9.0 // indirect
golang.org/x/sys v0.25.0 // indirect
)
4 changes: 2 additions & 2 deletions lambda/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,8 @@ github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde h1:x0TT0RDC7UhA
github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde/go.mod h1:nZgzbfBr3hhjoZnS66nKrHmduYNpc34ny7RK4z5/HM0=
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/stretchr/testify v1.7.2 h1:4jaiDzPyXQvSd7D0EjG45355tLlV3VOECpq10pLC+8s=
github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
Expand Down
93 changes: 93 additions & 0 deletions lambda/handlers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package main

import (
"context"
"encoding/json"
"fmt"
"os"
"slices"
"strings"

"github.com/aws/aws-lambda-go/events"
"github.com/chialab/print2pdf-go/print2pdf"
)

// Handle a request.
func handler(ctx context.Context, event events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {
headers := map[string]string{"Content-Type": "application/json"}
if CorsAllowedHosts == "" || CorsAllowedHosts == "*" {
headers["Access-Control-Allow-Origin"] = "*"
} else {
allowedHosts := strings.Split(CorsAllowedHosts, ",")
origin := event.Headers["Origin"]
if origin == "" {
origin = event.Headers["origin"]
}
if slices.Contains(allowedHosts, origin) {
headers["Access-Control-Allow-Origin"] = origin
}
}

var data print2pdf.GetPDFParams
err := json.Unmarshal([]byte(event.Body), &data)
if err != nil {
fmt.Fprintf(os.Stderr, "error decoding JSON: %s\noriginal request body: %s\n", err, event.Body)

return jsonError("internal server error", 500), nil
}
if !strings.HasSuffix(data.FileName, ".pdf") {
data.FileName += ".pdf"
}

h, err := print2pdf.NewS3Handler(ctx, BucketName, data.FileName)
if err != nil {
fmt.Fprintf(os.Stderr, "error creating print handler: %s\n", err)

return jsonError("internal server error", 500), nil
}

url, err := print2pdf.PrintPDF(ctx, data, h)
if ve, ok := err.(print2pdf.ValidationError); ok {
fmt.Fprintf(os.Stderr, "request validation error: %s\n", ve)

return jsonError(ve.Error(), 400), nil
} else if err != nil {
fmt.Fprintf(os.Stderr, "error getting PDF: %s\n", err)

return jsonError("internal server error", 500), nil
}

body, err := json.Marshal(ResponseData{Url: url})
if err != nil {
fmt.Fprintf(os.Stderr, "error encoding response to JSON: %s\n", err)

return jsonError("internal server error", 500), nil
}

return events.APIGatewayProxyResponse{
StatusCode: 200,
Body: string(body),
Headers: headers,
}, nil
}

// Prepare an HTTP error response.
func jsonError(message string, code int) events.APIGatewayProxyResponse {
ct := "application/json"
body, err := json.Marshal(ResponseError{message})
if err != nil {
fmt.Fprintf(os.Stderr, "error encoding error message to JSON: %s\noriginal error: %s\n", err, message)
body = []byte("internal server error")
code = 500
ct = "text/plain"
}

return events.APIGatewayProxyResponse{
StatusCode: code,
Body: string(body),
Headers: map[string]string{
"Content-Type": ct,
"X-Content-Type-Options": "nosniff",
},
}
}
103 changes: 18 additions & 85 deletions lambda/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,12 @@ package main

import (
"context"
"encoding/json"
"fmt"
"os"
"os/signal"
"slices"
"strings"
"syscall"

"github.com/aws/aws-lambda-go/events"
"github.com/aws/aws-lambda-go/lambda"
"github.com/chialab/print2pdf-go/print2pdf"
)
Expand All @@ -25,6 +22,9 @@ type ResponseError struct {
Message string `json:"message"`
}

// Version string, set at build time.
var Version = "development"

// S3 bucket name. Required.
var BucketName = os.Getenv("BUCKET")

Expand All @@ -33,102 +33,35 @@ var CorsAllowedHosts = os.Getenv("CORS_ALLOWED_HOSTS")

// Init function checks for required environment variables.
func init() {
if len(os.Args) > 1 && slices.Contains([]string{"-v", "--version"}, os.Args[1]) {
fmt.Printf("Version: %s\n", Version)
os.Exit(0)
}
if BucketName == "" {
fmt.Fprintln(os.Stderr, "missing required environment variable BUCKET")
os.Exit(1)
}
}

func main() {
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
defer stop()
if err := print2pdf.StartBrowser(ctx); err != nil {
fmt.Fprintf(os.Stderr, "error starting browser: %s\n", err)
if err := run(); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}

lambda.Start(handler)

<-ctx.Done()
stop()
}

// Handle a request.
func handler(ctx context.Context, event events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {
headers := map[string]string{"Content-Type": "application/json"}
if CorsAllowedHosts == "" || CorsAllowedHosts == "*" {
headers["Access-Control-Allow-Origin"] = "*"
} else {
allowedHosts := strings.Split(CorsAllowedHosts, ",")
origin := event.Headers["Origin"]
if origin == "" {
origin = event.Headers["origin"]
}
if slices.Contains(allowedHosts, origin) {
headers["Access-Control-Allow-Origin"] = origin
}
}

var data print2pdf.GetPDFParams
err := json.Unmarshal([]byte(event.Body), &data)
if err != nil {
fmt.Fprintf(os.Stderr, "error decoding JSON: %s\noriginal request body: %s\n", err, event.Body)

return JsonError("internal server error", 500), nil
}
if !strings.HasSuffix(data.FileName, ".pdf") {
data.FileName += ".pdf"
}

h, err := print2pdf.NewS3Handler(ctx, BucketName, data.FileName)
if err != nil {
fmt.Fprintf(os.Stderr, "error creating print handler: %s\n", err)

return JsonError("internal server error", 500), nil
}

url, err := print2pdf.PrintPDF(ctx, data, h)
if ve, ok := err.(print2pdf.ValidationError); ok {
fmt.Fprintf(os.Stderr, "request validation error: %s\n", ve)

return JsonError(ve.Error(), 400), nil
} else if err != nil {
fmt.Fprintf(os.Stderr, "error getting PDF: %s\n", err)

return JsonError("internal server error", 500), nil
}

body, err := json.Marshal(ResponseData{Url: url})
if err != nil {
fmt.Fprintf(os.Stderr, "error encoding response to JSON: %s\n", err)
func run() (err error) {
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
defer stop()
if err = print2pdf.StartBrowser(ctx); err != nil {
return fmt.Errorf("error starting browser: %s", err)

return JsonError("internal server error", 500), nil
}

return events.APIGatewayProxyResponse{
StatusCode: 200,
Body: string(body),
Headers: headers,
}, nil
}
lambda.StartWithOptions(handler, lambda.WithContext(ctx))

// Prepare an HTTP error response.
func JsonError(message string, code int) events.APIGatewayProxyResponse {
ct := "application/json"
body, err := json.Marshal(ResponseError{message})
if err != nil {
fmt.Fprintf(os.Stderr, "error encoding error message to JSON: %s\noriginal error: %s\n", err, message)
body = []byte("internal server error")
code = 500
ct = "text/plain"
}
<-ctx.Done()
stop()

return events.APIGatewayProxyResponse{
StatusCode: code,
Body: string(body),
Headers: map[string]string{
"Content-Type": ct,
"X-Content-Type-Options": "nosniff",
},
}
return nil
}
3 changes: 2 additions & 1 deletion plain/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,15 @@
FROM --platform=$BUILDPLATFORM golang:1.22-alpine AS builder
ARG TARGETOS
ARG TARGETARCH
ARG VERSION=development

WORKDIR /app

COPY go.mod go.sum /app/
RUN go mod download

COPY *.go /app/
RUN CGO_ENABLED=0 GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -ldflags '-s' -o build/print2pdf
RUN CGO_ENABLED=0 GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -ldflags "-s -X main.Version=${VERSION}" -o build/print2pdf

###
# Final image
Expand Down
Loading

0 comments on commit 4b1bd5b

Please sign in to comment.