/
apimiddleware.go
921 lines (835 loc) · 28.9 KB
/
apimiddleware.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
// Copyright (c) 2018-2019, The Bitum developers
// Copyright (c) 2017, The bitumdata developers
// See LICENSE for details.
package middleware
import (
"bytes"
"context"
"encoding/hex"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"net/url"
"strconv"
"strings"
"github.com/bitum-project/bitumd/chaincfg"
"github.com/bitum-project/bitumd/chaincfg/chainhash"
"github.com/bitum-project/bitumd/bitumjson"
"github.com/bitum-project/bitumd/bitumutil"
"github.com/bitum-project/bitumd/wire"
apitypes "github.com/bitum-project/bitumdata/api/types"
"github.com/go-chi/chi"
"github.com/go-chi/docgen"
)
type contextKey int
// These are the keys for different types of values stored in a request context.
const (
ctxAPIDocs contextKey = iota
CtxAddress
ctxBlockIndex0
ctxBlockIndex
ctxBlockStep
ctxBlockHash
ctxTxHash
ctxTxns
ctxTxInOutIndex
ctxN
ctxCount
ctxOffset
CtxBlockDate
CtxLimit
ctxGetStatus
ctxStakeVersionLatest
ctxRawHexTx
ctxM
ctxChartType
ctxChartGrouping
ctxTp
ctxAgendaId
ctxProposalToken
ctxXcToken
ctxStickWidth
)
type DataSource interface {
GetHeight() (int64, error)
GetBlockHeight(hash string) (int64, error)
GetBlockHash(idx int64) (string, error)
}
type StakeVersionsLatest func() (*bitumjson.StakeVersions, error)
// writeHTMLBadRequest is used for the Insight API error response for a BAD REQUEST.
// This means the request was malformed in some way or the request HASH,
// ADDRESS, BLOCK was not valid.
func writeHTMLBadRequest(w http.ResponseWriter, str string) {
w.Header().Set("Content-Type", "text/html; charset=utf-8")
w.WriteHeader(http.StatusBadRequest)
io.WriteString(w, str)
}
// writeHTMLNotFound is used for the Insight API response for an item NOT FOUND.
// This means the request was valid but no records were found for the item in
// question. For some endpoints responding with an empty array [] is expected
// such as a transaction query for addresses with no transactions.
func writeHTMLNotFound(w http.ResponseWriter, str string) {
w.Header().Set("Content-Type", "text/html; charset=utf-8")
w.WriteHeader(http.StatusNotFound)
io.WriteString(w, str)
}
// GetBlockStepCtx retrieves the ctxBlockStep data from the request context. If
// not set, the return value is -1.
func GetBlockStepCtx(r *http.Request) int {
step, ok := r.Context().Value(ctxBlockStep).(int)
if !ok {
apiLog.Error("block step is not set or is not an int")
return -1
}
return step
}
// GetBlockIndex0Ctx retrieves the ctxBlockIndex0 data from the request context.
// If not set, the return value is -1.
func GetBlockIndex0Ctx(r *http.Request) int {
idx, ok := r.Context().Value(ctxBlockIndex0).(int)
if !ok {
apiLog.Error("block index0 is not set or is not an int")
return -1
}
return idx
}
// GetTxIOIndexCtx retrieves the ctxTxInOutIndex data from the request context.
// If not set, the return value is -1.
func GetTxIOIndexCtx(r *http.Request) int {
index, ok := r.Context().Value(ctxTxInOutIndex).(int)
if !ok {
apiLog.Warn("txinoutindex is not set or is not an int")
return -1
}
return index
}
// GetNCtx retrieves the ctxN data from the request context. If not set, the
// return value is -1.
func GetNCtx(r *http.Request) int {
N, ok := r.Context().Value(ctxN).(int)
if !ok {
apiLog.Trace("N is not set or is not an int")
return -1
}
return N
}
// GetMCtx retrieves the ctxM data from the request context. If not set, the
// return value is -1.
func GetMCtx(r *http.Request) int {
M, ok := r.Context().Value(ctxM).(int)
if !ok {
apiLog.Trace("M is not set or is not an int")
return -1
}
return M
}
// GetTpCtx retrieves the ctxTp data from the request context.
// If the value is not set, an empty string is returned.
func GetTpCtx(r *http.Request) string {
tp, ok := r.Context().Value(ctxTp).(string)
if !ok {
apiLog.Trace("ticket pool interval not set")
return ""
}
return tp
}
// GetProposalTokenCtx retrieves the ctxProposalToken data from the request context.
// If the value is not set, an empty string is returned.
func GetProposalTokenCtx(r *http.Request) string {
tp, ok := r.Context().Value(ctxProposalToken).(string)
if !ok {
apiLog.Trace("proposal token hash not set")
return ""
}
return tp
}
// GetRawHexTx retrieves the ctxRawHexTx data from the request context. If not
// set, the return value is an empty string.
func GetRawHexTx(r *http.Request) (string, error) {
rawHexTx, ok := r.Context().Value(ctxRawHexTx).(string)
if !ok {
apiLog.Trace("hex transaction id not set")
return "", fmt.Errorf("hex transaction id not set")
}
msgtx := wire.NewMsgTx()
err := msgtx.Deserialize(hex.NewDecoder(strings.NewReader(rawHexTx)))
if err != nil {
return "", fmt.Errorf("failed to deserialize tx: %v", err)
}
return rawHexTx, nil
}
// OriginalRequestURI checks the X-Original-Request-URI HTTP request header for
// a valid URI, and patches the request URL's Path and RawPath. This may be
// useful in the event that a reverse proxy maps requests on path A to path B,
// but the request handler requires the original path A (e.g. generating links
// relative to path A).
func OriginalRequestURI(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
origRequestURI := r.Header.Get("X-Original-Request-URI")
if origRequestURI != "" {
// Create a temporary URL to parse and (un)escape the URI provided in
// the X-Original-Request-URI requests header.
newURL, err := url.Parse(origRequestURI)
if err != nil {
apiLog.Debugf("X-Original-Request-URI (%s) is not a valid URI: %v",
origRequestURI, err)
next.ServeHTTP(w, r)
return
}
// Patch the the Requests URL's Path and RawPath so that
// (*http.Request).URL.RequestURI() may assemble escaped path?query.
r.URL.Path = newURL.Path
r.URL.RawPath = newURL.RawPath
}
next.ServeHTTP(w, r)
})
}
// PostBroadcastTxCtx is middleware that checks for parameters given in POST
// request body of the broadcast transaction endpoint.
func PostBroadcastTxCtx(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
var req apitypes.InsightRawTx
body, err := ioutil.ReadAll(r.Body)
r.Body.Close()
if err != nil {
writeHTMLBadRequest(w, fmt.Sprintf("Error reading JSON message: %v", err))
return
}
err = json.Unmarshal(body, &req)
if err != nil {
writeHTMLBadRequest(w, fmt.Sprintf("Failed to parse request: %v", err))
return
}
// Successful extraction of Body JSON as long as the rawtx is not empty
// string we should return it.
if req.Rawtx == "" {
writeHTMLBadRequest(w, fmt.Sprintf("rawtx cannot be an empty string."))
return
}
ctx := context.WithValue(r.Context(), ctxRawHexTx, req.Rawtx)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
// GetTxIDCtx retrieves the ctxTxHash data from the request context. If not set,
// the return value is an empty string.
func GetTxIDCtx(r *http.Request) (*chainhash.Hash, error) {
hashStr, ok := r.Context().Value(ctxTxHash).(string)
if !ok {
apiLog.Trace("txid not set")
return nil, fmt.Errorf("txid not set")
}
hash, err := chainhash.NewHashFromStr(hashStr)
if err != nil {
apiLog.Trace("invalid hash '%s': %v", hashStr, err)
return nil, fmt.Errorf("invalid hash '%s': %v",
hashStr, err)
}
return hash, nil
}
// GetTxnsCtx retrieves the ctxTxns data from the request context. If not set,
// the return value is an empty string slice.
func GetTxnsCtx(r *http.Request) ([]*chainhash.Hash, error) {
hashStrs, ok := r.Context().Value(ctxTxns).([]string)
if !ok || len(hashStrs) == 0 {
apiLog.Trace("ctxTxns not set")
return nil, fmt.Errorf("ctxTxns not set")
}
hashes := make([]*chainhash.Hash, 0, len(hashStrs))
for _, hashStr := range hashStrs {
hash, err := chainhash.NewHashFromStr(hashStr)
if err != nil {
apiLog.Trace("invalid hash '%s': %v", hashStr, err)
return nil, fmt.Errorf("invalid hash '%s': %v", hashStr, err)
}
hashes = append(hashes, hash)
}
return hashes, nil
}
// Next is a dummy middleware that just continues with the next http.Handler.
func Next(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
next.ServeHTTP(w, r)
})
}
// PostTxnsCtx extract transaction IDs from the POST body
func PostTxnsCtx(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
req := apitypes.Txns{}
body, err := ioutil.ReadAll(r.Body)
r.Body.Close()
if err != nil {
apiLog.Debugf("No/invalid txns: %v", err)
http.Error(w, "error reading JSON message", http.StatusBadRequest)
return
}
err = json.Unmarshal(body, &req)
if err != nil {
apiLog.Debugf("failed to unmarshal JSON request to apitypes.Txns: %v", err)
http.Error(w, "failed to unmarshal JSON request", http.StatusBadRequest)
return
}
// Successful extraction of body JSON
ctx := context.WithValue(r.Context(), ctxTxns, req.Transactions)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
// ValidateTxnsPostCtx will confirm Post content length is valid.
func ValidateTxnsPostCtx(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
contentLengthString := r.Header.Get("Content-Length")
contentLength, err := strconv.Atoi(contentLengthString)
if err != nil {
http.Error(w, "Unable to parse Content-Length", http.StatusBadRequest)
return
}
// Broadcast Tx has the largest possible body.
maxPayload := 1 << 22
if contentLength > maxPayload {
http.Error(w, fmt.Sprintf("Maximum Content-Length is %d", maxPayload), http.StatusBadRequest)
return
}
next.ServeHTTP(w, r)
})
}
// GetBlockHashCtx retrieves the ctxBlockHash data from the request context. If
// not set, the return value is an empty string.
func GetBlockHashCtx(r *http.Request) (string, error) {
hash, ok := r.Context().Value(ctxBlockHash).(string)
if !ok {
apiLog.Trace("block hash not set")
return "", fmt.Errorf("block hash not set")
}
if _, err := chainhash.NewHashFromStr(hash); err != nil {
apiLog.Trace("invalid hash '%s': %v", hash, err)
return "", fmt.Errorf("invalid hash '%s': %v", hash, err)
}
return hash, nil
}
// GetAddressCtx retrieves the CtxAddress data from the request context. If not
// set, the return value is an empty string. The CtxAddress string data may be a
// comma-separated list of addresses, subject to the provided maximum number of
// addresses allowed. Duplicate addresses are removed, but the limit is enforced
// prior to removal of duplicates.
func GetAddressCtx(r *http.Request, activeNetParams *chaincfg.Params, maxAddrs int) ([]string, error) {
addressStr, ok := r.Context().Value(CtxAddress).(string)
if !ok || len(addressStr) == 0 {
apiLog.Trace("address not set")
return nil, fmt.Errorf("address not set")
}
addressStrs := strings.Split(addressStr, ",")
if len(addressStrs) > maxAddrs {
return nil, fmt.Errorf("maximum of %d addresses allowed", maxAddrs)
}
strInSlice := func(sl []string, s string) bool {
for i := range sl {
if sl[i] == s {
return true
}
}
return false
}
var addrStrs []string
for _, addrStr := range addressStrs {
address, err := bitumutil.DecodeAddress(addrStr)
if err != nil {
return nil, fmt.Errorf("invalid address '%v': %v",
addrStr, err)
}
if !address.IsForNet(activeNetParams) {
return nil, fmt.Errorf("%v is invalid for this network",
addrStr)
}
if strInSlice(addrStrs, addrStr) {
continue
}
addrStrs = append(addrStrs, addrStr)
}
return addrStrs, nil
}
// GetChartTypeCtx retrieves the ctxChart data from the request context.
// If not set, the return value is an empty string.
func GetChartTypeCtx(r *http.Request) string {
chartType, ok := r.Context().Value(ctxChartType).(string)
if !ok {
apiLog.Trace("chart type not set")
return ""
}
return chartType
}
// GetChartGroupingCtx retrieves the ctxChart data from the request context.
// If not set, the return value is an empty string.
func GetChartGroupingCtx(r *http.Request) string {
chartType, ok := r.Context().Value(ctxChartGrouping).(string)
if !ok {
apiLog.Trace("chart grouping not set")
return ""
}
return chartType
}
// GetCountCtx retrieves the ctxCount data ("to") URL path element from the
// request context. If not set, the return value is 20.
func GetCountCtx(r *http.Request) int {
count, ok := r.Context().Value(ctxCount).(int)
if !ok {
apiLog.Warn("count is not set or is not an int")
return 20
}
return count
}
// GetOffsetCtx retrieves the ctxOffset data ("from") from the request context.
// If not set, the return value is 0.
func GetOffsetCtx(r *http.Request) int {
offset, ok := r.Context().Value(ctxOffset).(int)
if !ok {
apiLog.Warn("offset is not set or is not an int")
return 0
}
return offset
}
// GetStatusInfoCtx retrieves the ctxGetStatus data ("q" POST form data) from
// the request context. If not set, the return value is an empty string.
func GetStatusInfoCtx(r *http.Request) string {
statusInfo, ok := r.Context().Value(ctxGetStatus).(string)
if !ok {
apiLog.Warn("status info is not set or is not a string")
return ""
}
return statusInfo
}
// GetBlockDateCtx retrieves the ctxBlockDate data from the request context. If
// not set, the return value is an empty string.
func GetBlockDateCtx(r *http.Request) string {
blockDate, _ := r.Context().Value(CtxBlockDate).(string)
return blockDate
}
// GetBlockIndexCtx retrieves the ctxBlockIndex data from the request context.
// If not set, the return -1.
func GetBlockIndexCtx(r *http.Request) int {
idx, ok := r.Context().Value(ctxBlockIndex).(int)
if !ok {
apiLog.Warn("block index not set or is not an int")
return -1
}
return idx
}
// CacheControl creates a new middleware to set the HTTP response header with
// "Cache-Control: max-age=maxAge" where maxAge is in seconds.
func CacheControl(maxAge int64) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Cache-Control", "max-age="+strconv.FormatInt(maxAge, 10))
next.ServeHTTP(w, r)
})
}
}
// BlockStepPathCtx returns a http.HandlerFunc that embeds the value at the url
// part {step} into the request context.
func BlockStepPathCtx(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
stepIdxStr := chi.URLParam(r, "step")
step, err := strconv.Atoi(stepIdxStr)
if err != nil {
apiLog.Infof("No/invalid step value (int64): %v", err)
http.NotFound(w, r)
return
}
ctx := context.WithValue(r.Context(), ctxBlockStep, step)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
// BlockIndexPathCtx returns a http.HandlerFunc that embeds the value at the url
// part {idx} into the request context.
func BlockIndexPathCtx(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
pathIdxStr := chi.URLParam(r, "idx")
idx, err := strconv.Atoi(pathIdxStr)
if err != nil {
apiLog.Infof("No/invalid idx value (int64): %v", err)
http.Error(w, "Valid index not provided", http.StatusBadRequest)
return
}
ctx := context.WithValue(r.Context(), ctxBlockIndex, idx)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
// BlockIndexOrHashPathCtx returns a http.HandlerFunc that embeds the value at
// the url part {idxorhash} into the request context.
func BlockIndexOrHashPathCtx(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
var ctx context.Context
pathIdxOrHashStr := chi.URLParam(r, "idxorhash")
if len(pathIdxOrHashStr) == 2*chainhash.HashSize {
ctx = context.WithValue(r.Context(), ctxBlockHash, pathIdxOrHashStr)
} else {
idx, err := strconv.Atoi(pathIdxOrHashStr)
if err != nil {
apiLog.Infof("No/invalid idx value (int64): %v", err)
http.Error(w, "Hash or index not provided", http.StatusBadRequest)
return
}
ctx = context.WithValue(r.Context(), ctxBlockIndex, idx)
}
next.ServeHTTP(w, r.WithContext(ctx))
})
}
// BlockIndex0PathCtx returns a http.HandlerFunc that embeds the value at the
// url part {idx0} into the request context.
func BlockIndex0PathCtx(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
pathIdxStr := chi.URLParam(r, "idx0")
idx, err := strconv.Atoi(pathIdxStr)
if err != nil {
apiLog.Infof("No/invalid idx0 value (int64): %v", err)
http.NotFound(w, r)
return
}
ctx := context.WithValue(r.Context(), ctxBlockIndex0, idx)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
// NPathCtx returns a http.HandlerFunc that embeds the value at the url part {N}
// into the request context.
func NPathCtx(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
pathNStr := chi.URLParam(r, "N")
N, err := strconv.Atoi(pathNStr)
if err != nil {
apiLog.Infof("No/invalid numeric value (uint64): %v", err)
http.NotFound(w, r)
return
}
ctx := context.WithValue(r.Context(), ctxN, N)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
// MPathCtx returns a http.HandlerFunc that embeds the value at the url
// part {M} into the request context
func MPathCtx(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
pathMStr := chi.URLParam(r, "M")
M, err := strconv.Atoi(pathMStr)
if err != nil {
apiLog.Infof("No/invalid numeric value (uint64): %v", err)
http.NotFound(w, r)
return
}
ctx := context.WithValue(r.Context(), ctxM, M)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
// TicketPoolCtx returns a http.HandlerFunc that embeds the value at the url
// part {tp} into the request context
func TicketPoolCtx(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
tp := chi.URLParam(r, "tp")
ctx := context.WithValue(r.Context(), ctxTp, tp)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
// ProposalTokenCtx returns a http.HandlerFunc that embeds the value at the url
// part {token} into the request context
func ProposalTokenCtx(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
token := chi.URLParam(r, "token")
ctx := context.WithValue(r.Context(), ctxProposalToken, token)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
// BlockHashPathCtx returns a http.HandlerFunc that embeds the value at the url
// part {blockhash} into the request context.
func BlockHashPathCtx(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
hash := chi.URLParam(r, "blockhash")
ctx := context.WithValue(r.Context(), ctxBlockHash, hash)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
// TransactionHashCtx returns a http.HandlerFunc that embeds the value at the
// url part {txid} into the request context.
func TransactionHashCtx(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
txid := chi.URLParam(r, "txid")
ctx := context.WithValue(r.Context(), ctxTxHash, txid)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
// TransactionIOIndexCtx returns a http.HandlerFunc that embeds the value at the
// url part {txinoutindex} into the request context
func TransactionIOIndexCtx(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
idxStr := chi.URLParam(r, "txinoutindex")
idx, err := strconv.Atoi(idxStr)
if err != nil {
apiLog.Infof("No/invalid numeric value (%v): %v", idxStr, err)
http.NotFound(w, r)
return
}
ctx := context.WithValue(r.Context(), ctxTxInOutIndex, idx)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
// AddressPathCtx returns a http.HandlerFunc that embeds the value at the url
// part {address} into the request context.
func AddressPathCtx(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
address := chi.URLParam(r, "address")
ctx := context.WithValue(r.Context(), CtxAddress, address)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
// ChartTypeCtx returns a http.HandlerFunc that embeds the value at the url
// part {charttype} into the request context.
func ChartTypeCtx(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx := context.WithValue(r.Context(), ctxChartType,
chi.URLParam(r, "charttype"))
next.ServeHTTP(w, r.WithContext(ctx))
})
}
// ChartGroupingCtx returns a http.HandlerFunc that embeds the value art the url
// part {chartgrouping} into the request context.
func ChartGroupingCtx(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx := context.WithValue(r.Context(), ctxChartGrouping,
chi.URLParam(r, "chartgrouping"))
next.ServeHTTP(w, r.WithContext(ctx))
})
}
// apiDocs generates a middleware with a "docs" in the context containing a map
// of the routers handlers, etc.
func apiDocs(mux *chi.Mux) func(next http.Handler) http.Handler {
var buf bytes.Buffer
err := json.Indent(&buf, []byte(docgen.JSONRoutesDoc(mux)), "", "\t")
if err != nil {
apiLog.Errorf("failed to prepare JSON routes docs: %v", err)
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
http.Error(w, http.StatusText(http.StatusInternalServerError),
http.StatusInternalServerError)
})
}
}
docs := buf.String()
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx := context.WithValue(r.Context(), ctxAPIDocs, docs)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
}
// APIDirectory is the actual handler used with apiDocs
// (e.g. mux.With(apiDocs(mux)).HandleFunc("/help", APIDirectory))
func APIDirectory(w http.ResponseWriter, r *http.Request) {
docs := r.Context().Value(ctxAPIDocs).(string)
io.WriteString(w, docs)
}
// TransactionsCtx returns a http.Handlerfunc that embeds the {address,
// blockhash} value in the request into the request context.
func TransactionsCtx(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
address := r.FormValue("address")
if address != "" {
ctx := context.WithValue(r.Context(), CtxAddress, address)
next.ServeHTTP(w, r.WithContext(ctx))
}
hash := r.FormValue("block")
if hash != "" {
ctx := context.WithValue(r.Context(), ctxBlockHash, hash)
next.ServeHTTP(w, r.WithContext(ctx))
}
})
}
// PaginationCtx returns a http.Handlerfunc that embeds the {to,from} value in
// the request into the request context.
func PaginationCtx(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
to, from := r.FormValue("to"), r.FormValue("from")
if to == "" {
to = "20"
}
if from == "" {
from = "0"
}
offset, err := strconv.Atoi(from)
if err != nil {
http.Error(w, "invalid from value", 422)
return
}
count, err := strconv.Atoi(to)
if err != nil {
http.Error(w, "invalid to value", 422)
return
}
ctx := context.WithValue(r.Context(), ctxCount, count)
ctx = context.WithValue(ctx, ctxOffset, offset)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
// AddressPostCtx returns a http.HandlerFunc that embeds the {addrs} value in
// the post request into the request context.
func AddressPostCtx(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
address := r.PostFormValue("addrs")
ctx := context.WithValue(r.Context(), CtxAddress, address)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
// BlockDateQueryCtx returns a http.Handlerfunc that embeds the {blockdate,
// limit} value in the request into the request context.
func BlockDateQueryCtx(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
blockDate := r.FormValue("blockDate")
limit := r.FormValue("limit")
if blockDate == "" {
http.Error(w, "invalid block date", 422)
return
}
fmt.Println("limit in block query ", limit)
ctx := context.WithValue(r.Context(), CtxBlockDate, blockDate)
ctx = context.WithValue(ctx, CtxLimit, limit)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
// AgendIdCtx returns a http.HandlerFunc that embeds the value at the url
// part {agendaId} into the request context.
func AgendIdCtx(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
agendaId := chi.URLParam(r, "agendaId")
ctx := context.WithValue(r.Context(), ctxAgendaId, agendaId)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
// GetAgendaIdCtx retrieves the ctxAgendaId data from the request context.
// If not set, the return value is an empty string.
func GetAgendaIdCtx(r *http.Request) string {
agendaId, ok := r.Context().Value(ctxAgendaId).(string)
if !ok {
apiLog.Error("agendaId not parsed")
return ""
}
return agendaId
}
// BlockHashPathAndIndexCtx embeds the value at the url part {blockhash}, and
// the corresponding block index, into a request context.
func BlockHashPathAndIndexCtx(r *http.Request, source DataSource) context.Context {
hash := chi.URLParam(r, "blockhash")
height, err := source.GetBlockHeight(hash)
if err != nil {
apiLog.Errorf("Unable to GetBlockHeight(%d): %v", height, err)
}
ctx := context.WithValue(r.Context(), ctxBlockHash, hash)
return context.WithValue(ctx, ctxBlockIndex, int(height)) // Must be int!
}
// StatusInfoCtx embeds the best block index and the POST form data for
// parameter "q" into a request context.
func StatusInfoCtx(r *http.Request, source DataSource) context.Context {
idx := int64(-1)
h, err := source.GetHeight()
if h >= 0 && err == nil {
idx = h
}
ctx := context.WithValue(r.Context(), ctxBlockIndex, int(idx)) // Must be int!
q := r.FormValue("q")
return context.WithValue(ctx, ctxGetStatus, q)
}
// BlockHashLatestCtx embeds the current block height and hash into a request
// context.
func BlockHashLatestCtx(r *http.Request, source DataSource) context.Context {
var hash string
// if hash, err = c.BlockData.GetBestBlockHash(int64(idx)); err != nil {
// apiLog.Errorf("Unable to GetBestBlockHash: %v", idx, err)
// }
idx, err := source.GetHeight()
if idx >= 0 && err == nil {
var err error
if hash, err = source.GetBlockHash(idx); err != nil {
apiLog.Errorf("Unable to GetBlockHash(%d): %v", idx, err)
}
}
ctx := context.WithValue(r.Context(), ctxBlockIndex, int(idx)) // Must be int!
return context.WithValue(ctx, ctxBlockHash, hash)
}
// StakeVersionLatestCtx embeds the specified StakeVersionsLatest function into
// a request context.
func StakeVersionLatestCtx(r *http.Request, stakeVerFun StakeVersionsLatest) context.Context {
ver := -1
stkVers, err := stakeVerFun()
if err == nil && stkVers != nil {
ver = int(stkVers.StakeVersion)
}
return context.WithValue(r.Context(), ctxStakeVersionLatest, ver)
}
// BlockIndexLatestCtx embeds the current block height into a request context.
func BlockIndexLatestCtx(r *http.Request, source DataSource) context.Context {
idx := int64(-1)
h, err := source.GetHeight()
if h >= 0 && err == nil {
idx = h
}
return context.WithValue(r.Context(), ctxBlockIndex, int(idx)) // Must be int!
}
// GetBlockHeightCtx returns the block height for the block index or hash
// specified on the URL path.
func GetBlockHeightCtx(r *http.Request, source DataSource) (int64, error) {
idxI, ok := r.Context().Value(ctxBlockIndex).(int)
idx := int64(idxI)
if !ok || idx < 0 {
hash, err := GetBlockHashCtx(r)
if err != nil {
return 0, err
}
idx, err = source.GetBlockHeight(hash)
if err != nil {
return 0, err
}
}
return idx, nil
}
// GetLatestVoteVersionCtx attempts to retrieve the latest stake version
// embedded in the request context.
func GetLatestVoteVersionCtx(r *http.Request) int {
ver, ok := r.Context().Value(ctxStakeVersionLatest).(int)
if !ok {
apiLog.Error("latest stake version is not set or is not an int")
return -1
}
return ver
}
// ExchangeTokenContext pulls the exchange token from the URL.
func ExchangeTokenContext(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
token := chi.URLParam(r, "token")
ctx := context.WithValue(r.Context(), ctxXcToken, token)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
// RetrieveExchangeTokenCtx tries to fetch the exchange token from the request
// context.
func RetrieveExchangeTokenCtx(r *http.Request) string {
token, ok := r.Context().Value(ctxXcToken).(string)
if !ok {
apiLog.Error("non-string encountered in exchange token context")
return ""
}
return token
}
// ExchangeTokenContext pulls the bin width from the URL.
func StickWidthContext(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
bin := chi.URLParam(r, "bin")
ctx := context.WithValue(r.Context(), ctxStickWidth, bin)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
// RetrieveStickWidthCtx tries to fetch the candlestick width from the request
// context.
func RetrieveStickWidthCtx(r *http.Request) string {
bin, ok := r.Context().Value(ctxStickWidth).(string)
if !ok {
apiLog.Error("non-string encountered in candlestick width context")
return ""
}
return bin
}