1
0
Fork 0
forked from forgejo/forgejo

Fix recovery middleware to render gitea style page. (#13857)

* Some changes to fix recovery

* Move Recovery to middlewares

* Remove trace code

* Fix lint

* add session middleware and remove dependent on macaron for sso

* Fix panic 500 page rendering

* Fix bugs

* Fix fmt

* Fix vendor

* recover unnecessary change

* Fix lint and addd some comments about the copied codes.

* Use util.StatDir instead of com.StatDir

Co-authored-by: 6543 <6543@obermui.de>
This commit is contained in:
Lunny Xiao 2021-01-05 21:05:40 +08:00 committed by GitHub
parent 126c9331d6
commit 15a475b7db
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
75 changed files with 5233 additions and 307 deletions

View file

@ -4,3 +4,4 @@
*.swp
/gocache/gocache
c.out
.idea

View file

@ -17,11 +17,7 @@ type CollectionsFilter struct {
ScopeId uint32
}
type nonStreamIdNonResumeScopeMeta struct {
ScopeId string `json:"scope"`
}
type nonStreamIdResumeScopeMeta struct {
type nonStreamIdNonCollectionsMeta struct {
ManifestId string `json:"uid"`
}
@ -29,7 +25,7 @@ type nonStreamIdNonResumeCollectionsMeta struct {
CollectionsList []string `json:"collections"`
}
type nonStreamIdResumeCollectionsMeta struct {
type nonStreamIdCollectionsMeta struct {
ManifestId string `json:"uid"`
CollectionsList []string `json:"collections"`
}
@ -99,10 +95,19 @@ func (c *CollectionsFilter) ToStreamReqBody() ([]byte, error) {
case false:
switch c.UseManifestUid {
case true:
filter := &nonStreamIdResumeScopeMeta{
ManifestId: fmt.Sprintf("%x", c.ManifestUid),
switch len(c.CollectionsList) > 0 {
case true:
filter := &nonStreamIdCollectionsMeta{
ManifestId: fmt.Sprintf("%x", c.ManifestUid),
CollectionsList: c.outputCollectionsFilterColList(),
}
output = *filter
case false:
filter := &nonStreamIdNonCollectionsMeta{
ManifestId: fmt.Sprintf("%x", c.ManifestUid),
}
output = *filter
}
output = *filter
case false:
switch len(c.CollectionsList) > 0 {
case true:
@ -111,7 +116,7 @@ func (c *CollectionsFilter) ToStreamReqBody() ([]byte, error) {
}
output = *filter
case false:
output = nonStreamIdNonResumeScopeMeta{ScopeId: c.outputScopeId()}
return nil, fmt.Errorf("Specifying scopeID must require the use of streamId")
}
}
}

View file

@ -375,6 +375,25 @@ func (c *Client) setCollection(req *gomemcached.MCRequest, context ...*ClientCon
return nil
}
// Sets collection info in extras
func (c *Client) setExtrasCollection(req *gomemcached.MCRequest, context ...*ClientContext) error {
collectionId := uint32(0)
if len(context) > 0 {
collectionId = context[0].CollId
}
// if the optional collection is specified, it must be default for clients that haven't turned on collections
if atomic.LoadUint32(&c.collectionsEnabled) == 0 {
if collectionId != 0 {
return fmt.Errorf("Client does not use collections but a collection was specified")
}
} else {
req.Extras = make([]byte, 4)
binary.BigEndian.PutUint32(req.Extras, collectionId)
}
return nil
}
func (c *Client) setVbSeqnoContext(req *gomemcached.MCRequest, context ...*ClientContext) error {
if len(context) == 0 || req == nil {
return nil
@ -516,9 +535,14 @@ func (c *Client) Del(vb uint16, key string, context ...*ClientContext) (*gomemca
// Get a random document
func (c *Client) GetRandomDoc(context ...*ClientContext) (*gomemcached.MCResponse, error) {
return c.Send(&gomemcached.MCRequest{
req := &gomemcached.MCRequest{
Opcode: 0xB6,
})
}
err := c.setExtrasCollection(req, context...)
if err != nil {
return nil, err
}
return c.Send(req)
}
// AuthList lists SASL auth mechanisms.

View file

@ -83,7 +83,8 @@ type UprEvent struct {
SystemEvent SystemEventType // Only valid if IsSystemEvent() is true
SysEventVersion uint8 // Based on the version, the way Extra bytes is parsed is different
ValueLen int // Cache it to avoid len() calls for performance
CollectionId uint64 // Valid if Collection is in use
CollectionId uint32 // Valid if Collection is in use
StreamId *uint16 // Nil if not in use
}
// FailoverLog containing vvuid and sequnce number
@ -103,7 +104,7 @@ func makeUprEvent(rq gomemcached.MCRequest, stream *UprStream, bytesReceivedFrom
DataType: rq.DataType,
ValueLen: len(rq.Body),
SystemEvent: InvalidSysEvent,
CollectionId: math.MaxUint64,
CollectionId: math.MaxUint32,
}
event.PopulateFieldsBasedOnStreamType(rq, stream.StreamType)
@ -153,6 +154,8 @@ func makeUprEvent(rq gomemcached.MCRequest, stream *UprStream, bytesReceivedFrom
event.PopulateEvent(rq.Extras)
} else if event.IsSeqnoAdv() {
event.PopulateSeqnoAdv(rq.Extras)
} else if event.IsOsoSnapshot() {
event.PopulateOso(rq.Extras)
}
return event
@ -160,6 +163,15 @@ func makeUprEvent(rq gomemcached.MCRequest, stream *UprStream, bytesReceivedFrom
func (event *UprEvent) PopulateFieldsBasedOnStreamType(rq gomemcached.MCRequest, streamType DcpStreamType) {
switch streamType {
case CollectionsStreamId:
for _, extra := range rq.FramingExtras {
streamId, streamIdErr := extra.GetStreamId()
if streamIdErr == nil {
event.StreamId = &streamId
}
}
// After parsing streamID, still need to populate regular collectionID
fallthrough
case CollectionsNonStreamId:
switch rq.Opcode {
// Only these will have CID encoded within the key
@ -167,15 +179,12 @@ func (event *UprEvent) PopulateFieldsBasedOnStreamType(rq gomemcached.MCRequest,
gomemcached.UPR_DELETION,
gomemcached.UPR_EXPIRATION:
uleb128 := Uleb128(rq.Key)
result, bytesShifted := uleb128.ToUint64(rq.Keylen)
result, bytesShifted := uleb128.ToUint32(rq.Keylen)
event.CollectionId = result
event.Key = rq.Key[bytesShifted:]
default:
event.Key = rq.Key
}
case CollectionsStreamId:
// TODO - not implemented
fallthrough
case NonCollectionStream:
// Let default behavior be legacy stream type
fallthrough
@ -208,6 +217,10 @@ func (event *UprEvent) IsSeqnoAdv() bool {
return event.Opcode == gomemcached.DCP_SEQNO_ADV
}
func (event *UprEvent) IsOsoSnapshot() bool {
return event.Opcode == gomemcached.DCP_OSO_SNAPSHOT
}
func (event *UprEvent) PopulateEvent(extras []byte) {
if len(extras) < dcpSystemEventExtraLen {
// Wrong length, don't parse
@ -229,6 +242,14 @@ func (event *UprEvent) PopulateSeqnoAdv(extras []byte) {
event.Seqno = binary.BigEndian.Uint64(extras[:8])
}
func (event *UprEvent) PopulateOso(extras []byte) {
if len(extras) < dcpOsoExtraLen {
// Wrong length, don't parse
return
}
event.Flags = binary.BigEndian.Uint32(extras[:4])
}
func (event *UprEvent) GetSystemEventName() (string, error) {
switch event.SystemEvent {
case CollectionCreate:
@ -345,15 +366,32 @@ func (event *UprEvent) GetMaxTTL() (uint32, error) {
}
}
// Only if error is nil:
// Returns true if event states oso begins
// Return false if event states oso ends
func (event *UprEvent) GetOsoBegin() (bool, error) {
if !event.IsOsoSnapshot() {
return false, ErrorInvalidOp
}
if event.Flags == 1 {
return true, nil
} else if event.Flags == 2 {
return false, nil
} else {
return false, ErrorInvalidOp
}
}
type Uleb128 []byte
func (u Uleb128) ToUint64(cachedLen int) (result uint64, bytesShifted int) {
func (u Uleb128) ToUint32(cachedLen int) (result uint32, bytesShifted int) {
var shift uint = 0
for curByte := 0; curByte < cachedLen; curByte++ {
oneByte := u[curByte]
last7Bits := 0x7f & oneByte
result |= uint64(last7Bits) << shift
result |= uint32(last7Bits) << shift
bytesShifted++
if oneByte&0x80 == 0 {
break

View file

@ -26,6 +26,7 @@ const opaqueOpen = 0xBEAF0001
const opaqueFailover = 0xDEADBEEF
const opaqueGetSeqno = 0xDEADBEEF
const uprDefaultNoopInterval = 120
const dcpOsoExtraLen = 4
// Counter on top of opaqueOpen that others can draw from for open and control msgs
var opaqueOpenCtrlWell uint32 = opaqueOpen
@ -117,6 +118,7 @@ type UprFeatures struct {
DcpPriority PriorityType
EnableExpiry bool
EnableStreamId bool
EnableOso bool
}
/**
@ -601,6 +603,20 @@ func (feed *UprFeed) uprOpen(name string, sequence uint32, bufSize uint32, featu
activatedFeatures.EnableStreamId = true
}
if features.EnableOso {
rq := &gomemcached.MCRequest{
Opcode: gomemcached.UPR_CONTROL,
Key: []byte("enable_out_of_order_snapshots"),
Body: []byte("true"),
Opaque: getUprOpenCtrlOpaque(),
}
err = sendMcRequestSync(feed.conn, rq)
if err != nil {
return
}
activatedFeatures.EnableOso = true
}
// everything is ok so far, set upr feed to open state
feed.activatedFeatures = activatedFeatures
feed.setOpen()
@ -976,6 +992,12 @@ loop:
break loop
}
event = makeUprEvent(pkt, stream, bytes)
case gomemcached.DCP_OSO_SNAPSHOT:
if stream == nil {
logging.Infof("Stream not found for vb %d: %#v", vb, pkt)
break loop
}
event = makeUprEvent(pkt, stream, bytes)
default:
logging.Infof("Recived an unknown response for vbucket %d", vb)
}

3
vendor/github.com/couchbase/gomemcached/go.mod generated vendored Normal file
View file

@ -0,0 +1,3 @@
module github.com/couchbase/gomemcached
go 1.13

View file

@ -104,6 +104,7 @@ const (
DCP_SYSTEM_EVENT = CommandCode(0x5f) // A system event has occurred
DCP_SEQNO_ADV = CommandCode(0x64) // Sent when the vb seqno has advanced due to an unsubscribed event
DCP_OSO_SNAPSHOT = CommandCode(0x65) // Marks the begin and end of out-of-sequence-number stream
)
// command codes that are counted toward DCP control buffer
@ -117,6 +118,7 @@ var BufferedCommandCodeMap = map[CommandCode]bool{
UPR_EXPIRATION: true,
DCP_SYSTEM_EVENT: true,
DCP_SEQNO_ADV: true,
DCP_OSO_SNAPSHOT: true,
}
// Status field for memcached response.
@ -156,6 +158,9 @@ const (
SUBDOC_PATH_NOT_FOUND = Status(0xc0)
SUBDOC_BAD_MULTI = Status(0xcc)
SUBDOC_MULTI_PATH_FAILURE_DELETED = Status(0xd3)
// Not a Memcached status
UNKNOWN_STATUS = Status(0xffff)
)
// for log redaction
@ -174,6 +179,10 @@ var isFatal = map[Status]bool{
EACCESS: true,
ENOMEM: true,
NOT_SUPPORTED: true,
// consider statuses coming from outside couchbase (eg OS errors) as fatal for the connection
// as there might be unread data left over on the wire
UNKNOWN_STATUS: true,
}
// the producer/consumer bit in dcp flags

View file

@ -38,7 +38,7 @@ func (res *MCResponse) Error() string {
}
func errStatus(e error) Status {
status := Status(0xffff)
status := UNKNOWN_STATUS
if res, ok := e.(*MCResponse); ok {
status = res.Status
}

View file

@ -1,20 +0,0 @@
language: go
go:
- 1.10.x
- 1.11.x
- 1.12.x
- 1.13.x
- 1.14.x
script:
- go get -d -t ./...
- go vet ./...
- go test ./...
- >
go_version=$(go version);
if [ ${go_version:13:4} = "1.12" ]; then
go get -u golang.org/x/tools/cmd/goimports;
goimports -d -e ./ | grep '.*' && { echo; echo "Aborting due to non-empty goimports output."; exit 1; } || :;
fi

View file

@ -1,5 +1,66 @@
# Changelog
## v1.5.1 (2020-12-06)
- Performance improvement: removing 1 allocation by foregoing context.WithValue, thank you @bouk for
your contribution (https://github.com/go-chi/chi/pull/555). Note: new benchmarks posted in README.
- `middleware.CleanPath`: new middleware that clean's request path of double slashes
- deprecate & remove `chi.ServerBaseContext` in favour of stdlib `http.Server#BaseContext`
- plus other tiny improvements, see full commit history below
- History of changes: see https://github.com/go-chi/chi/compare/v4.1.2...v1.5.1
## v1.5.0 (2020-11-12) - now with go.mod support
`chi` dates back to 2016 with it's original implementation as one of the first routers to adopt the newly introduced
context.Context api to the stdlib -- set out to design a router that is faster, more modular and simpler than anything
else out there -- while not introducing any custom handler types or dependencies. Today, `chi` still has zero dependencies,
and in many ways is future proofed from changes, given it's minimal nature. Between versions, chi's iterations have been very
incremental, with the architecture and api being the same today as it was originally designed in 2016. For this reason it
makes chi a pretty easy project to maintain, as well thanks to the many amazing community contributions over the years
to who all help make chi better (total of 86 contributors to date -- thanks all!).
Chi has been an labour of love, art and engineering, with the goals to offer beautiful ergonomics, flexibility, performance
and simplicity when building HTTP services with Go. I've strived to keep the router very minimal in surface area / code size,
and always improving the code wherever possible -- and as of today the `chi` package is just 1082 lines of code (not counting
middlewares, which are all optional). As well, I don't have the exact metrics, but from my analysis and email exchanges from
companies and developers, chi is used by thousands of projects around the world -- thank you all as there is no better form of
joy for me than to have art I had started be helpful and enjoyed by others. And of course I use chi in all of my own projects too :)
For me, the asthetics of chi's code and usage are very important. With the introduction of Go's module support
(which I'm a big fan of), chi's past versioning scheme choice to v2, v3 and v4 would mean I'd require the import path
of "github.com/go-chi/chi/v4", leading to the lengthy discussion at https://github.com/go-chi/chi/issues/462.
Haha, to some, you may be scratching your head why I've spent > 1 year stalling to adopt "/vXX" convention in the import
path -- which isn't horrible in general -- but for chi, I'm unable to accept it as I strive for perfection in it's API design,
aesthetics and simplicity. It just doesn't feel good to me given chi's simple nature -- I do not foresee a "v5" or "v6",
and upgrading between versions in the future will also be just incremental.
I do understand versioning is a part of the API design as well, which is why the solution for a while has been to "do nothing",
as Go supports both old and new import paths with/out go.mod. However, now that Go module support has had time to iron out kinks and
is adopted everywhere, it's time for chi to get with the times. Luckily, I've discovered a path forward that will make me happy,
while also not breaking anyone's app who adopted a prior versioning from tags in v2/v3/v4. I've made an experimental release of
v1.5.0 with go.mod silently, and tested it with new and old projects, to ensure the developer experience is preserved, and it's
largely unnoticed. Fortunately, Go's toolchain will check the tags of a repo and consider the "latest" tag the one with go.mod.
However, you can still request a specific older tag such as v4.1.2, and everything will "just work". But new users can just
`go get github.com/go-chi/chi` or `go get github.com/go-chi/chi@latest` and they will get the latest version which contains
go.mod support, which is v1.5.0+. `chi` will not change very much over the years, just like it hasn't changed much from 4 years ago.
Therefore, we will stay on v1.x from here on, starting from v1.5.0. Any breaking changes will bump a "minor" release and
backwards-compatible improvements/fixes will bump a "tiny" release.
For existing projects who want to upgrade to the latest go.mod version, run: `go get -u github.com/go-chi/chi@v1.5.0`,
which will get you on the go.mod version line (as Go's mod cache may still remember v4.x). Brand new systems can run
`go get -u github.com/go-chi/chi` or `go get -u github.com/go-chi/chi@latest` to install chi, which will install v1.5.0+
built with go.mod support.
My apologies to the developers who will disagree with the decisions above, but, hope you'll try it and see it's a very
minor request which is backwards compatible and won't break your existing installations.
Cheers all, happy coding!
---
## v4.1.2 (2020-06-02)
- fix that handles MethodNotAllowed with path variables, thank you @caseyhadden for your contribution
@ -23,7 +84,6 @@
- middleware.Recoverer: a bit prettier
- History of changes: see https://github.com/go-chi/chi/compare/v4.0.4...v4.1.0
## v4.0.4 (2020-03-24)
- middleware.Recoverer: new pretty stack trace printing (https://github.com/go-chi/chi/pull/496)

View file

@ -15,7 +15,8 @@ public API service, which in turn powers all of our client-side applications.
The key considerations of chi's design are: project structure, maintainability, standard http
handlers (stdlib-only), developer productivity, and deconstructing a large system into many small
parts. The core router `github.com/go-chi/chi` is quite small (less than 1000 LOC), but we've also
included some useful/optional subpackages: [middleware](/middleware), [render](https://github.com/go-chi/render) and [docgen](https://github.com/go-chi/docgen). We hope you enjoy it too!
included some useful/optional subpackages: [middleware](/middleware), [render](https://github.com/go-chi/render)
and [docgen](https://github.com/go-chi/docgen). We hope you enjoy it too!
## Install
@ -27,10 +28,11 @@ included some useful/optional subpackages: [middleware](/middleware), [render](h
* **Lightweight** - cloc'd in ~1000 LOC for the chi router
* **Fast** - yes, see [benchmarks](#benchmarks)
* **100% compatible with net/http** - use any http or middleware pkg in the ecosystem that is also compatible with `net/http`
* **Designed for modular/composable APIs** - middlewares, inline middlewares, route groups and subrouter mounting
* **Designed for modular/composable APIs** - middlewares, inline middlewares, route groups and sub-router mounting
* **Context control** - built on new `context` package, providing value chaining, cancellations and timeouts
* **Robust** - in production at Pressly, CloudFlare, Heroku, 99Designs, and many others (see [discussion](https://github.com/go-chi/chi/issues/91))
* **Doc generation** - `docgen` auto-generates routing documentation from your source to JSON or Markdown
* **Go.mod support** - v1.x of chi (starting from v1.5.0), now has go.mod support (see [CHANGELOG](https://github.com/go-chi/chi/blob/master/CHANGELOG.md#v150-2020-11-12---now-with-gomod-support))
* **No external dependencies** - plain ol' Go stdlib + net/http
@ -334,9 +336,12 @@ with `net/http` can be used with chi's mux.
----------------------------------------------------------------------------------------------------
| chi/middleware Handler | description |
| :--------------------- | :---------------------------------------------------------------------- |
| [AllowContentEncoding] | Enforces a whitelist of request Content-Encoding headers |
| [AllowContentType] | Explicit whitelist of accepted request Content-Types |
| [BasicAuth] | Basic HTTP authentication |
| [Compress] | Gzip compression for clients that accept compressed responses |
| [ContentCharset] | Ensure charset for Content-Type request headers |
| [CleanPath] | Clean double slashes from request path |
| [GetHead] | Automatically route undefined HEAD requests to GET handlers |
| [Heartbeat] | Monitoring endpoint to check the servers pulse |
| [Logger] | Logs the start and end of each request with the elapsed processing time |
@ -346,6 +351,7 @@ with `net/http` can be used with chi's mux.
| [Recoverer] | Gracefully absorb panics and prints the stack trace |
| [RequestID] | Injects a request ID into the context of each request |
| [RedirectSlashes] | Redirect slashes on routing paths |
| [RouteHeaders] | Route handling for request headers |
| [SetHeader] | Short-hand middleware to set a response header key/value |
| [StripSlashes] | Strip slashes on routing paths |
| [Throttle] | Puts a ceiling on the number of concurrent requests |
@ -359,20 +365,19 @@ with `net/http` can be used with chi's mux.
[BasicAuth]: https://pkg.go.dev/github.com/go-chi/chi/middleware#BasicAuth
[Compress]: https://pkg.go.dev/github.com/go-chi/chi/middleware#Compress
[ContentCharset]: https://pkg.go.dev/github.com/go-chi/chi/middleware#ContentCharset
[CleanPath]: https://pkg.go.dev/github.com/go-chi/chi/middleware#CleanPath
[GetHead]: https://pkg.go.dev/github.com/go-chi/chi/middleware#GetHead
[GetReqID]: https://pkg.go.dev/github.com/go-chi/chi/middleware#GetReqID
[Heartbeat]: https://pkg.go.dev/github.com/go-chi/chi/middleware#Heartbeat
[Logger]: https://pkg.go.dev/github.com/go-chi/chi/middleware#Logger
[New]: https://pkg.go.dev/github.com/go-chi/chi/middleware#New
[NextRequestID]: https://pkg.go.dev/github.com/go-chi/chi/middleware#NextRequestID
[NoCache]: https://pkg.go.dev/github.com/go-chi/chi/middleware#NoCache
[PrintPrettyStack]: https://pkg.go.dev/github.com/go-chi/chi/middleware#PrintPrettyStack
[Profiler]: https://pkg.go.dev/github.com/go-chi/chi/middleware#Profiler
[RealIP]: https://pkg.go.dev/github.com/go-chi/chi/middleware#RealIP
[Recoverer]: https://pkg.go.dev/github.com/go-chi/chi/middleware#Recoverer
[RedirectSlashes]: https://pkg.go.dev/github.com/go-chi/chi/middleware#RedirectSlashes
[RequestID]: https://pkg.go.dev/github.com/go-chi/chi/middleware#RequestID
[RequestLogger]: https://pkg.go.dev/github.com/go-chi/chi/middleware#RequestLogger
[RequestID]: https://pkg.go.dev/github.com/go-chi/chi/middleware#RequestID
[RouteHeaders]: https://pkg.go.dev/github.com/go-chi/chi/middleware#RouteHeaders
[SetHeader]: https://pkg.go.dev/github.com/go-chi/chi/middleware#SetHeader
[StripSlashes]: https://pkg.go.dev/github.com/go-chi/chi/middleware#StripSlashes
[Throttle]: https://pkg.go.dev/github.com/go-chi/chi/middleware#Throttle
@ -390,7 +395,6 @@ with `net/http` can be used with chi's mux.
[LogEntry]: https://pkg.go.dev/github.com/go-chi/chi/middleware#LogEntry
[LogFormatter]: https://pkg.go.dev/github.com/go-chi/chi/middleware#LogFormatter
[LoggerInterface]: https://pkg.go.dev/github.com/go-chi/chi/middleware#LoggerInterface
[Pattern]: https://pkg.go.dev/github.com/go-chi/chi/middleware#Pattern
[ThrottleOpts]: https://pkg.go.dev/github.com/go-chi/chi/middleware#ThrottleOpts
[WrapResponseWriter]: https://pkg.go.dev/github.com/go-chi/chi/middleware#WrapResponseWriter
@ -430,25 +434,25 @@ and..
The benchmark suite: https://github.com/pkieltyka/go-http-routing-benchmark
Results as of Jan 9, 2019 with Go 1.11.4 on Linux X1 Carbon laptop
Results as of Nov 29, 2020 with Go 1.15.5 on Linux AMD 3950x
```shell
BenchmarkChi_Param 3000000 475 ns/op 432 B/op 3 allocs/op
BenchmarkChi_Param5 2000000 696 ns/op 432 B/op 3 allocs/op
BenchmarkChi_Param20 1000000 1275 ns/op 432 B/op 3 allocs/op
BenchmarkChi_ParamWrite 3000000 505 ns/op 432 B/op 3 allocs/op
BenchmarkChi_GithubStatic 3000000 508 ns/op 432 B/op 3 allocs/op
BenchmarkChi_GithubParam 2000000 669 ns/op 432 B/op 3 allocs/op
BenchmarkChi_GithubAll 10000 134627 ns/op 87699 B/op 609 allocs/op
BenchmarkChi_GPlusStatic 3000000 402 ns/op 432 B/op 3 allocs/op
BenchmarkChi_GPlusParam 3000000 500 ns/op 432 B/op 3 allocs/op
BenchmarkChi_GPlus2Params 3000000 586 ns/op 432 B/op 3 allocs/op
BenchmarkChi_GPlusAll 200000 7237 ns/op 5616 B/op 39 allocs/op
BenchmarkChi_ParseStatic 3000000 408 ns/op 432 B/op 3 allocs/op
BenchmarkChi_ParseParam 3000000 488 ns/op 432 B/op 3 allocs/op
BenchmarkChi_Parse2Params 3000000 551 ns/op 432 B/op 3 allocs/op
BenchmarkChi_ParseAll 100000 13508 ns/op 11232 B/op 78 allocs/op
BenchmarkChi_StaticAll 20000 81933 ns/op 67826 B/op 471 allocs/op
BenchmarkChi_Param 3075895 384 ns/op 400 B/op 2 allocs/op
BenchmarkChi_Param5 2116603 566 ns/op 400 B/op 2 allocs/op
BenchmarkChi_Param20 964117 1227 ns/op 400 B/op 2 allocs/op
BenchmarkChi_ParamWrite 2863413 420 ns/op 400 B/op 2 allocs/op
BenchmarkChi_GithubStatic 3045488 395 ns/op 400 B/op 2 allocs/op
BenchmarkChi_GithubParam 2204115 540 ns/op 400 B/op 2 allocs/op
BenchmarkChi_GithubAll 10000 113811 ns/op 81203 B/op 406 allocs/op
BenchmarkChi_GPlusStatic 3337485 359 ns/op 400 B/op 2 allocs/op
BenchmarkChi_GPlusParam 2825853 423 ns/op 400 B/op 2 allocs/op
BenchmarkChi_GPlus2Params 2471697 483 ns/op 400 B/op 2 allocs/op
BenchmarkChi_GPlusAll 194220 5950 ns/op 5200 B/op 26 allocs/op
BenchmarkChi_ParseStatic 3365324 356 ns/op 400 B/op 2 allocs/op
BenchmarkChi_ParseParam 2976614 404 ns/op 400 B/op 2 allocs/op
BenchmarkChi_Parse2Params 2638084 439 ns/op 400 B/op 2 allocs/op
BenchmarkChi_ParseAll 109567 11295 ns/op 10400 B/op 52 allocs/op
BenchmarkChi_StaticAll 16846 71308 ns/op 62802 B/op 314 allocs/op
```
Comparison with other routers: https://gist.github.com/pkieltyka/123032f12052520aaccab752bd3e78cc
@ -459,6 +463,17 @@ on the duplicated (alloc'd) request and returns it the new request object. This
how setting context on a request in Go works.
## Go module support & note on chi's versioning
* Go.mod support means we reset our versioning starting from v1.5 (see [CHANGELOG](https://github.com/go-chi/chi/blob/master/CHANGELOG.md#v150-2020-11-12---now-with-gomod-support))
* All older tags are preserved, are backwards-compatible and will "just work" as they
* Brand new systems can run `go get -u github.com/go-chi/chi` as normal, or `go get -u github.com/go-chi/chi@latest`
to install chi, which will install v1.x+ built with go.mod support, starting from v1.5.0.
* For existing projects who want to upgrade to the latest go.mod version, run: `go get -u github.com/go-chi/chi@v1.5.0`,
which will get you on the go.mod version line (as Go's mod cache may still remember v4.x).
* Any breaking changes will bump a "minor" release and backwards-compatible improvements/fixes will bump a "tiny" release.
## Credits
* Carl Jackson for https://github.com/zenazn/goji

View file

@ -2,9 +2,9 @@ package chi
import (
"context"
"net"
"net/http"
"strings"
"time"
)
// URLParam returns the url parameter from a http.Request object.
@ -30,26 +30,6 @@ func RouteContext(ctx context.Context) *Context {
return val
}
// ServerBaseContext wraps an http.Handler to set the request context to the
// `baseCtx`.
func ServerBaseContext(baseCtx context.Context, h http.Handler) http.Handler {
fn := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
baseCtx := baseCtx
// Copy over default net/http server context keys
if v, ok := ctx.Value(http.ServerContextKey).(*http.Server); ok {
baseCtx = context.WithValue(baseCtx, http.ServerContextKey, v)
}
if v, ok := ctx.Value(http.LocalAddrContextKey).(net.Addr); ok {
baseCtx = context.WithValue(baseCtx, http.LocalAddrContextKey, v)
}
h.ServeHTTP(w, r.WithContext(baseCtx))
})
return fn
}
// NewRouteContext returns a new routing Context object.
func NewRouteContext() *Context {
return &Context{}
@ -92,6 +72,11 @@ type Context struct {
// methodNotAllowed hint
methodNotAllowed bool
// parentCtx is the parent of this one, for using Context as a
// context.Context directly. This is an optimization that saves
// 1 allocation.
parentCtx context.Context
}
// Reset a routing context to its initial state.
@ -107,6 +92,7 @@ func (x *Context) Reset() {
x.routeParams.Keys = x.routeParams.Keys[:0]
x.routeParams.Values = x.routeParams.Values[:0]
x.methodNotAllowed = false
x.parentCtx = nil
}
// URLParam returns the corresponding URL parameter value from the request
@ -160,6 +146,32 @@ func (s *RouteParams) Add(key, value string) {
s.Values = append(s.Values, value)
}
// directContext provides direct access to the routing *Context object,
// while implementing the context.Context interface, thereby allowing
// us to saving 1 allocation during routing.
type directContext Context
var _ context.Context = (*directContext)(nil)
func (d *directContext) Deadline() (deadline time.Time, ok bool) {
return d.parentCtx.Deadline()
}
func (d *directContext) Done() <-chan struct{} {
return d.parentCtx.Done()
}
func (d *directContext) Err() error {
return d.parentCtx.Err()
}
func (d *directContext) Value(key interface{}) interface{} {
if key == RouteCtxKey {
return (*Context)(d)
}
return d.parentCtx.Value(key)
}
// contextKey is a value for use with context.WithValue. It's used as
// a pointer so it fits in an interface{} without allocation. This technique
// for defining context keys was copied from Go 1.7's new use of context in net/http.

View file

@ -1,6 +1,7 @@
package middleware
import (
"crypto/subtle"
"fmt"
"net/http"
)
@ -16,7 +17,7 @@ func BasicAuth(realm string, creds map[string]string) func(next http.Handler) ht
}
credPass, credUserOk := creds[user]
if !credUserOk || pass != credPass {
if !credUserOk || subtle.ConstantTimeCompare([]byte(pass), []byte(credPass)) != 1 {
basicAuthFailed(w, realm)
return
}

28
vendor/github.com/go-chi/chi/middleware/clean_path.go generated vendored Normal file
View file

@ -0,0 +1,28 @@
package middleware
import (
"net/http"
"path"
"github.com/go-chi/chi"
)
// CleanPath middleware will clean out double slash mistakes from a user's request path.
// For example, if a user requests /users//1 or //users////1 will both be treated as: /users/1
func CleanPath(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
rctx := chi.RouteContext(r.Context())
routePath := rctx.RoutePath
if routePath == "" {
if r.URL.RawPath != "" {
routePath = r.URL.RawPath
} else {
routePath = r.URL.Path
}
rctx.RoutePath = path.Clean(routePath)
}
next.ServeHTTP(w, r)
})
}

View file

@ -19,9 +19,9 @@ func SetHeader(key, value string) func(next http.Handler) http.Handler {
// AllowContentType enforces a whitelist of request Content-Types otherwise responds
// with a 415 Unsupported Media Type status.
func AllowContentType(contentTypes ...string) func(next http.Handler) http.Handler {
cT := []string{}
for _, t := range contentTypes {
cT = append(cT, strings.ToLower(t))
allowedContentTypes := make(map[string]struct{}, len(contentTypes))
for _, ctype := range contentTypes {
allowedContentTypes[strings.TrimSpace(strings.ToLower(ctype))] = struct{}{}
}
return func(next http.Handler) http.Handler {
@ -37,11 +37,9 @@ func AllowContentType(contentTypes ...string) func(next http.Handler) http.Handl
s = s[0:i]
}
for _, t := range cT {
if t == s {
next.ServeHTTP(w, r)
return
}
if _, ok := allowedContentTypes[s]; ok {
next.ServeHTTP(w, r)
return
}
w.WriteHeader(http.StatusUnsupportedMediaType)

View file

@ -6,6 +6,7 @@ import (
"log"
"net/http"
"os"
"runtime"
"time"
)
@ -16,7 +17,7 @@ var (
// DefaultLogger is called by the Logger middleware handler to log each request.
// Its made a package-level variable so that it can be reconfigured for custom
// logging configurations.
DefaultLogger = RequestLogger(&DefaultLogFormatter{Logger: log.New(os.Stdout, "", log.LstdFlags), NoColor: false})
DefaultLogger func(next http.Handler) http.Handler
)
// Logger is a middleware that logs the start and end of each request, along
@ -27,6 +28,16 @@ var (
//
// Alternatively, look at https://github.com/goware/httplog for a more in-depth
// http logger with structured logging support.
//
// IMPORTANT NOTE: Logger should go before any other middleware that may change
// the response, such as `middleware.Recoverer`. Example:
//
// ```go
// r := chi.NewRouter()
// r.Use(middleware.Logger) // <--<< Logger should come before Recoverer
// r.Use(middleware.Recoverer)
// r.Get("/", handler)
// ```
func Logger(next http.Handler) http.Handler {
return DefaultLogger(next)
}
@ -153,3 +164,11 @@ func (l *defaultLogEntry) Write(status, bytes int, header http.Header, elapsed t
func (l *defaultLogEntry) Panic(v interface{}, stack []byte) {
PrintPrettyStack(v)
}
func init() {
color := true
if runtime.GOOS == "windows" {
color = false
}
DefaultLogger = RequestLogger(&DefaultLogFormatter{Logger: log.New(os.Stdout, "", log.LstdFlags), NoColor: !color})
}

View file

@ -14,13 +14,18 @@ func StripSlashes(next http.Handler) http.Handler {
fn := func(w http.ResponseWriter, r *http.Request) {
var path string
rctx := chi.RouteContext(r.Context())
if rctx.RoutePath != "" {
if rctx != nil && rctx.RoutePath != "" {
path = rctx.RoutePath
} else {
path = r.URL.Path
}
if len(path) > 1 && path[len(path)-1] == '/' {
rctx.RoutePath = path[:len(path)-1]
newPath := path[:len(path)-1]
if rctx == nil {
r.URL.Path = newPath
} else {
rctx.RoutePath = newPath
}
}
next.ServeHTTP(w, r)
}
@ -36,7 +41,7 @@ func RedirectSlashes(next http.Handler) http.Handler {
fn := func(w http.ResponseWriter, r *http.Request) {
var path string
rctx := chi.RouteContext(r.Context())
if rctx.RoutePath != "" {
if rctx != nil && rctx.RoutePath != "" {
path = rctx.RoutePath
} else {
path = r.URL.Path
@ -47,7 +52,8 @@ func RedirectSlashes(next http.Handler) http.Handler {
} else {
path = path[:len(path)-1]
}
http.Redirect(w, r, path, 301)
redirectUrl := fmt.Sprintf("//%s%s", r.Host, path)
http.Redirect(w, r, redirectUrl, 301)
return
}
next.ServeHTTP(w, r)

View file

@ -53,7 +53,7 @@ func URLFormat(next http.Handler) http.Handler {
if strings.Index(path, ".") > 0 {
base := strings.LastIndex(path, "/")
idx := strings.Index(path[base:], ".")
idx := strings.LastIndex(path[base:], ".")
if idx > 0 {
idx += base

46
vendor/github.com/go-chi/chi/mux.go generated vendored
View file

@ -1,7 +1,6 @@
package chi
import (
"context"
"fmt"
"net/http"
"strings"
@ -78,9 +77,10 @@ func (mx *Mux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
rctx = mx.pool.Get().(*Context)
rctx.Reset()
rctx.Routes = mx
rctx.parentCtx = r.Context()
// NOTE: r.WithContext() causes 2 allocations and context.WithValue() causes 1 allocation
r = r.WithContext(context.WithValue(r.Context(), RouteCtxKey, rctx))
// NOTE: r.WithContext() causes 2 allocations
r = r.WithContext((*directContext)(rctx))
// Serve the request and once its done, put the request context back in the sync pool
mx.handler.ServeHTTP(w, r)
@ -227,7 +227,7 @@ func (mx *Mux) With(middlewares ...func(http.Handler) http.Handler) Router {
// Similarly as in handle(), we must build the mux handler once additional
// middleware registration isn't allowed for this stack, like now.
if !mx.inline && mx.handler == nil {
mx.buildRouteHandler()
mx.updateRouteHandler()
}
// Copy middlewares from parent inline muxs
@ -261,10 +261,11 @@ func (mx *Mux) Group(fn func(r Router)) Router {
// along the `pattern` as a subrouter. Effectively, this is a short-hand
// call to Mount. See _examples/.
func (mx *Mux) Route(pattern string, fn func(r Router)) Router {
subRouter := NewRouter()
if fn != nil {
fn(subRouter)
if fn == nil {
panic(fmt.Sprintf("chi: attempting to Route() a nil subrouter on '%s'", pattern))
}
subRouter := NewRouter()
fn(subRouter)
mx.Mount(pattern, subRouter)
return subRouter
}
@ -277,6 +278,10 @@ func (mx *Mux) Route(pattern string, fn func(r Router)) Router {
// routing at the `handler`, which in most cases is another chi.Router. As a result,
// if you define two Mount() routes on the exact same pattern the mount will panic.
func (mx *Mux) Mount(pattern string, handler http.Handler) {
if handler == nil {
panic(fmt.Sprintf("chi: attempting to Mount() a nil handler on '%s'", pattern))
}
// Provide runtime safety for ensuring a pattern isn't mounted on an existing
// routing pattern.
if mx.tree.findPattern(pattern+"*") || mx.tree.findPattern(pattern+"/*") {
@ -294,7 +299,16 @@ func (mx *Mux) Mount(pattern string, handler http.Handler) {
mountHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
rctx := RouteContext(r.Context())
// shift the url path past the previous subrouter
rctx.RoutePath = mx.nextRoutePath(rctx)
// reset the wildcard URLParam which connects the subrouter
n := len(rctx.URLParams.Keys) - 1
if n >= 0 && rctx.URLParams.Keys[n] == "*" && len(rctx.URLParams.Values) > n {
rctx.URLParams.Values[n] = ""
}
handler.ServeHTTP(w, r)
})
@ -367,14 +381,6 @@ func (mx *Mux) MethodNotAllowedHandler() http.HandlerFunc {
return methodNotAllowedHandler
}
// buildRouteHandler builds the single mux handler that is a chain of the middleware
// stack, as defined by calls to Use(), and the tree router (Mux) itself. After this
// point, no other middlewares can be registered on this Mux's stack. But you can still
// compose additional middlewares via Group()'s or using a chained middleware handler.
func (mx *Mux) buildRouteHandler() {
mx.handler = chain(mx.middlewares, http.HandlerFunc(mx.routeHTTP))
}
// handle registers a http.Handler in the routing tree for a particular http method
// and routing pattern.
func (mx *Mux) handle(method methodTyp, pattern string, handler http.Handler) *node {
@ -384,7 +390,7 @@ func (mx *Mux) handle(method methodTyp, pattern string, handler http.Handler) *n
// Build the computed routing handler for this routing pattern.
if !mx.inline && mx.handler == nil {
mx.buildRouteHandler()
mx.updateRouteHandler()
}
// Build endpoint handler with inline middlewares for the route
@ -458,6 +464,14 @@ func (mx *Mux) updateSubRoutes(fn func(subMux *Mux)) {
}
}
// updateRouteHandler builds the single mux handler that is a chain of the middleware
// stack, as defined by calls to Use(), and the tree router (Mux) itself. After this
// point, no other middlewares can be registered on this Mux's stack. But you can still
// compose additional middlewares via Group()'s or using a chained middleware handler.
func (mx *Mux) updateRouteHandler() {
mx.handler = chain(mx.middlewares, http.HandlerFunc(mx.routeHTTP))
}
// methodNotAllowedHandler is a helper function to respond with a 405,
// method not allowed.
func methodNotAllowedHandler(w http.ResponseWriter, r *http.Request) {

27
vendor/github.com/unrolled/render/.gitignore generated vendored Normal file
View file

@ -0,0 +1,27 @@
# Compiled Object files, Static and Dynamic libs (Shared Objects)
*.o
*.a
*.so
# Folders
_obj
_test
# Architecture specific extensions/prefixes
*.[568vq]
[568vq].out
*.cgo1.go
*.cgo2.c
_cgo_defun.c
_cgo_gotypes.go
_cgo_export.*
_testmain.go
*.exe
*.test
*.pem
.DS_Store

15
vendor/github.com/unrolled/render/.travis.yml generated vendored Normal file
View file

@ -0,0 +1,15 @@
language: go
go:
- 1.11.x
- 1.12.x
- tip
env:
- GO111MODULE=on
install:
- go mod download
script:
- go test -v -race -tags=integration

20
vendor/github.com/unrolled/render/LICENSE generated vendored Normal file
View file

@ -0,0 +1,20 @@
The MIT License (MIT)
Copyright (c) 2014 Cory Jacobsen
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

508
vendor/github.com/unrolled/render/README.md generated vendored Normal file
View file

@ -0,0 +1,508 @@
# Render [![GoDoc](http://godoc.org/github.com/unrolled/render?status.svg)](http://godoc.org/github.com/unrolled/render) [![Build Status](https://travis-ci.org/unrolled/render.svg)](https://travis-ci.org/unrolled/render)
Render is a package that provides functionality for easily rendering JSON, XML, text, binary data, and HTML templates. This package is based on the [Martini](https://github.com/go-martini/martini) [render](https://github.com/martini-contrib/render) work.
## Block Deprecation Notice
Go 1.6 introduces a new [block](https://github.com/golang/go/blob/release-branch.go1.6/src/html/template/example_test.go#L128) action. This conflicts with Render's included `block` template function. To provide an easy migration path, a new function was created called `partial`. It is a duplicate of the old `block` function. It is advised that all users of the `block` function update their code to avoid any issues in the future. Previous to Go 1.6, Render's `block` functionality will continue to work but a message will be logged urging you to migrate to the new `partial` function.
## Usage
Render can be used with pretty much any web framework providing you can access the `http.ResponseWriter` from your handler. The rendering functions simply wraps Go's existing functionality for marshaling and rendering data.
- HTML: Uses the [html/template](http://golang.org/pkg/html/template/) package to render HTML templates.
- JSON: Uses the [encoding/json](http://golang.org/pkg/encoding/json/) package to marshal data into a JSON-encoded response.
- XML: Uses the [encoding/xml](http://golang.org/pkg/encoding/xml/) package to marshal data into an XML-encoded response.
- Binary data: Passes the incoming data straight through to the `http.ResponseWriter`.
- Text: Passes the incoming string straight through to the `http.ResponseWriter`.
~~~ go
// main.go
package main
import (
"encoding/xml"
"net/http"
"github.com/unrolled/render" // or "gopkg.in/unrolled/render.v1"
)
type ExampleXml struct {
XMLName xml.Name `xml:"example"`
One string `xml:"one,attr"`
Two string `xml:"two,attr"`
}
func main() {
r := render.New()
mux := http.NewServeMux()
mux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
w.Write([]byte("Welcome, visit sub pages now."))
})
mux.HandleFunc("/data", func(w http.ResponseWriter, req *http.Request) {
r.Data(w, http.StatusOK, []byte("Some binary data here."))
})
mux.HandleFunc("/text", func(w http.ResponseWriter, req *http.Request) {
r.Text(w, http.StatusOK, "Plain text here")
})
mux.HandleFunc("/json", func(w http.ResponseWriter, req *http.Request) {
r.JSON(w, http.StatusOK, map[string]string{"hello": "json"})
})
mux.HandleFunc("/jsonp", func(w http.ResponseWriter, req *http.Request) {
r.JSONP(w, http.StatusOK, "callbackName", map[string]string{"hello": "jsonp"})
})
mux.HandleFunc("/xml", func(w http.ResponseWriter, req *http.Request) {
r.XML(w, http.StatusOK, ExampleXml{One: "hello", Two: "xml"})
})
mux.HandleFunc("/html", func(w http.ResponseWriter, req *http.Request) {
// Assumes you have a template in ./templates called "example.tmpl"
// $ mkdir -p templates && echo "<h1>Hello {{.}}.</h1>" > templates/example.tmpl
r.HTML(w, http.StatusOK, "example", "World")
})
http.ListenAndServe("127.0.0.1:3000", mux)
}
~~~
~~~ html
<!-- templates/example.tmpl -->
<h1>Hello {{.}}.</h1>
~~~
### Available Options
Render comes with a variety of configuration options _(Note: these are not the default option values. See the defaults below.)_:
~~~ go
// ...
r := render.New(render.Options{
Directory: "templates", // Specify what path to load the templates from.
FileSystem: &LocalFileSystem{}, // Specify filesystem from where files are loaded.
Asset: func(name string) ([]byte, error) { // Load from an Asset function instead of file.
return []byte("template content"), nil
},
AssetNames: func() []string { // Return a list of asset names for the Asset function
return []string{"filename.tmpl"}
},
Layout: "layout", // Specify a layout template. Layouts can call {{ yield }} to render the current template or {{ partial "css" }} to render a partial from the current template.
Extensions: []string{".tmpl", ".html"}, // Specify extensions to load for templates.
Funcs: []template.FuncMap{AppHelpers}, // Specify helper function maps for templates to access.
Delims: render.Delims{"{[{", "}]}"}, // Sets delimiters to the specified strings.
Charset: "UTF-8", // Sets encoding for content-types. Default is "UTF-8".
DisableCharset: true, // Prevents the charset from being appended to the content type header.
IndentJSON: true, // Output human readable JSON.
IndentXML: true, // Output human readable XML.
PrefixJSON: []byte(")]}',\n"), // Prefixes JSON responses with the given bytes.
PrefixXML: []byte("<?xml version='1.0' encoding='UTF-8'?>"), // Prefixes XML responses with the given bytes.
HTMLContentType: "application/xhtml+xml", // Output XHTML content type instead of default "text/html".
IsDevelopment: true, // Render will now recompile the templates on every HTML response.
UnEscapeHTML: true, // Replace ensure '&<>' are output correctly (JSON only).
StreamingJSON: true, // Streams the JSON response via json.Encoder.
RequirePartials: true, // Return an error if a template is missing a partial used in a layout.
DisableHTTPErrorRendering: true, // Disables automatic rendering of http.StatusInternalServerError when an error occurs.
})
// ...
~~~
### Default Options
These are the preset options for Render:
~~~ go
r := render.New()
// Is the same as the default configuration options:
r := render.New(render.Options{
Directory: "templates",
FileSystem: &LocalFileSystem{},
Asset: nil,
AssetNames: nil,
Layout: "",
Extensions: []string{".tmpl"},
Funcs: []template.FuncMap{},
Delims: render.Delims{"{{", "}}"},
Charset: "UTF-8",
DisableCharset: false,
IndentJSON: false,
IndentXML: false,
PrefixJSON: []byte(""),
PrefixXML: []byte(""),
BinaryContentType: "application/octet-stream",
HTMLContentType: "text/html",
JSONContentType: "application/json",
JSONPContentType: "application/javascript",
TextContentType: "text/plain",
XMLContentType: "application/xhtml+xml",
IsDevelopment: false,
UnEscapeHTML: false,
StreamingJSON: false,
RequirePartials: false,
DisableHTTPErrorRendering: false,
})
~~~
### JSON vs Streaming JSON
By default, Render does **not** stream JSON to the `http.ResponseWriter`. It instead marshalls your object into a byte array, and if no errors occurred, writes that byte array to the `http.ResponseWriter`. If you would like to use the built it in streaming functionality (`json.Encoder`), you can set the `StreamingJSON` setting to `true`. This will stream the output directly to the `http.ResponseWriter`. Also note that streaming is only implemented in `render.JSON` and not `render.JSONP`, and the `UnEscapeHTML` and `Indent` options are ignored when streaming.
### Loading Templates
By default Render will attempt to load templates with a '.tmpl' extension from the "templates" directory. Templates are found by traversing the templates directory and are named by path and basename. For instance, the following directory structure:
~~~
templates/
|
|__ admin/
| |
| |__ index.tmpl
| |
| |__ edit.tmpl
|
|__ home.tmpl
~~~
Will provide the following templates:
~~~
admin/index
admin/edit
home
~~~
You can also load templates from memory by providing the Asset and AssetNames options,
e.g. when generating an asset file using [go-bindata](https://github.com/jteeuwen/go-bindata).
### Layouts
Render provides `yield` and `partial` functions for layouts to access:
~~~ go
// ...
r := render.New(render.Options{
Layout: "layout",
})
// ...
~~~
~~~ html
<!-- templates/layout.tmpl -->
<html>
<head>
<title>My Layout</title>
<!-- Render the partial template called `css-$current_template` here -->
{{ partial "css" }}
</head>
<body>
<!-- render the partial template called `header-$current_template` here -->
{{ partial "header" }}
<!-- Render the current template here -->
{{ yield }}
<!-- render the partial template called `footer-$current_template` here -->
{{ partial "footer" }}
</body>
</html>
~~~
`current` can also be called to get the current template being rendered.
~~~ html
<!-- templates/layout.tmpl -->
<html>
<head>
<title>My Layout</title>
</head>
<body>
This is the {{ current }} page.
</body>
</html>
~~~
Partials are defined by individual templates as seen below. The partial template's
name needs to be defined as "{partial name}-{template name}".
~~~ html
<!-- templates/home.tmpl -->
{{ define "header-home" }}
<h1>Home</h1>
{{ end }}
{{ define "footer-home"}}
<p>The End</p>
{{ end }}
~~~
By default, the template is not required to define all partials referenced in the
layout. If you want an error to be returned when a template does not define a
partial, set `Options.RequirePartials = true`.
### Character Encodings
Render will automatically set the proper Content-Type header based on which function you call. See below for an example of what the default settings would output (note that UTF-8 is the default, and binary data does not output the charset):
~~~ go
// main.go
package main
import (
"encoding/xml"
"net/http"
"github.com/unrolled/render" // or "gopkg.in/unrolled/render.v1"
)
type ExampleXml struct {
XMLName xml.Name `xml:"example"`
One string `xml:"one,attr"`
Two string `xml:"two,attr"`
}
func main() {
r := render.New(render.Options{})
mux := http.NewServeMux()
// This will set the Content-Type header to "application/octet-stream".
// Note that this does not receive a charset value.
mux.HandleFunc("/data", func(w http.ResponseWriter, req *http.Request) {
r.Data(w, http.StatusOK, []byte("Some binary data here."))
})
// This will set the Content-Type header to "application/json; charset=UTF-8".
mux.HandleFunc("/json", func(w http.ResponseWriter, req *http.Request) {
r.JSON(w, http.StatusOK, map[string]string{"hello": "json"})
})
// This will set the Content-Type header to "text/xml; charset=UTF-8".
mux.HandleFunc("/xml", func(w http.ResponseWriter, req *http.Request) {
r.XML(w, http.StatusOK, ExampleXml{One: "hello", Two: "xml"})
})
// This will set the Content-Type header to "text/plain; charset=UTF-8".
mux.HandleFunc("/text", func(w http.ResponseWriter, req *http.Request) {
r.Text(w, http.StatusOK, "Plain text here")
})
// This will set the Content-Type header to "text/html; charset=UTF-8".
mux.HandleFunc("/html", func(w http.ResponseWriter, req *http.Request) {
// Assumes you have a template in ./templates called "example.tmpl"
// $ mkdir -p templates && echo "<h1>Hello {{.}}.</h1>" > templates/example.tmpl
r.HTML(w, http.StatusOK, "example", "World")
})
http.ListenAndServe("127.0.0.1:3000", mux)
}
~~~
In order to change the charset, you can set the `Charset` within the `render.Options` to your encoding value:
~~~ go
// main.go
package main
import (
"encoding/xml"
"net/http"
"github.com/unrolled/render" // or "gopkg.in/unrolled/render.v1"
)
type ExampleXml struct {
XMLName xml.Name `xml:"example"`
One string `xml:"one,attr"`
Two string `xml:"two,attr"`
}
func main() {
r := render.New(render.Options{
Charset: "ISO-8859-1",
})
mux := http.NewServeMux()
// This will set the Content-Type header to "application/octet-stream".
// Note that this does not receive a charset value.
mux.HandleFunc("/data", func(w http.ResponseWriter, req *http.Request) {
r.Data(w, http.StatusOK, []byte("Some binary data here."))
})
// This will set the Content-Type header to "application/json; charset=ISO-8859-1".
mux.HandleFunc("/json", func(w http.ResponseWriter, req *http.Request) {
r.JSON(w, http.StatusOK, map[string]string{"hello": "json"})
})
// This will set the Content-Type header to "text/xml; charset=ISO-8859-1".
mux.HandleFunc("/xml", func(w http.ResponseWriter, req *http.Request) {
r.XML(w, http.StatusOK, ExampleXml{One: "hello", Two: "xml"})
})
// This will set the Content-Type header to "text/plain; charset=ISO-8859-1".
mux.HandleFunc("/text", func(w http.ResponseWriter, req *http.Request) {
r.Text(w, http.StatusOK, "Plain text here")
})
// This will set the Content-Type header to "text/html; charset=ISO-8859-1".
mux.HandleFunc("/html", func(w http.ResponseWriter, req *http.Request) {
// Assumes you have a template in ./templates called "example.tmpl"
// $ mkdir -p templates && echo "<h1>Hello {{.}}.</h1>" > templates/example.tmpl
r.HTML(w, http.StatusOK, "example", "World")
})
http.ListenAndServe("127.0.0.1:3000", mux)
}
~~~
### Error Handling
The rendering functions return any errors from the rendering engine.
By default, they will also write the error to the HTTP response and set the status code to 500. You can disable
this behavior so that you can handle errors yourself by setting
`Options.DisableHTTPErrorRendering: true`.
~~~go
r := render.New(render.Options{
DisableHTTPErrorRendering: true,
})
//...
err := r.HTML(w, http.StatusOK, "example", "World")
if err != nil{
http.Redirect(w, r, "/my-custom-500", http.StatusFound)
}
~~~
## Integration Examples
### [Echo](https://github.com/labstack/echo)
~~~ go
// main.go
package main
import (
"io"
"net/http"
"github.com/labstack/echo"
"github.com/unrolled/render" // or "gopkg.in/unrolled/render.v1"
)
type RenderWrapper struct { // We need to wrap the renderer because we need a different signature for echo.
rnd *render.Render
}
func (r *RenderWrapper) Render(w io.Writer, name string, data interface{},c echo.Context) error {
return r.rnd.HTML(w, 0, name, data) // The zero status code is overwritten by echo.
}
func main() {
r := &RenderWrapper{render.New()}
e := echo.New()
e.Renderer = r
e.GET("/", func(c echo.Context) error {
return c.Render(http.StatusOK, "TemplateName", "TemplateData")
})
e.Logger.Fatal(e.Start(":1323"))
}
~~~
### [Gin](https://github.com/gin-gonic/gin)
~~~ go
// main.go
package main
import (
"net/http"
"github.com/gin-gonic/gin"
"github.com/unrolled/render" // or "gopkg.in/unrolled/render.v1"
)
func main() {
r := render.New(render.Options{
IndentJSON: true,
})
router := gin.Default()
router.GET("/", func(c *gin.Context) {
r.JSON(c.Writer, http.StatusOK, map[string]string{"welcome": "This is rendered JSON!"})
})
router.Run(":3000")
}
~~~
### [Goji](https://github.com/zenazn/goji)
~~~ go
// main.go
package main
import (
"net/http"
"github.com/zenazn/goji"
"github.com/zenazn/goji/web"
"github.com/unrolled/render" // or "gopkg.in/unrolled/render.v1"
)
func main() {
r := render.New(render.Options{
IndentJSON: true,
})
goji.Get("/", func(c web.C, w http.ResponseWriter, req *http.Request) {
r.JSON(w, http.StatusOK, map[string]string{"welcome": "This is rendered JSON!"})
})
goji.Serve() // Defaults to ":8000".
}
~~~
### [Negroni](https://github.com/codegangsta/negroni)
~~~ go
// main.go
package main
import (
"net/http"
"github.com/urfave/negroni"
"github.com/unrolled/render" // or "gopkg.in/unrolled/render.v1"
)
func main() {
r := render.New(render.Options{
IndentJSON: true,
})
mux := http.NewServeMux()
mux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
r.JSON(w, http.StatusOK, map[string]string{"welcome": "This is rendered JSON!"})
})
n := negroni.Classic()
n.UseHandler(mux)
n.Run(":3000")
}
~~~
### [Traffic](https://github.com/pilu/traffic)
~~~ go
// main.go
package main
import (
"net/http"
"github.com/pilu/traffic"
"github.com/unrolled/render" // or "gopkg.in/unrolled/render.v1"
)
func main() {
r := render.New(render.Options{
IndentJSON: true,
})
router := traffic.New()
router.Get("/", func(w traffic.ResponseWriter, req *traffic.Request) {
r.JSON(w, http.StatusOK, map[string]string{"welcome": "This is rendered JSON!"})
})
router.Run()
}
~~~

46
vendor/github.com/unrolled/render/buffer.go generated vendored Normal file
View file

@ -0,0 +1,46 @@
package render
import "bytes"
// bufPool represents a reusable buffer pool for executing templates into.
var bufPool *BufferPool
// BufferPool implements a pool of bytes.Buffers in the form of a bounded channel.
// Pulled from the github.com/oxtoacart/bpool package (Apache licensed).
type BufferPool struct {
c chan *bytes.Buffer
}
// NewBufferPool creates a new BufferPool bounded to the given size.
func NewBufferPool(size int) (bp *BufferPool) {
return &BufferPool{
c: make(chan *bytes.Buffer, size),
}
}
// Get gets a Buffer from the BufferPool, or creates a new one if none are
// available in the pool.
func (bp *BufferPool) Get() (b *bytes.Buffer) {
select {
case b = <-bp.c:
// reuse existing buffer
default:
// create new buffer
b = bytes.NewBuffer([]byte{})
}
return
}
// Put returns the given Buffer to the BufferPool.
func (bp *BufferPool) Put(b *bytes.Buffer) {
b.Reset()
select {
case bp.c <- b:
default: // Discard the buffer if the pool is full.
}
}
// Initialize buffer pool for writing templates into.
func init() {
bufPool = NewBufferPool(64)
}

55
vendor/github.com/unrolled/render/doc.go generated vendored Normal file
View file

@ -0,0 +1,55 @@
/*Package render is a package that provides functionality for easily rendering JSON, XML, binary data, and HTML templates.
package main
import (
"encoding/xml"
"net/http"
"github.com/unrolled/render" // or "gopkg.in/unrolled/render.v1"
)
type ExampleXml struct {
XMLName xml.Name `xml:"example"`
One string `xml:"one,attr"`
Two string `xml:"two,attr"`
}
func main() {
r := render.New()
mux := http.NewServeMux()
mux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
w.Write([]byte("Welcome, visit sub pages now."))
})
mux.HandleFunc("/data", func(w http.ResponseWriter, req *http.Request) {
r.Data(w, http.StatusOK, []byte("Some binary data here."))
})
mux.HandleFunc("/text", func(w http.ResponseWriter, req *http.Request) {
r.Text(w, http.StatusOK, "Plain text here")
})
mux.HandleFunc("/json", func(w http.ResponseWriter, req *http.Request) {
r.JSON(w, http.StatusOK, map[string]string{"hello": "json"})
})
mux.HandleFunc("/jsonp", func(w http.ResponseWriter, req *http.Request) {
r.JSONP(w, http.StatusOK, "callbackName", map[string]string{"hello": "jsonp"})
})
mux.HandleFunc("/xml", func(w http.ResponseWriter, req *http.Request) {
r.XML(w, http.StatusOK, ExampleXml{One: "hello", Two: "xml"})
})
mux.HandleFunc("/html", func(w http.ResponseWriter, req *http.Request) {
// Assumes you have a template in ./templates called "example.tmpl".
// $ mkdir -p templates && echo "<h1>Hello HTML world.</h1>" > templates/example.tmpl
r.HTML(w, http.StatusOK, "example", nil)
})
http.ListenAndServe("0.0.0.0:3000", mux)
}
*/
package render

217
vendor/github.com/unrolled/render/engine.go generated vendored Normal file
View file

@ -0,0 +1,217 @@
package render
import (
"bytes"
"encoding/json"
"encoding/xml"
"html/template"
"io"
"net/http"
)
// Engine is the generic interface for all responses.
type Engine interface {
Render(io.Writer, interface{}) error
}
// Head defines the basic ContentType and Status fields.
type Head struct {
ContentType string
Status int
}
// Data built-in renderer.
type Data struct {
Head
}
// HTML built-in renderer.
type HTML struct {
Head
Name string
Templates *template.Template
}
// JSON built-in renderer.
type JSON struct {
Head
Indent bool
UnEscapeHTML bool
Prefix []byte
StreamingJSON bool
}
// JSONP built-in renderer.
type JSONP struct {
Head
Indent bool
Callback string
}
// Text built-in renderer.
type Text struct {
Head
}
// XML built-in renderer.
type XML struct {
Head
Indent bool
Prefix []byte
}
// Write outputs the header content.
func (h Head) Write(w http.ResponseWriter) {
w.Header().Set(ContentType, h.ContentType)
w.WriteHeader(h.Status)
}
// Render a data response.
func (d Data) Render(w io.Writer, v interface{}) error {
if hw, ok := w.(http.ResponseWriter); ok {
c := hw.Header().Get(ContentType)
if c != "" {
d.Head.ContentType = c
}
d.Head.Write(hw)
}
w.Write(v.([]byte))
return nil
}
// Render a HTML response.
func (h HTML) Render(w io.Writer, binding interface{}) error {
// Retrieve a buffer from the pool to write to.
out := bufPool.Get()
err := h.Templates.ExecuteTemplate(out, h.Name, binding)
if err != nil {
return err
}
if hw, ok := w.(http.ResponseWriter); ok {
h.Head.Write(hw)
}
out.WriteTo(w)
// Return the buffer to the pool.
bufPool.Put(out)
return nil
}
// Render a JSON response.
func (j JSON) Render(w io.Writer, v interface{}) error {
if j.StreamingJSON {
return j.renderStreamingJSON(w, v)
}
var result []byte
var err error
if j.Indent {
result, err = json.MarshalIndent(v, "", " ")
result = append(result, '\n')
} else {
result, err = json.Marshal(v)
}
if err != nil {
return err
}
// Unescape HTML if needed.
if j.UnEscapeHTML {
result = bytes.Replace(result, []byte("\\u003c"), []byte("<"), -1)
result = bytes.Replace(result, []byte("\\u003e"), []byte(">"), -1)
result = bytes.Replace(result, []byte("\\u0026"), []byte("&"), -1)
}
// JSON marshaled fine, write out the result.
if hw, ok := w.(http.ResponseWriter); ok {
j.Head.Write(hw)
}
if len(j.Prefix) > 0 {
w.Write(j.Prefix)
}
w.Write(result)
return nil
}
func (j JSON) renderStreamingJSON(w io.Writer, v interface{}) error {
if hw, ok := w.(http.ResponseWriter); ok {
j.Head.Write(hw)
}
if len(j.Prefix) > 0 {
w.Write(j.Prefix)
}
return json.NewEncoder(w).Encode(v)
}
// Render a JSONP response.
func (j JSONP) Render(w io.Writer, v interface{}) error {
var result []byte
var err error
if j.Indent {
result, err = json.MarshalIndent(v, "", " ")
} else {
result, err = json.Marshal(v)
}
if err != nil {
return err
}
// JSON marshaled fine, write out the result.
if hw, ok := w.(http.ResponseWriter); ok {
j.Head.Write(hw)
}
w.Write([]byte(j.Callback + "("))
w.Write(result)
w.Write([]byte(");"))
// If indenting, append a new line.
if j.Indent {
w.Write([]byte("\n"))
}
return nil
}
// Render a text response.
func (t Text) Render(w io.Writer, v interface{}) error {
if hw, ok := w.(http.ResponseWriter); ok {
c := hw.Header().Get(ContentType)
if c != "" {
t.Head.ContentType = c
}
t.Head.Write(hw)
}
w.Write([]byte(v.(string)))
return nil
}
// Render an XML response.
func (x XML) Render(w io.Writer, v interface{}) error {
var result []byte
var err error
if x.Indent {
result, err = xml.MarshalIndent(v, "", " ")
result = append(result, '\n')
} else {
result, err = xml.Marshal(v)
}
if err != nil {
return err
}
// XML marshaled fine, write out the result.
if hw, ok := w.(http.ResponseWriter); ok {
x.Head.Write(hw)
}
if len(x.Prefix) > 0 {
w.Write(x.Prefix)
}
w.Write(result)
return nil
}

21
vendor/github.com/unrolled/render/fs.go generated vendored Normal file
View file

@ -0,0 +1,21 @@
package render
import (
"io/ioutil"
"path/filepath"
)
type FileSystem interface {
Walk(root string, walkFn filepath.WalkFunc) error
ReadFile(filename string) ([]byte, error)
}
type LocalFileSystem struct{}
func (LocalFileSystem) Walk(root string, walkFn filepath.WalkFunc) error {
return filepath.Walk(root, walkFn)
}
func (LocalFileSystem) ReadFile(filename string) ([]byte, error) {
return ioutil.ReadFile(filename)
}

5
vendor/github.com/unrolled/render/go.mod generated vendored Normal file
View file

@ -0,0 +1,5 @@
module github.com/unrolled/render
go 1.12
require github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385

2
vendor/github.com/unrolled/render/go.sum generated vendored Normal file
View file

@ -0,0 +1,2 @@
github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385 h1:clC1lXBpe2kTj2VHdaIu9ajZQe4kcEY9j0NsnDDBZ3o=
github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385/go.mod h1:0vRUJqYpeSZifjYj7uP3BG/gKcuzL9xWVV/Y+cK33KM=

21
vendor/github.com/unrolled/render/helpers.go generated vendored Normal file
View file

@ -0,0 +1,21 @@
// +build go1.6
package render
import (
"fmt"
"html/template"
)
// Included helper functions for use when rendering HTML.
var helperFuncs = template.FuncMap{
"yield": func() (string, error) {
return "", fmt.Errorf("yield called with no layout defined")
},
"partial": func() (string, error) {
return "", fmt.Errorf("block called with no layout defined")
},
"current": func() (string, error) {
return "", nil
},
}

26
vendor/github.com/unrolled/render/helpers_pre16.go generated vendored Normal file
View file

@ -0,0 +1,26 @@
// +build !go1.6
package render
import (
"fmt"
"html/template"
)
// Included helper functions for use when rendering HTML.
var helperFuncs = template.FuncMap{
"yield": func() (string, error) {
return "", fmt.Errorf("yield called with no layout defined")
},
// `block` is deprecated! Use the `partial` below if you need this functionality still.
// Otherwise, checkout Go's `block` implementation introduced in 1.6
"block": func() (string, error) {
return "", fmt.Errorf("block called with no layout defined")
},
"partial": func() (string, error) {
return "", fmt.Errorf("block called with no layout defined")
},
"current": func() (string, error) {
return "", nil
},
}

480
vendor/github.com/unrolled/render/render.go generated vendored Normal file
View file

@ -0,0 +1,480 @@
package render
import (
"bytes"
"fmt"
"html/template"
"io"
"log"
"net/http"
"os"
"path/filepath"
"strings"
"sync"
)
const (
// ContentBinary header value for binary data.
ContentBinary = "application/octet-stream"
// ContentHTML header value for HTML data.
ContentHTML = "text/html"
// ContentJSON header value for JSON data.
ContentJSON = "application/json"
// ContentJSONP header value for JSONP data.
ContentJSONP = "application/javascript"
// ContentLength header constant.
ContentLength = "Content-Length"
// ContentText header value for Text data.
ContentText = "text/plain"
// ContentType header constant.
ContentType = "Content-Type"
// ContentXHTML header value for XHTML data.
ContentXHTML = "application/xhtml+xml"
// ContentXML header value for XML data.
ContentXML = "text/xml"
// Default character encoding.
defaultCharset = "UTF-8"
)
// helperFuncs had to be moved out. See helpers.go|helpers_pre16.go files.
// Delims represents a set of Left and Right delimiters for HTML template rendering.
type Delims struct {
// Left delimiter, defaults to {{.
Left string
// Right delimiter, defaults to }}.
Right string
}
// Options is a struct for specifying configuration options for the render.Render object.
type Options struct {
// Directory to load templates. Default is "templates".
Directory string
// FileSystem to access files
FileSystem FileSystem
// Asset function to use in place of directory. Defaults to nil.
Asset func(name string) ([]byte, error)
// AssetNames function to use in place of directory. Defaults to nil.
AssetNames func() []string
// Layout template name. Will not render a layout if blank (""). Defaults to blank ("").
Layout string
// Extensions to parse template files from. Defaults to [".tmpl"].
Extensions []string
// Funcs is a slice of FuncMaps to apply to the template upon compilation. This is useful for helper functions. Defaults to empty map.
Funcs []template.FuncMap
// Delims sets the action delimiters to the specified strings in the Delims struct.
Delims Delims
// Appends the given character set to the Content-Type header. Default is "UTF-8".
Charset string
// If DisableCharset is set to true, it will not append the above Charset value to the Content-Type header. Default is false.
DisableCharset bool
// Outputs human readable JSON.
IndentJSON bool
// Outputs human readable XML. Default is false.
IndentXML bool
// Prefixes the JSON output with the given bytes. Default is false.
PrefixJSON []byte
// Prefixes the XML output with the given bytes.
PrefixXML []byte
// Allows changing the binary content type.
BinaryContentType string
// Allows changing the HTML content type.
HTMLContentType string
// Allows changing the JSON content type.
JSONContentType string
// Allows changing the JSONP content type.
JSONPContentType string
// Allows changing the Text content type.
TextContentType string
// Allows changing the XML content type.
XMLContentType string
// If IsDevelopment is set to true, this will recompile the templates on every request. Default is false.
IsDevelopment bool
// Unescape HTML characters "&<>" to their original values. Default is false.
UnEscapeHTML bool
// Streams JSON responses instead of marshalling prior to sending. Default is false.
StreamingJSON bool
// Require that all partials executed in the layout are implemented in all templates using the layout. Default is false.
RequirePartials bool
// Deprecated: Use the above `RequirePartials` instead of this. As of Go 1.6, blocks are built in. Default is false.
RequireBlocks bool
// Disables automatic rendering of http.StatusInternalServerError when an error occurs. Default is false.
DisableHTTPErrorRendering bool
// Enables using partials without the current filename suffix which allows use of the same template in multiple files. e.g {{ partial "carosuel" }} inside the home template will match carosel-home or carosel.
// ***NOTE*** - This option should be named RenderPartialsWithoutSuffix as that is what it does. "Prefix" is a typo. Maintaining the existing name for backwards compatibility.
RenderPartialsWithoutPrefix bool
}
// HTMLOptions is a struct for overriding some rendering Options for specific HTML call.
type HTMLOptions struct {
// Layout template name. Overrides Options.Layout.
Layout string
// Funcs added to Options.Funcs.
Funcs template.FuncMap
}
// Render is a service that provides functions for easily writing JSON, XML,
// binary data, and HTML templates out to a HTTP Response.
type Render struct {
// Customize Secure with an Options struct.
opt Options
templates *template.Template
templatesLk sync.Mutex
compiledCharset string
}
// New constructs a new Render instance with the supplied options.
func New(options ...Options) *Render {
var o Options
if len(options) > 0 {
o = options[0]
}
r := Render{
opt: o,
}
r.prepareOptions()
r.compileTemplates()
return &r
}
func (r *Render) prepareOptions() {
// Fill in the defaults if need be.
if len(r.opt.Charset) == 0 {
r.opt.Charset = defaultCharset
}
if r.opt.DisableCharset == false {
r.compiledCharset = "; charset=" + r.opt.Charset
}
if len(r.opt.Directory) == 0 {
r.opt.Directory = "templates"
}
if r.opt.FileSystem == nil {
r.opt.FileSystem = &LocalFileSystem{}
}
if len(r.opt.Extensions) == 0 {
r.opt.Extensions = []string{".tmpl"}
}
if len(r.opt.BinaryContentType) == 0 {
r.opt.BinaryContentType = ContentBinary
}
if len(r.opt.HTMLContentType) == 0 {
r.opt.HTMLContentType = ContentHTML
}
if len(r.opt.JSONContentType) == 0 {
r.opt.JSONContentType = ContentJSON
}
if len(r.opt.JSONPContentType) == 0 {
r.opt.JSONPContentType = ContentJSONP
}
if len(r.opt.TextContentType) == 0 {
r.opt.TextContentType = ContentText
}
if len(r.opt.XMLContentType) == 0 {
r.opt.XMLContentType = ContentXML
}
}
func (r *Render) compileTemplates() {
if r.opt.Asset == nil || r.opt.AssetNames == nil {
r.compileTemplatesFromDir()
return
}
r.compileTemplatesFromAsset()
}
func (r *Render) compileTemplatesFromDir() {
dir := r.opt.Directory
r.templates = template.New(dir)
r.templates.Delims(r.opt.Delims.Left, r.opt.Delims.Right)
// Walk the supplied directory and compile any files that match our extension list.
r.opt.FileSystem.Walk(dir, func(path string, info os.FileInfo, err error) error {
// Fix same-extension-dirs bug: some dir might be named to: "users.tmpl", "local.html".
// These dirs should be excluded as they are not valid golang templates, but files under
// them should be treat as normal.
// If is a dir, return immediately (dir is not a valid golang template).
if info == nil || info.IsDir() {
return nil
}
rel, err := filepath.Rel(dir, path)
if err != nil {
return err
}
ext := ""
if strings.Index(rel, ".") != -1 {
ext = filepath.Ext(rel)
}
for _, extension := range r.opt.Extensions {
if ext == extension {
buf, err := r.opt.FileSystem.ReadFile(path)
if err != nil {
panic(err)
}
name := (rel[0 : len(rel)-len(ext)])
tmpl := r.templates.New(filepath.ToSlash(name))
// Add our funcmaps.
for _, funcs := range r.opt.Funcs {
tmpl.Funcs(funcs)
}
// Break out if this parsing fails. We don't want any silent server starts.
template.Must(tmpl.Funcs(helperFuncs).Parse(string(buf)))
break
}
}
return nil
})
}
func (r *Render) compileTemplatesFromAsset() {
dir := r.opt.Directory
r.templates = template.New(dir)
r.templates.Delims(r.opt.Delims.Left, r.opt.Delims.Right)
for _, path := range r.opt.AssetNames() {
if !strings.HasPrefix(path, dir) {
continue
}
rel, err := filepath.Rel(dir, path)
if err != nil {
panic(err)
}
ext := ""
if strings.Index(rel, ".") != -1 {
ext = "." + strings.Join(strings.Split(rel, ".")[1:], ".")
}
for _, extension := range r.opt.Extensions {
if ext == extension {
buf, err := r.opt.Asset(path)
if err != nil {
panic(err)
}
name := (rel[0 : len(rel)-len(ext)])
tmpl := r.templates.New(filepath.ToSlash(name))
// Add our funcmaps.
for _, funcs := range r.opt.Funcs {
tmpl.Funcs(funcs)
}
// Break out if this parsing fails. We don't want any silent server starts.
template.Must(tmpl.Funcs(helperFuncs).Parse(string(buf)))
break
}
}
}
}
// TemplateLookup is a wrapper around template.Lookup and returns
// the template with the given name that is associated with t, or nil
// if there is no such template.
func (r *Render) TemplateLookup(t string) *template.Template {
return r.templates.Lookup(t)
}
func (r *Render) execute(name string, binding interface{}) (*bytes.Buffer, error) {
buf := new(bytes.Buffer)
return buf, r.templates.ExecuteTemplate(buf, name, binding)
}
func (r *Render) layoutFuncs(name string, binding interface{}) template.FuncMap {
return template.FuncMap{
"yield": func() (template.HTML, error) {
buf, err := r.execute(name, binding)
// Return safe HTML here since we are rendering our own template.
return template.HTML(buf.String()), err
},
"current": func() (string, error) {
return name, nil
},
"block": func(partialName string) (template.HTML, error) {
log.Print("Render's `block` implementation is now depericated. Use `partial` as a drop in replacement.")
fullPartialName := fmt.Sprintf("%s-%s", partialName, name)
if r.TemplateLookup(fullPartialName) == nil && r.opt.RenderPartialsWithoutPrefix {
fullPartialName = partialName
}
if r.opt.RequireBlocks || r.TemplateLookup(fullPartialName) != nil {
buf, err := r.execute(fullPartialName, binding)
// Return safe HTML here since we are rendering our own template.
return template.HTML(buf.String()), err
}
return "", nil
},
"partial": func(partialName string) (template.HTML, error) {
fullPartialName := fmt.Sprintf("%s-%s", partialName, name)
if r.TemplateLookup(fullPartialName) == nil && r.opt.RenderPartialsWithoutPrefix {
fullPartialName = partialName
}
if r.opt.RequirePartials || r.TemplateLookup(fullPartialName) != nil {
buf, err := r.execute(fullPartialName, binding)
// Return safe HTML here since we are rendering our own template.
return template.HTML(buf.String()), err
}
return "", nil
},
}
}
func (r *Render) prepareHTMLOptions(htmlOpt []HTMLOptions) HTMLOptions {
layout := r.opt.Layout
funcs := template.FuncMap{}
for _, tmp := range r.opt.Funcs {
for k, v := range tmp {
funcs[k] = v
}
}
if len(htmlOpt) > 0 {
opt := htmlOpt[0]
if len(opt.Layout) > 0 {
layout = opt.Layout
}
for k, v := range opt.Funcs {
funcs[k] = v
}
}
return HTMLOptions{
Layout: layout,
Funcs: funcs,
}
}
// Render is the generic function called by XML, JSON, Data, HTML, and can be called by custom implementations.
func (r *Render) Render(w io.Writer, e Engine, data interface{}) error {
err := e.Render(w, data)
if hw, ok := w.(http.ResponseWriter); err != nil && !r.opt.DisableHTTPErrorRendering && ok {
http.Error(hw, err.Error(), http.StatusInternalServerError)
}
return err
}
// Data writes out the raw bytes as binary data.
func (r *Render) Data(w io.Writer, status int, v []byte) error {
head := Head{
ContentType: r.opt.BinaryContentType,
Status: status,
}
d := Data{
Head: head,
}
return r.Render(w, d, v)
}
// HTML builds up the response from the specified template and bindings.
func (r *Render) HTML(w io.Writer, status int, name string, binding interface{}, htmlOpt ...HTMLOptions) error {
r.templatesLk.Lock()
defer r.templatesLk.Unlock()
// If we are in development mode, recompile the templates on every HTML request.
if r.opt.IsDevelopment {
r.compileTemplates()
}
opt := r.prepareHTMLOptions(htmlOpt)
if tpl := r.templates.Lookup(name); tpl != nil {
if len(opt.Layout) > 0 {
tpl.Funcs(r.layoutFuncs(name, binding))
name = opt.Layout
}
if len(opt.Funcs) > 0 {
tpl.Funcs(opt.Funcs)
}
}
head := Head{
ContentType: r.opt.HTMLContentType + r.compiledCharset,
Status: status,
}
h := HTML{
Head: head,
Name: name,
Templates: r.templates,
}
return r.Render(w, h, binding)
}
// JSON marshals the given interface object and writes the JSON response.
func (r *Render) JSON(w io.Writer, status int, v interface{}) error {
head := Head{
ContentType: r.opt.JSONContentType + r.compiledCharset,
Status: status,
}
j := JSON{
Head: head,
Indent: r.opt.IndentJSON,
Prefix: r.opt.PrefixJSON,
UnEscapeHTML: r.opt.UnEscapeHTML,
StreamingJSON: r.opt.StreamingJSON,
}
return r.Render(w, j, v)
}
// JSONP marshals the given interface object and writes the JSON response.
func (r *Render) JSONP(w io.Writer, status int, callback string, v interface{}) error {
head := Head{
ContentType: r.opt.JSONPContentType + r.compiledCharset,
Status: status,
}
j := JSONP{
Head: head,
Indent: r.opt.IndentJSON,
Callback: callback,
}
return r.Render(w, j, v)
}
// Text writes out a string as plain text.
func (r *Render) Text(w io.Writer, status int, v string) error {
head := Head{
ContentType: r.opt.TextContentType + r.compiledCharset,
Status: status,
}
t := Text{
Head: head,
}
return r.Render(w, t, v)
}
// XML marshals the given interface object and writes the XML response.
func (r *Render) XML(w io.Writer, status int, v interface{}) error {
head := Head{
ContentType: r.opt.XMLContentType + r.compiledCharset,
Status: status,
}
x := XML{
Head: head,
Indent: r.opt.IndentXML,
Prefix: r.opt.PrefixXML,
}
return r.Render(w, x, v)
}