http.go 2.57 KB
package term

import (
	termbox "github.com/nsf/termbox-go"
	"ngrok/client/mvc"
	"ngrok/log"
	"ngrok/proto"
	"ngrok/util"
	"unicode/utf8"
)

const (
	size          = 10
	pathMaxLength = 25
)

type HttpView struct {
	log.Logger
	*area

	httpProto    *proto.Http
	HttpRequests *util.Ring
	shutdown     chan int
	termView     *TermView
}

func colorFor(status string) termbox.Attribute {
	switch status[0] {
	case '3':
		return termbox.ColorCyan
	case '4':
		return termbox.ColorYellow
	case '5':
		return termbox.ColorRed
	default:
	}
	return termbox.ColorWhite
}

func newTermHttpView(ctl mvc.Controller, termView *TermView, proto *proto.Http, x, y int) *HttpView {
	v := &HttpView{
		httpProto:    proto,
		HttpRequests: util.NewRing(size),
		area:         NewArea(x, y, 70, size+5),
		shutdown:     make(chan int),
		termView:     termView,
		Logger:       log.NewPrefixLogger("view", "term", "http"),
	}
	ctl.Go(v.Run)
	return v
}

func (v *HttpView) Run() {
	updates := v.httpProto.Txns.Reg()

	for {
		select {
		case txn := <-updates:
			v.Debug("Got HTTP update")
			if txn.(*proto.HttpTxn).Resp == nil {
				v.HttpRequests.Add(txn)
			}
			v.Render()
		}
	}
}

func (v *HttpView) Render() {
	v.Clear()
	v.Printf(0, 0, "HTTP Requests")
	v.Printf(0, 1, "-------------")
	for i, obj := range v.HttpRequests.Slice() {
		txn := obj.(*proto.HttpTxn)
		path := truncatePath(txn.Req.URL.Path)
		v.Printf(0, 3+i, "%s %v", txn.Req.Method, path)
		if txn.Resp != nil {
			v.APrintf(colorFor(txn.Resp.Status), 30, 3+i, "%s", txn.Resp.Status)
		}
	}
	v.termView.Flush()
}

func (v *HttpView) Shutdown() {
	close(v.shutdown)
}

func truncatePath(path string) string {
	// Truncate all long strings based on rune count
	if utf8.RuneCountInString(path) > pathMaxLength {
		path = string([]rune(path)[:pathMaxLength])
	}

	// By this point, len(path) should be < pathMaxLength if we're dealing with single-byte runes.
	// Otherwise, we have a multi-byte string and need to calculate the size of each rune and
	// truncate manually.
	//
	// This is a workaround for a bug in termbox-go. Remove it when this issue is fixed:
	// https://github.com/nsf/termbox-go/pull/21
	if len(path) > pathMaxLength {
		out := make([]byte, pathMaxLength, pathMaxLength)
		length := 0
		for {
			r, size := utf8.DecodeRuneInString(path[length:])
			if r == utf8.RuneError && size == 1 {
				break
			}

			// utf8.EncodeRune expects there to be enough room to store the full size of the rune
			if length+size <= pathMaxLength {
				utf8.EncodeRune(out[length:], r)
				length += size
			} else {
				break
			}
		}
		path = string(out[:length])
	}
	return path
}