Compare commits

..

6 Commits

Author SHA1 Message Date
stefanprodan
6c8a85a5ab Release v1.8.0 2019-08-05 10:00:23 +03:00
Stefan Prodan
c9dc78f29c Merge pull request #22 from imduffy15/master
Add delayed chunk endpoint
2019-08-05 09:20:27 +03:00
Ian Duffy
6a9b0253ac Update README.md 2019-08-04 10:06:20 +01:00
Ian Duffy
198211e20b Add delayed chunk endpoint
Adds an endpoint that does chunk based encoding. The endpoint just stalls
and eventually returns the stall time.

Similar to the delay endpoint but in a chunked maner.

Fixed up the metrics interceptor to wrap ResponseWriter correctly too.
2019-08-04 00:34:12 +01:00
stefanprodan
e1ca9e227d Remove the middle htlm section 2019-07-26 01:15:47 +03:00
stefanprodan
4fc593f42c Bump version to 1.7.1 2019-07-26 01:02:01 +03:00
9 changed files with 351 additions and 82 deletions

View File

@@ -38,6 +38,7 @@ Web API:
* `POST /store` writes the posted content to disk at /data/hash and returns the SHA1 hash of the content
* `GET /store/{hash}` returns the content of the file /data/hash if exists
* `GET /ws/echo` echos content via websockets `podcli ws ws://localhost:9898/ws/echo`
* `GET /chunked/{seconds}` uses `transfer-encoding` type `chunked` to give a partial response and then waits for the specified period
### Guides

View File

@@ -1,6 +1,6 @@
apiVersion: v1
version: 1.6.0
appVersion: 1.6.1
version: 1.7.0
appVersion: 1.7.1
name: podinfo
engine: gotpl
description: Podinfo Helm chart for Kubernetes

View File

@@ -12,7 +12,7 @@ faults:
image:
repository: quay.io/stefanprodan/podinfo
tag: 1.6.1
tag: 1.7.1
pullPolicy: IfNotPresent
service:

36
pkg/api/chunked.go Normal file
View File

@@ -0,0 +1,36 @@
package api
import (
"math/rand"
"net/http"
"strconv"
"time"
"github.com/gorilla/mux"
)
func (s *Server) chunkedHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
delay, err := strconv.Atoi(vars["wait"])
if err != nil {
delay = rand.Intn(int(s.config.HttpServerTimeout*time.Second)-10) + 10
}
flusher, ok := w.(http.Flusher)
if !ok {
s.ErrorResponse(w, r, "Streaming unsupported!", http.StatusInternalServerError)
return
}
w.Header().Set("Connection", "Keep-Alive")
w.Header().Set("Transfer-Encoding", "chunked")
w.Header().Set("X-Content-Type-Options", "nosniff")
flusher.Flush()
time.Sleep(time.Duration(delay) * time.Second)
s.JSONResponse(w, r, map[string]int{"delay": delay})
flusher.Flush()
}

35
pkg/api/chunked_test.go Normal file
View File

@@ -0,0 +1,35 @@
package api
import (
"net/http"
"net/http/httptest"
"regexp"
"testing"
)
func TestChunkedHandler(t *testing.T) {
req, err := http.NewRequest("GET", "/chunked/0", nil)
if err != nil {
t.Fatal(err)
}
rr := httptest.NewRecorder()
srv := NewMockServer()
srv.router.HandleFunc("/chunked/{wait}", srv.chunkedHandler)
srv.router.ServeHTTP(rr, req)
// Check the status code is what we expect.
if status := rr.Code; status != http.StatusOK {
t.Errorf("handler returned wrong status code: got %v want %v",
status, http.StatusOK)
}
// Check the response body is what we expect.
expected := ".*delay.*0.*"
r := regexp.MustCompile(expected)
if !r.MatchString(rr.Body.String()) {
t.Fatalf("handler returned unexpected body:\ngot \n%v \nwant \n%s",
rr.Body.String(), expected)
}
}

View File

@@ -3,6 +3,7 @@ package api
import (
"bufio"
"fmt"
"io"
"net"
"net/http"
"regexp"
@@ -51,7 +52,7 @@ func (p *PrometheusMiddleware) Handler(next http.Handler) http.Handler {
begin := time.Now()
interceptor := &interceptor{ResponseWriter: w, statusCode: http.StatusOK}
path := p.getRouteName(r)
next.ServeHTTP(interceptor, r)
next.ServeHTTP(interceptor.wrappedResponseWriter(), r)
var (
status = strconv.Itoa(interceptor.statusCode)
took = time.Since(begin)
@@ -94,6 +95,14 @@ type interceptor struct {
recorded bool
}
func (i *interceptor) Hijack() (net.Conn, *bufio.ReadWriter, error) {
hj, ok := i.ResponseWriter.(http.Hijacker)
if !ok {
return nil, nil, fmt.Errorf("interceptor: can't cast parent ResponseWriter to Hijacker")
}
return hj.Hijack()
}
func (i *interceptor) WriteHeader(code int) {
if !i.recorded {
i.statusCode = code
@@ -102,10 +111,262 @@ func (i *interceptor) WriteHeader(code int) {
i.ResponseWriter.WriteHeader(code)
}
func (i *interceptor) Hijack() (net.Conn, *bufio.ReadWriter, error) {
hj, ok := i.ResponseWriter.(http.Hijacker)
if !ok {
return nil, nil, fmt.Errorf("interceptor: can't cast parent ResponseWriter to Hijacker")
// Returns a wrapped http.ResponseWriter that implements the same optional interfaces
// that the underlying ResponseWriter has.
// Handle every possible combination so that code that checks for the existence of each
// optional interface functions properly.
// Based on https://github.com/felixge/httpsnoop/blob/eadd4fad6aac69ae62379194fe0219f3dbc80fd3/wrap_generated_gteq_1.8.go#L66
func (i *interceptor) wrappedResponseWriter() http.ResponseWriter {
closeNotifier, isCloseNotifier := i.ResponseWriter.(http.CloseNotifier)
flush, isFlusher := i.ResponseWriter.(http.Flusher)
hijack, isHijacker := i.ResponseWriter.(http.Hijacker)
push, isPusher := i.ResponseWriter.(http.Pusher)
readFrom, isReaderFrom := i.ResponseWriter.(io.ReaderFrom)
switch {
case !isCloseNotifier && !isFlusher && !isHijacker && !isPusher && !isReaderFrom:
return struct {
http.ResponseWriter
}{i}
case isCloseNotifier && !isFlusher && !isHijacker && !isPusher && !isReaderFrom:
return struct {
http.ResponseWriter
http.CloseNotifier
}{i, closeNotifier}
case !isCloseNotifier && isFlusher && !isHijacker && !isPusher && !isReaderFrom:
return struct {
http.ResponseWriter
http.Flusher
}{i, flush}
case !isCloseNotifier && !isFlusher && isHijacker && !isPusher && !isReaderFrom:
return struct {
http.ResponseWriter
http.Hijacker
}{i, hijack}
case !isCloseNotifier && !isFlusher && !isHijacker && isPusher && !isReaderFrom:
return struct {
http.ResponseWriter
http.Pusher
}{i, push}
case !isCloseNotifier && !isFlusher && !isHijacker && !isPusher && isReaderFrom:
return struct {
http.ResponseWriter
io.ReaderFrom
}{i, readFrom}
case isCloseNotifier && isFlusher && !isHijacker && !isPusher && !isReaderFrom:
return struct {
http.ResponseWriter
http.CloseNotifier
http.Flusher
}{i, closeNotifier, flush}
case isCloseNotifier && !isFlusher && isHijacker && !isPusher && !isReaderFrom:
return struct {
http.ResponseWriter
http.CloseNotifier
http.Hijacker
}{i, closeNotifier, hijack}
case isCloseNotifier && !isFlusher && !isHijacker && isPusher && !isReaderFrom:
return struct {
http.ResponseWriter
http.CloseNotifier
http.Pusher
}{i, closeNotifier, push}
case isCloseNotifier && !isFlusher && !isHijacker && !isPusher && isReaderFrom:
return struct {
http.ResponseWriter
http.CloseNotifier
io.ReaderFrom
}{i, closeNotifier, readFrom}
case !isCloseNotifier && isFlusher && isHijacker && !isPusher && !isReaderFrom:
return struct {
http.ResponseWriter
http.Flusher
http.Hijacker
}{i, flush, hijack}
case !isCloseNotifier && isFlusher && !isHijacker && isPusher && !isReaderFrom:
return struct {
http.ResponseWriter
http.Flusher
http.Pusher
}{i, flush, push}
case !isCloseNotifier && isFlusher && !isHijacker && !isPusher && isReaderFrom:
return struct {
http.ResponseWriter
http.Flusher
io.ReaderFrom
}{i, flush, readFrom}
case !isCloseNotifier && !isFlusher && isHijacker && isPusher && !isReaderFrom:
return struct {
http.ResponseWriter
http.Hijacker
http.Pusher
}{i, hijack, push}
case !isCloseNotifier && !isFlusher && isHijacker && !isPusher && isReaderFrom:
return struct {
http.ResponseWriter
http.Hijacker
io.ReaderFrom
}{i, hijack, readFrom}
case !isCloseNotifier && !isFlusher && !isHijacker && isPusher && isReaderFrom:
return struct {
http.ResponseWriter
http.Pusher
io.ReaderFrom
}{i, push, readFrom}
case isCloseNotifier && isFlusher && isHijacker && !isPusher && !isReaderFrom:
return struct {
http.ResponseWriter
http.CloseNotifier
http.Flusher
http.Hijacker
}{i, closeNotifier, flush, hijack}
case isCloseNotifier && isFlusher && !isHijacker && isPusher && !isReaderFrom:
return struct {
http.ResponseWriter
http.CloseNotifier
http.Flusher
http.Pusher
}{i, closeNotifier, flush, push}
case isCloseNotifier && isFlusher && !isHijacker && !isPusher && isReaderFrom:
return struct {
http.ResponseWriter
http.CloseNotifier
http.Flusher
io.ReaderFrom
}{i, closeNotifier, flush, readFrom}
case isCloseNotifier && !isFlusher && isHijacker && isPusher && !isReaderFrom:
return struct {
http.ResponseWriter
http.CloseNotifier
http.Hijacker
http.Pusher
}{i, closeNotifier, hijack, push}
case isCloseNotifier && !isFlusher && isHijacker && !isPusher && isReaderFrom:
return struct {
http.ResponseWriter
http.CloseNotifier
http.Hijacker
io.ReaderFrom
}{i, closeNotifier, hijack, readFrom}
case isCloseNotifier && !isFlusher && !isHijacker && isPusher && isReaderFrom:
return struct {
http.ResponseWriter
http.CloseNotifier
http.Pusher
io.ReaderFrom
}{i, closeNotifier, push, readFrom}
case !isCloseNotifier && isFlusher && isHijacker && isPusher && !isReaderFrom:
return struct {
http.ResponseWriter
http.Flusher
http.Hijacker
http.Pusher
}{i, flush, hijack, push}
case !isCloseNotifier && isFlusher && isHijacker && !isPusher && isReaderFrom:
return struct {
http.ResponseWriter
http.Flusher
http.Hijacker
io.ReaderFrom
}{i, flush, hijack, readFrom}
case !isCloseNotifier && isFlusher && !isHijacker && isPusher && isReaderFrom:
return struct {
http.ResponseWriter
http.Flusher
http.Pusher
io.ReaderFrom
}{i, flush, push, readFrom}
case !isCloseNotifier && !isFlusher && isHijacker && isPusher && isReaderFrom:
return struct {
http.ResponseWriter
http.Hijacker
http.Pusher
io.ReaderFrom
}{i, hijack, push, readFrom}
case isCloseNotifier && isFlusher && isHijacker && isPusher && !isReaderFrom:
return struct {
http.ResponseWriter
http.CloseNotifier
http.Flusher
http.Hijacker
http.Pusher
}{i, closeNotifier, flush, hijack, push}
case isCloseNotifier && isFlusher && isHijacker && !isPusher && isReaderFrom:
return struct {
http.ResponseWriter
http.CloseNotifier
http.Flusher
http.Hijacker
io.ReaderFrom
}{i, closeNotifier, flush, hijack, readFrom}
case isCloseNotifier && isFlusher && !isHijacker && isPusher && isReaderFrom:
return struct {
http.ResponseWriter
http.CloseNotifier
http.Flusher
http.Pusher
io.ReaderFrom
}{i, closeNotifier, flush, push, readFrom}
case isCloseNotifier && !isFlusher && isHijacker && isPusher && isReaderFrom:
return struct {
http.ResponseWriter
http.CloseNotifier
http.Hijacker
http.Pusher
io.ReaderFrom
}{i, closeNotifier, hijack, push, readFrom}
case !isCloseNotifier && isFlusher && isHijacker && isPusher && isReaderFrom:
return struct {
http.ResponseWriter
http.Flusher
http.Hijacker
http.Pusher
io.ReaderFrom
}{i, flush, hijack, push, readFrom}
case isCloseNotifier && isFlusher && isHijacker && isPusher && isReaderFrom:
return struct {
http.ResponseWriter
http.CloseNotifier
http.Flusher
http.Hijacker
http.Pusher
io.ReaderFrom
}{i, closeNotifier, flush, hijack, push, readFrom}
default:
return struct {
http.ResponseWriter
}{i}
}
return hj.Hijack()
}

View File

@@ -3,17 +3,18 @@ package api
import (
"context"
"fmt"
"github.com/gorilla/mux"
"github.com/prometheus/client_golang/prometheus/promhttp"
"github.com/spf13/viper"
"github.com/stefanprodan/k8s-podinfo/pkg/fscache"
"go.uber.org/zap"
"net/http"
_ "net/http/pprof"
"os"
"strings"
"sync/atomic"
"time"
"github.com/gorilla/mux"
"github.com/prometheus/client_golang/prometheus/promhttp"
"github.com/spf13/viper"
"github.com/stefanprodan/k8s-podinfo/pkg/fscache"
"go.uber.org/zap"
)
var (
@@ -80,6 +81,8 @@ func (s *Server) registerHandlers() {
s.router.HandleFunc("/api/info", s.infoHandler).Methods("GET")
s.router.HandleFunc("/api/echo", s.echoHandler).Methods("POST")
s.router.HandleFunc("/ws/echo", s.echoWsHandler)
s.router.HandleFunc("/chunked", s.chunkedHandler)
s.router.HandleFunc("/chunked/{wait:[0-9]+}", s.chunkedHandler)
}
func (s *Server) registerMiddlewares() {

View File

@@ -1,4 +1,4 @@
package version
var VERSION = "1.7.0"
var VERSION = "1.8.0"
var REVISION = "unknown"

View File

@@ -44,73 +44,6 @@
</v-layout>
</v-parallax>
</section>
<section>
<v-layout
column
wrap
class="my-5"
align-center
>
<v-flex xs12 sm4 class="my-3">
<div class="text-xs-center">
<h2 class="headline">The best way to start developing</h2>
<span class="subheading">
stateless microservices with Go for Kubernetes
</span>
</div>
</v-flex>
<v-flex xs12>
<v-container grid-list-xl>
<v-layout row wrap align-center>
<v-flex xs12 md4>
<v-card class="elevation-0 transparent">
<v-card-text class="text-xs-center">
<v-icon x-large class="blue--text text--lighten-2">cloud</v-icon>
</v-card-text>
<v-card-title primary-title class="layout justify-center">
<div class="headline text-xs-center">Cloud Native</div>
</v-card-title>
<v-card-text>
Distributed as a Helm chart. Builtin Kubernetes health checks (readiness and liveness).
Graceful shutdown on interrupt signals.
</v-card-text>
</v-card>
</v-flex>
<v-flex xs12 md4>
<v-card class="elevation-0 transparent">
<v-card-text class="text-xs-center">
<v-icon x-large class="blue--text text--lighten-2">graphic_eq</v-icon>
</v-card-text>
<v-card-title primary-title class="layout justify-center">
<div class="headline">Observability</div>
</v-card-title>
<v-card-text>
Instrumentation with Prometheus (RED method).
Structured logging with zap and Fluentd.
Tracing with Jaeger and Istio.
</v-card-text>
</v-card>
</v-flex>
<v-flex xs12 md4>
<v-card class="elevation-0 transparent">
<v-card-text class="text-xs-center">
<v-icon x-large class="blue--text text--lighten-2">flash_on</v-icon>
</v-card-text>
<v-card-title primary-title class="layout justify-center">
<div class="headline text-xs-center">Release automation</div>
</v-card-title>
<v-card-text>
Multi-platform Docker image AMD64 and ARMv7.
CI/CD powered by: TravisCI CircleCI Quay.io Google Cloud Container Builder Skaffold Weave Flux.
</v-card-text>
</v-card>
</v-flex>
</v-layout>
</v-container>
</v-flex>
</v-layout>
</section>
<v-footer class="blue darken-2">
<v-layout row wrap align-center>
<v-flex xs12>