jokesontap

package module
v0.0.0-...-a5960b6 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 14, 2019 License: MIT Imports: 12 Imported by: 0

README

jokesontap

Have you ever wanted to query Chuck Norris-like jokes as fast as possible? Of course you have. Get them while they're hot.

This application server retrieves fresh, specifically nerdy, jokes from the Internet Chuck Norris Database, but adds a flare of personality by switching out the name with a random one from uinames.com. Why should Chuck get all the credit?

Building

build.sh will test and build the server binary.

cd jokesontap
./build.sh
ls ./bin

Features

  • fast, concurrent web server
  • ahead-of-time random name cache partially mitigates backpressure from names API and avoids API rate limiting
  • detailed logging
  • customized application settings through command line parameters
  • automatic build and testing through build script
  • well tested code, of course

Usage

Starting the Server

After building the binary can be run with defaults.

chmod +x ./bin/jokesontap
./bin/jokesontap 

Or set server options.

./bin/jokesontap --help
./bin/jokesontap --port 8080 --log-level error
Querying

The server has a single root endpoint which will return a new Chuck Norris-like joke with a random name.

Assuming the server is running on default port 5000, query the server and get a joke.

$ curl http://localhost:5000
Bruce Banner's OSI network model has only one layer - Physical.

Known Limitations

As of writing uinames.com, which is used to generate the random names, has a rate limit after a certain number of requests. This is partially mitigated by eagerly querying and storing names in memory, but if pushed the server may not be able to serve a new joke for lack of a random name.

Benchmarks

Benchmarking the server for 30 seconds, after the names cache (10,000 entries) was allowed to fill. Disclaimer: benchmark results were run with the server and benchmarking client on the same laptop.

The results are as follows:

$ timeout 60 siege -b -c 100 http://localhost:5000/
~~~
Transactions:                  13000 hits
Availability:                  97.01 %
Elapsed time:                  59.99 secs
Data transferred:               0.96 MB
Response time:                  0.43 secs
Transaction rate:             216.70 trans/sec
Throughput:                     0.02 MB/sec
Concurrency:                   92.77
Successful transactions:       13000
Failed transactions:             400
Longest transaction:            5.30
Shortest transaction:           0.15

Ultimately this service's current bottleneck is the name server which we depend on to generate random names. The name server throttles our traffic and thus we cannot generate names fast enough. This could be remedied by some of the enhancements in TODO.

TODO

  • Implement caching so that when given the Cache-Control header the server will reuse a previous name always, or when the name server is unavailable.
  • Implement Prometheus metrics.
  • As of writing, the Internet Chuck Norris Database only contains 574 jokes total. This is such a small database it would be more efficient to simply download the entire data set on a daily basis and serve the jokes from memory without calling out to the external service. This functionality needs to be confirmed with the specific application requirements before implementation.

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	// ErrNon200NameApiResponse occurs when we receive a status code that is not 200 or 429.
	ErrNon200NameApiResponse   = errors.New("general error in names API response")
	ErrNamesApiTooManyRequests = errors.New("too many requests to names API")
)
View Source
var (
	ErrNamesChanUninitialized = errors.New("the server's names channel is uninitialized, please submit an issue")
	ErrNoNamesAvailable       = errors.New("the server has no names to provide")
)
View Source
var ErrUnsuccessfulJokeQuery = errors.New("general error getting new joke")

Functions

func InitLogger

func InitLogger(w io.Writer, level string, format string, prettyJson bool)

InitLogger sets values on the global logger for use everywhere else in the application.

Types

type BudgetNameReq

type BudgetNameReq struct {

	// MinDiff is the minimum amount of time between now and the oldest names API request allowed before
	// we are "over budget", after which we cannot make any more requests.
	//
	// When creating a budget where x operations can be run in y time, this should be set as the y value.
	MinDiff time.Duration

	// NameClient is used to request new names.
	NameClient NameRequester
	// NameChan is populated with the results of each names API request.
	NameChan chan Name
	// contains filtered or unexported fields
}

TODO: this implementation is fairly specific to the problem it solves. We could break this out to be a more general TODO: budget executor, but probably not necessary until we have to use this pattern in more than one place. BudgetNameReq is a budgeted names API requester which will make no more requests than the external API will tolerate.

func (*BudgetNameReq) RequestOften

func (b *BudgetNameReq) RequestOften()

RequestOften gets new names from the names API and pushes them to the names channel, as often as possible. If the timestamp of the oldest call is more than MinDiff then we wait until we are with the budget to make the next call.

type Joke

type Joke struct {
	Type  string `json:"type"`
	Value struct {
		ID   int    `json:"id"`
		Joke string `json:"joke"`
	} `json:"value"`
}

Joke maps to the Internet Chuck Norris database API response.

func (*Joke) Successful

func (j *Joke) Successful() bool

Successful returns true when a populated Joke response was successful.

type JokeClient

type JokeClient struct {
	// ApiUrl is the base URL of the jokes API to query
	ApiUrl url.URL
	// HttpClient is a http client which can be reused across multiple requests.
	HttpClient *http.Client
}

JokeClient can request jokes from a joke server.

func NewJokeClient

func NewJokeClient(baseUrl url.URL) *JokeClient

NewJokeClient creates a JokeClient with default values where baseUrl is the API URL without any parameters.

func (*JokeClient) Joke

func (c *JokeClient) Joke() (string, error)

Joke returns a new joke.

func (*JokeClient) JokeWithCustomName

func (c *JokeClient) JokeWithCustomName(fName, lName string) (string, error)

JokeWithCustomName gets a new joke using the first and last name passed in.

type Name

type Name struct {
	Name    string `json:"name"`
	Surname string `json:"surname"`
}

type NameClient

type NameClient struct {
	// ApiUrl is the full URL of the names server from which we can request new names.
	ApiUrl url.URL
	// HttpClient is a http client which can be reused across multiple requests.
	HttpClient *http.Client
	// contains filtered or unexported fields
}

NameClient can request random names from a names server.

func NewNameClient

func NewNameClient(baseUrl url.URL) *NameClient

NewNameClient creates a NameClient with default values where baseUrl is the API URL to query.

func (*NameClient) Names

func (c *NameClient) Names() ([]Name, error)

Names gets several names from the names API. Names is intelligent as it relates to the restrictions of the name API and will short circuit if too many requests are made. If Names is called more often than the API will allow an ErrTooManyNameRequests error will be returned.

type NameRequester

type NameRequester interface {
	Names() ([]Name, error)
}

type Server

type Server struct {
	// Port is the port where the server will listen.
	Port int32
	// JokeClient requests new jokes using a customized name, if given.
	JokeClient *JokeClient
	// Names is a buffered channel where names will be retrieved from.  The server expects for this
	// to be populated ahead of time by another thread.  We are basically using this as a queue, but the
	// implementation is more simple and more easily supports handling timeouts.
	Names chan Name
}

func (*Server) GetCustomJoke

func (s *Server) GetCustomJoke(w http.ResponseWriter, req *http.Request)

func (*Server) ListenAndServe

func (s *Server) ListenAndServe() error

Directories

Path Synopsis
cmd
srv

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL