func (li *logInfo) SendHTTPError(w http.ResponseWriter, statusCode int, err error) {
	errorBody := http.StatusText(statusCode)
	if !li.instanceOpts.MaskInternalErrors || statusCode != http.StatusInternalServerError {
		errorBody += fmt.Sprintf("\n%v", err)
	}
	http.Error(w, errorBody, statusCode)
}
SendHTTPError generates a custom error page to give more information on why something didn't work
package ctfe
import (
	"context"
	"crypto"
	"crypto/sha256"
	"encoding/base64"
	"encoding/json"
	"errors"
	"flag"
	"fmt"
	"io/ioutil"
	"net/http"
	"strconv"
	"strings"
	"sync"
	"time"
	"github.com/golang/glog"
	"github.com/google/certificate-transparency-go/asn1"
	"github.com/google/certificate-transparency-go/tls"
	"github.com/google/certificate-transparency-go/trillian/util"
	"github.com/google/certificate-transparency-go/x509"
	"github.com/google/trillian"
	"github.com/google/trillian/monitoring"
	"github.com/google/trillian/types"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
	"google.golang.org/protobuf/encoding/prototext"
	ct "github.com/google/certificate-transparency-go"
)
var (
	alignGetEntries = flag.Bool("align_getentries", true, "Enable get-entries request alignment")
)
const (
	cacheControlHeader = "Cache-Control"
	cacheControlImmutable = "public, max-age=86400"
	contentTypeHeader string = "Content-Type"
	contentTypeJSON string = "application/json"
	jsonMapKeyCertificates string = "certificates"
	getEntriesParamStart = "start"
	getEntriesParamEnd = "end"
	getProofParamHash = "hash"
	getProofParamTreeSize = "tree_size"
	getSTHConsistencyParamFirst = "first"
	getSTHConsistencyParamSecond = "second"
	getEntryAndProofParamLeafIndex = "leaf_index"
	getEntryAndProofParamTreeSize = "tree_size"
)
var (
	MaxGetEntriesAllowed int64 = 1000
	emptyProof = make([][]byte, 0)
)
type EntrypointName string
const (
	AddChainName          = EntrypointName("AddChain")
	AddPreChainName       = EntrypointName("AddPreChain")
	GetSTHName            = EntrypointName("GetSTH")
	GetSTHConsistencyName = EntrypointName("GetSTHConsistency")
	GetProofByHashName    = EntrypointName("GetProofByHash")
	GetEntriesName        = EntrypointName("GetEntries")
	GetRootsName          = EntrypointName("GetRoots")
	GetEntryAndProofName  = EntrypointName("GetEntryAndProof")
)
var (
	once               sync.Once
	knownLogs          monitoring.Gauge      
	isMirrorLog        monitoring.Gauge      
	maxMergeDelay      monitoring.Gauge      
	expMergeDelay      monitoring.Gauge      
	lastSCTTimestamp   monitoring.Gauge      
	lastSTHTimestamp   monitoring.Gauge      
	lastSTHTreeSize    monitoring.Gauge      
	frozenSTHTimestamp monitoring.Gauge      
	reqsCounter        monitoring.Counter    
	rspsCounter        monitoring.Counter    
	rspLatency         monitoring.Histogram  
	alignedGetEntries  monitoring.Counter    
)
func setupMetrics(mf monitoring.MetricFactory) {
	knownLogs = mf.NewGauge("known_logs", "Set to 1 for known logs", "logid")
	isMirrorLog = mf.NewGauge("is_mirror", "Set to 1 for mirror logs", "logid")
	maxMergeDelay = mf.NewGauge("max_merge_delay", "Maximum Merge Delay in seconds", "logid")
	expMergeDelay = mf.NewGauge("expected_merge_delay", "Expected Merge Delay in seconds", "logid")
	lastSCTTimestamp = mf.NewGauge("last_sct_timestamp", "Time of last SCT in ms since epoch", "logid")
	lastSTHTimestamp = mf.NewGauge("last_sth_timestamp", "Time of last STH in ms since epoch", "logid")
	lastSTHTreeSize = mf.NewGauge("last_sth_treesize", "Size of tree at last STH", "logid")
	frozenSTHTimestamp = mf.NewGauge("frozen_sth_timestamp", "Time of the frozen STH in ms since epoch", "logid")
	reqsCounter = mf.NewCounter("http_reqs", "Number of requests", "logid", "ep")
	rspsCounter = mf.NewCounter("http_rsps", "Number of responses", "logid", "ep", "rc")
	rspLatency = mf.NewHistogram("http_latency", "Latency of responses in seconds", "logid", "ep", "rc")
	alignedGetEntries = mf.NewCounter("aligned_get_entries", "Number of get-entries requests which were aligned to size limit boundaries", "logid", "aligned")
}
var Entrypoints = []EntrypointName{AddChainName, AddPreChainName, GetSTHName, GetSTHConsistencyName, GetProofByHashName, GetEntriesName, GetRootsName, GetEntryAndProofName}
type PathHandlers map[string]AppHandler
type AppHandler struct {
	Info    *logInfo
	Handler func(context.Context, *logInfo, http.ResponseWriter, *http.Request) (int, error)
	Name    EntrypointName
	Method  string  
}
func (a AppHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	var statusCode int
	label0 := strconv.FormatInt(a.Info.logID, 10)
	label1 := string(a.Name)
	reqsCounter.Inc(label0, label1)
	startTime := a.Info.TimeSource.Now()
	logCtx := a.Info.RequestLog.Start(r.Context())
	a.Info.RequestLog.LogPrefix(logCtx, a.Info.LogPrefix)
	defer func() {
		latency := a.Info.TimeSource.Now().Sub(startTime).Seconds()
		rspLatency.Observe(latency, label0, label1, strconv.Itoa(statusCode))
	}()
	glog.V(2).Infof("%s: request %v %q => %s", a.Info.LogPrefix, r.Method, r.URL, a.Name)
	if r.Method != a.Method {
		glog.Warningf("%s: %s wrong HTTP method: %v", a.Info.LogPrefix, a.Name, r.Method)
		a.Info.SendHTTPError(w, http.StatusMethodNotAllowed, fmt.Errorf("method not allowed: %s", r.Method))
		a.Info.RequestLog.Status(logCtx, http.StatusMethodNotAllowed)
		return
	}
	if r.Method == http.MethodGet {
		if err := r.ParseForm(); err != nil {
			a.Info.SendHTTPError(w, http.StatusBadRequest, fmt.Errorf("failed to parse form data: %s", err))
			a.Info.RequestLog.Status(logCtx, http.StatusBadRequest)
			return
		}
	}
	ctx, cancel := context.WithDeadline(logCtx, getRPCDeadlineTime(a.Info))
	defer cancel()
	var err error
	statusCode, err = a.Handler(ctx, a.Info, w, r)
	a.Info.RequestLog.Status(ctx, statusCode)
	glog.V(2).Infof("%s: %s <= st=%d", a.Info.LogPrefix, a.Name, statusCode)
	rspsCounter.Inc(label0, label1, strconv.Itoa(statusCode))
	if err != nil {
		glog.Warningf("%s: %s handler error: %v", a.Info.LogPrefix, a.Name, err)
		a.Info.SendHTTPError(w, statusCode, err)
		return
	}
	if statusCode != http.StatusOK {
		glog.Warningf("%s: %s handler non 200 without error: %d %v", a.Info.LogPrefix, a.Name, statusCode, err)
		a.Info.SendHTTPError(w, http.StatusInternalServerError, fmt.Errorf("http handler misbehaved, st: %d", statusCode))
		return
	}
}
type CertValidationOpts struct {
	trustedRoots *PEMCertPool
	currentTime time.Time
	rejectExpired bool
	rejectUnexpired bool
	notAfterStart *time.Time
	notAfterLimit *time.Time
	acceptOnlyCA bool
	extKeyUsages []x509.ExtKeyUsage
	rejectExtIds []asn1.ObjectIdentifier
}
func NewCertValidationOpts(trustedRoots *PEMCertPool, currentTime time.Time, rejectExpired bool, rejectUnexpired bool, notAfterStart *time.Time, notAfterLimit *time.Time, acceptOnlyCA bool, extKeyUsages []x509.ExtKeyUsage) CertValidationOpts {
	var vOpts CertValidationOpts
	vOpts.trustedRoots = trustedRoots
	vOpts.currentTime = currentTime
	vOpts.rejectExpired = rejectExpired
	vOpts.rejectUnexpired = rejectUnexpired
	vOpts.notAfterStart = notAfterStart
	vOpts.notAfterLimit = notAfterLimit
	vOpts.acceptOnlyCA = acceptOnlyCA
	vOpts.extKeyUsages = extKeyUsages
	return vOpts
}
type logInfo struct {
	LogPrefix string
	TimeSource util.TimeSource
	RequestLog RequestLog
	instanceOpts InstanceOptions
	logID int64
	validationOpts CertValidationOpts
	rpcClient trillian.TrillianLogClient
	signer crypto.Signer
	sthGetter STHGetter
}
func newLogInfo(
	instanceOpts InstanceOptions,
	validationOpts CertValidationOpts,
	signer crypto.Signer,
	timeSource util.TimeSource,
) *logInfo {
	vCfg := instanceOpts.Validated
	cfg := vCfg.Config
	logID, prefix := cfg.LogId, cfg.Prefix
	li := &logInfo{
		logID:          logID,
		LogPrefix:      fmt.Sprintf("%s{%d}", prefix, logID),
		rpcClient:      instanceOpts.Client,
		signer:         signer,
		TimeSource:     timeSource,
		instanceOpts:   instanceOpts,
		validationOpts: validationOpts,
		RequestLog:     instanceOpts.RequestLog,
	}
	once.Do(func() { setupMetrics(instanceOpts.MetricFactory) })
	label := strconv.FormatInt(logID, 10)
	knownLogs.Set(1.0, label)
	switch {
	case vCfg.FrozenSTH != nil:
		li.sthGetter = &FrozenSTHGetter{sth: vCfg.FrozenSTH}
		frozenSTHTimestamp.Set(float64(vCfg.FrozenSTH.Timestamp), label)
	case cfg.IsMirror:
		st := instanceOpts.STHStorage
		if st == nil {
			st = DefaultMirrorSTHStorage{}
		}
		li.sthGetter = &MirrorSTHGetter{li: li, st: st}
	default:
		li.sthGetter = &LogSTHGetter{li: li}
	}
	if cfg.IsMirror {
		isMirrorLog.Set(1.0, label)
	}
	maxMergeDelay.Set(float64(cfg.MaxMergeDelaySec), label)
	expMergeDelay.Set(float64(cfg.ExpectedMergeDelaySec), label)
	return li
}
func (li *logInfo) Handlers(prefix string) PathHandlers {
	if !strings.HasPrefix(prefix, "/") {
		prefix = "/" + prefix
	}
	prefix = strings.TrimRight(prefix, "/")
	ph := PathHandlers{
		prefix + ct.AddChainPath:          AppHandler{Info: li, Handler: addChain, Name: AddChainName, Method: http.MethodPost},
		prefix + ct.AddPreChainPath:       AppHandler{Info: li, Handler: addPreChain, Name: AddPreChainName, Method: http.MethodPost},
		prefix + ct.GetSTHPath:            AppHandler{Info: li, Handler: getSTH, Name: GetSTHName, Method: http.MethodGet},
		prefix + ct.GetSTHConsistencyPath: AppHandler{Info: li, Handler: getSTHConsistency, Name: GetSTHConsistencyName, Method: http.MethodGet},
		prefix + ct.GetProofByHashPath:    AppHandler{Info: li, Handler: getProofByHash, Name: GetProofByHashName, Method: http.MethodGet},
		prefix + ct.GetEntriesPath:        AppHandler{Info: li, Handler: getEntries, Name: GetEntriesName, Method: http.MethodGet},
		prefix + ct.GetRootsPath:          AppHandler{Info: li, Handler: getRoots, Name: GetRootsName, Method: http.MethodGet},
		prefix + ct.GetEntryAndProofPath:  AppHandler{Info: li, Handler: getEntryAndProof, Name: GetEntryAndProofName, Method: http.MethodGet},
	}
	if li.instanceOpts.Validated.Config.IsMirror {
		delete(ph, prefix+ct.AddChainPath)
		delete(ph, prefix+ct.AddPreChainPath)
	}
	return ph
}