Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

OpenLibrary API proxy example #79

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ issues:
# checks from err113 are useful.
- "err113: do not define dynamic errors.*"
exclude-rules:
- path: internal/examples/.*/.*\.go
- path: examples/.*/.*\.go
linters:
- forbidigo # log.Fatal, fmt.Printf used in example programs
- gosec
Expand Down
7 changes: 3 additions & 4 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -29,17 +29,16 @@ clean: ## Delete intermediate build artifacts
.PHONY: test
test: build ## Run unit tests
$(GO) test -vet=off -race -cover ./...
cd internal/examples/pets && $(GO) test -vet=off -race -cover ./...
cd internal/examples/connect+grpc && $(GO) test -vet=off -race -cover ./...
cd examples/pets && $(GO) test -vet=off -race -cover ./...
cd examples/connect+grpc && $(GO) test -vet=off -race -cover ./...

.PHONY: build
build: generate ## Build all packages
$(GO) build ./...

.PHONY: generate
generate: $(BIN)/buf $(BIN)/license-header ## Regenerate code and licenses
$(BIN)/buf generate internal/proto
cd internal/examples/pets && ../../../$(BIN)/buf generate internal/proto
$(BIN)/buf generate
@# We want to operate on a list of modified and new files, excluding
@# deleted and ignored files. git-ls-files can't do this alone. comm -23 takes
@# two files and prints the union, dropping lines common to both (-3) and
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ between gRPC, gRPC-Web, Connect, or REST, Vanguard has got you covered. With sup
Google's [HTTP transcoding options](https://github.com/googleapis/googleapis/blob/master/google/api/http.proto#L44),
it can effortlessly translate protocols using strongly typed Protobuf definitions.

[See an example in action!](internal/examples/fileserver/main.go)
[See an example in action!](examples/fileserver/main.go)

## Why Vanguard?

Expand Down
3 changes: 2 additions & 1 deletion buf.work.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
version: v1
directories:
- internal/proto
- internal/examples/pets/internal/proto
- examples/pets/proto
- examples/openlibraryproxy/proto
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
module connectrpc.com/vanguard/internal/examples/connect+grpc
module connectrpc.com/vanguard/examples/connect+grpc

go 1.20

// once the repo is public, we can remove this
replace connectrpc.com/vanguard => ../../../
replace connectrpc.com/vanguard => ../../

require (
buf.build/gen/go/connectrpc/eliza/connectrpc/go v1.11.1-20230822171018-8b8b971d6fde.1
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
42 changes: 42 additions & 0 deletions examples/openlibraryproxy/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# OpenLibrary Proxy

Open Library proxy creates a vanguard service to serve [books.proto](./proto/openlibrary/v1/books.proto].

## Demo

Run the proxy server with:
```
go run .
```

Now we can use `buf curl` to call OpenLibrary's REST API via our newly created proxy.

### Get a book

```shell
curl --location 'https://openlibrary.org/api/books?bibkeys=ISBN:0201558025,LCCN:93005405&format=json'
```

```shell
buf curl --schema=./proto http://localhost:8080/openlibrary.v1.BooksService/GetBooks --data '{"bibkeys": "ISBN:0201558025,LCCN:93005405"}'
```

### Search for books

Search currently can't translate due to duplicate field errors:
```
{
"code": "internal",
"message": "proto: (line 17562:5): duplicate field \"num_found\""
}
```

```shell
curl --location 'http://openlibrary.org/search.json?q=the%2Blord%2Bof%2Bthe%2Brings
```

```shell
buf curl --schema=./proto http://localhost:8080/openlibrary.v1.BooksService/SearchBooks \
--data '{"q": "the lord of the rings"}'
```

106 changes: 106 additions & 0 deletions examples/openlibraryproxy/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
// Copyright 2023 Buf Technologies, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package main

import (
"flag"
"log"
"net/http"
"net/http/httputil"
"net/url"
"os"
"strings"

"connectrpc.com/vanguard"
_ "connectrpc.com/vanguard/internal/gen/openlibrary/v1"
)

func main() {
flagset := flag.NewFlagSet("stripeproxy", flag.ExitOnError)
port := flagset.String("p", "8080", "port to serve on")
addr := flagset.String("url", "https://openlibrary.org", "base URL to proxy to")
debug := flagset.Bool("debug", false, "enable debug logging")
if err := flagset.Parse(os.Args[1:]); err != nil {
log.Fatal(err)
}

remote, err := url.Parse(*addr)
if err != nil {
log.Fatal(err)
}
proxy := httputil.NewSingleHostReverseProxy(remote)
if *debug {
proxy.Transport = &DebugTransport{}
}
// Create the handler for the proxy.
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
log.Println(r.Method, r.URL)
r.Host = remote.Host
w.Header().Set("X-Vanguard", "OpenLibrary Proxy")

// Alter the request for the OpenLibrary API implementation to
// ensure that all requests are JSON and use the data jscmd.
if strings.HasPrefix(r.URL.Path, "/api/books") {
// Convert all requests to JSON, and use the data jscmd.
values := r.URL.Query()
values.Set("format", "json")
values.Set("jscmd", "data")
r.URL.RawQuery = values.Encode()
}
proxy.ServeHTTP(w, r)
})

mux := &vanguard.Mux{
Protocols: []vanguard.Protocol{
// Convert all requests to REST.
vanguard.ProtocolREST,
},
}
// Register the OpenLibrary BooksService.
if err := mux.RegisterServiceByName(handler, "openlibrary.v1.BooksService"); err != nil {
log.Fatal(err)
}

// Alter the codec to use proto names for JSON.
mux.AddCodec(vanguard.CodecJSON, func(resolver vanguard.TypeResolver) vanguard.Codec {
codec := vanguard.DefaultJSONCodec(resolver)
codec.MarshalOptions.UseProtoNames = true
emcfarlane marked this conversation as resolved.
Show resolved Hide resolved
return codec
})

log.Printf("Proxy %s on HTTP port: %s\n", *addr, *port)
log.Fatal(http.ListenAndServe(":"+*port, mux))
}

type DebugTransport struct{}

func (d *DebugTransport) RoundTrip(req *http.Request) (*http.Response, error) {
req.RequestURI = req.URL.String()
raw, err := httputil.DumpRequest(req, true)
if err != nil {
return nil, err
}
log.Println("Request:", string(raw))
rsp, err := http.DefaultTransport.RoundTrip(req)
if err != nil {
return nil, err
}
raw, err = httputil.DumpResponse(rsp, true)
if err != nil {
return nil, err
}
log.Println("Response:", string(raw))
return rsp, nil
}
Loading