Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions cmd/dcrdata/internal/explorer/explorer.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@ package explorer

import (
"context"
"crypto/rand"
"fmt"
"math"
"math/big"
"net/http"
"os"
"os/signal"
Expand Down Expand Up @@ -201,6 +203,8 @@ type pageData struct {
BlockInfo *types.BlockInfo
BlockchainInfo *chainjson.GetBlockChainInfoResult
HomeInfo *types.HomeInfo
eTag string
lastModified time.Time
}

type explorerUI struct {
Expand Down Expand Up @@ -359,6 +363,7 @@ func New(cfg *ExplorerConfig) *explorerUI {
},
},
}
exp.resetETagAndLastModified()

log.Infof("Mean Voting Blocks calculated: %d", exp.pageData.HomeInfo.Params.MeanVotingBlocks)

Expand Down Expand Up @@ -447,6 +452,9 @@ func (exp *explorerUI) StoreMPData(_ *mempool.StakeData, _ []types.MempoolTx, in
exp.invsMtx.Lock()
exp.invs = inv
exp.invsMtx.Unlock()

exp.resetETagAndLastModified()

log.Debugf("Updated mempool details for the explorerUI.")
}

Expand Down Expand Up @@ -616,6 +624,8 @@ func (exp *explorerUI) Store(blockData *blockdata.BlockData, msgBlock *wire.MsgB
}()
}

exp.resetETagAndLastModified()

return nil
}

Expand All @@ -629,6 +639,21 @@ func (exp *explorerUI) ChartsUpdated() {
exp.pageData.Unlock()
}

// resetETagAndLastModified resets the eTag and last modified time to new
// values and is protected by the exp.pageData mutex.
func (exp *explorerUI) resetETagAndLastModified() {
exp.pageData.Lock()
exp.pageData.eTag = generateRandomString()
exp.pageData.lastModified = time.Now()
exp.pageData.Unlock()
}

func (exp *explorerUI) eTagAndLastModified() (eTag string, lastModified time.Time) {
exp.pageData.RLock()
defer exp.pageData.RUnlock()
return exp.pageData.eTag, exp.pageData.lastModified
}

func (exp *explorerUI) updateDevFundBalance() {
// yield processor to other goroutines
runtime.Gosched()
Expand Down Expand Up @@ -879,3 +904,16 @@ func indexPrice(index exchanges.CurrencyPair, indices map[string]map[exchanges.C
}
return price / nSources
}

const letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
const nLetters = len(letters)

// generateRandomString creates a random alphanumeric string of length 16.
func generateRandomString() string {
bytes := make([]byte, 16)
for i := range bytes {
num, _ := rand.Int(rand.Reader, big.NewInt(int64(nLetters)))
bytes[i] = letters[num.Int64()]
}
return string(bytes)
}
27 changes: 27 additions & 0 deletions cmd/dcrdata/internal/explorer/explorermiddleware.go
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,33 @@ func (exp *explorerUI) SyncStatusFileIntercept(next http.Handler) http.Handler {
})
}

// ETagAndLastModifiedIntercept handles ETag and Last-Modified headers for caching purposes.
func (exp *explorerUI) ETagAndLastModifiedIntercept(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
eTag, lastModified := exp.eTagAndLastModified()
if match := r.Header.Get("If-None-Match"); match != "" {
if match == eTag {
w.WriteHeader(http.StatusNotModified)
return
}
} else if modifiedSince := r.Header.Get("If-Modified-Since"); modifiedSince != "" {
if t, err := time.Parse(http.TimeFormat, modifiedSince); err == nil {
if lastModified.Before(t.Add(1 * time.Second)) {
w.WriteHeader(http.StatusNotModified)
return
}
}
}

// Set ETag and Last-Modified headers.
w.Header().Set("ETag", eTag)
w.Header().Set("Last-Modified", lastModified.Format(http.TimeFormat))
w.Header().Set("Cache-Control", "private")

next.ServeHTTP(w, r)
})
}

func getBlockHashCtx(r *http.Request) string {
hash, ok := r.Context().Value(ctxBlockHash).(string)
if !ok {
Expand Down
24 changes: 15 additions & 9 deletions cmd/dcrdata/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -761,23 +761,15 @@ func _main(ctx context.Context) error {
r.Get("/rejects", func(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, "/disapproved", http.StatusPermanentRedirect)
})
r.Get("/disapproved", explore.DisapprovedBlocks)
r.Get("/mempool", explore.Mempool)
r.Get("/parameters", explore.ParametersPage)
r.With(explore.BlockHashPathOrIndexCtx).Get("/block/{blockhash}", explore.Block)
r.With(explorer.TransactionHashCtx).Get("/tx/{txid}", explore.TxPage)
r.With(explorer.TransactionHashCtx, explorer.TransactionIoIndexCtx).Get("/tx/{txid}/{inout}/{inoutid}", explore.TxPage)
r.With(explorer.AddressPathCtx).Get("/address/{address}", explore.AddressPage)
r.With(explorer.AddressPathCtx).Get("/addresstable/{address}", explore.AddressTable)
r.Get("/treasury", explore.TreasuryPage)
r.Get("/treasurytable", explore.TreasuryTable)
r.Get("/agendas", explore.AgendasPage)
r.With(explorer.AgendaPathCtx).Get("/agenda/{agendaid}", explore.AgendaPage)
r.Get("/proposals", explore.ProposalsPage)
r.With(explorer.ProposalPathCtx).Get("/proposal/{proposaltoken}", explore.ProposalPage)
r.Get("/decodetx", explore.DecodeTxPage)
r.Get("/search", explore.Search)
r.Get("/charts", explore.Charts)
r.Get("/ticketpool", explore.Ticketpool)
r.Get("/market", explore.MarketPage)
r.Get("/stats", func(w http.ResponseWriter, r *http.Request) {
Expand All @@ -786,9 +778,23 @@ func _main(ctx context.Context) error {
// MenuFormParser will typically redirect, but going to the homepage as a
// fallback.
r.With(explorer.MenuFormParser).Post("/set", explore.Home)
r.Get("/attack-cost", explore.AttackCost)
r.Get("/verify-message", explore.VerifyMessagePage)
r.With(mw.Tollbooth(limiter)).Post("/verify-message", explore.VerifyMessageHandler)

// Pages that can be cached because they depend on block and/or mempool data cached by
// *explorer.explorerUI. This middleware sets ETag and Last-Modified headers that are
// reset if a new block or mempool change is detected.
withCache := r.With(explore.ETagAndLastModifiedIntercept)
withCache.Get("/", explore.Home)
withCache.Get("/disapproved", explore.DisapprovedBlocks)
withCache.Get("/mempool", explore.Mempool)
withCache.Get("/charts", explore.Charts)
withCache.Get("/treasury", explore.TreasuryPage)
withCache.Get("/treasurytable", explore.TreasuryTable)
withCache.Get("/parameters", explore.ParametersPage)
withCache.Get("/agendas", explore.AgendasPage)
withCache.With(explorer.AgendaPathCtx).Get("/agenda/{agendaid}", explore.AgendaPage)
withCache.Get("/attack-cost", explore.AttackCost)
})

// Configure a page for the bare "/insight" path. This mounts the static
Expand Down