The code and data behind xeiaso.net
5
fork

Configure Feed

Select the types of activity you want to include in your feed.

cmd/patreon-saasproxy: use twirp/protobuf instead of yolo json

Signed-off-by: Xe Iaso <me@xeiaso.net>

Xe Iaso e02b9ef3 0eb26108

+3202 -13
+22
cmd/patreon-saasproxy/main.go
··· 1 1 package main 2 2 3 3 import ( 4 + "context" 4 5 "encoding/base64" 5 6 "encoding/json" 6 7 "expvar" ··· 14 15 "github.com/facebookgo/flagenv" 15 16 _ "github.com/joho/godotenv/autoload" 16 17 "golang.org/x/oauth2" 18 + "google.golang.org/protobuf/types/known/emptypb" 19 + "google.golang.org/protobuf/types/known/timestamppb" 17 20 "gopkg.in/mxpv/patreon-go.v1" 18 21 "tailscale.com/client/tailscale" 19 22 "tailscale.com/hostinfo" ··· 21 24 "tailscale.com/tsnet" 22 25 "tailscale.com/tsweb" 23 26 "xeiaso.net/v4/internal" 27 + "xeiaso.net/v4/internal/adminpb" 24 28 ) 25 29 26 30 var ( ··· 110 114 log.Fatal(err) 111 115 } 112 116 117 + ph := adminpb.NewPatreonServer(s) 118 + http.Handle(adminpb.PatreonPathPrefix, ph) 119 + 113 120 slog.Info("listening over tailscale", "hostname", *tailscaleHostname) 114 121 115 122 log.Fatal(http.Serve(ln, nil)) ··· 118 125 type Server struct { 119 126 lc *tailscale.LocalClient 120 127 cts oauth2.TokenSource 128 + } 129 + 130 + func (s *Server) GetToken(ctx context.Context, _ *emptypb.Empty) (*adminpb.PatreonToken, error) { 131 + token, err := s.cts.Token() 132 + if err != nil { 133 + slog.Error("token fetch failed", "err", err) 134 + return nil, err 135 + } 136 + 137 + return &adminpb.PatreonToken{ 138 + AccessToken: token.AccessToken, 139 + TokenType: token.TokenType, 140 + RefreshToken: token.RefreshToken, 141 + Expiry: timestamppb.New(token.Expiry), 142 + }, nil 121 143 } 122 144 123 145 func (s *Server) GiveToken(w http.ResponseWriter, r *http.Request) {
+4
flake.nix
··· 192 192 zig 193 193 nodejs 194 194 195 + protobuf 196 + protoc-gen-go 197 + protoc-gen-twirp 198 + 195 199 jq 196 200 jo 197 201
+3 -2
go.mod
··· 6 6 github.com/bep/debounce v1.2.1 7 7 github.com/donatj/hmacsig v1.1.0 8 8 github.com/facebookgo/flagenv v0.0.0-20160425205200-fcd59fca7456 9 + github.com/go-faker/faker/v4 v4.2.0 9 10 github.com/go-git/go-git/v5 v5.10.0 10 11 github.com/joho/godotenv v1.5.1 12 + github.com/twitchtv/twirp v8.1.3+incompatible 11 13 golang.org/x/oauth2 v0.12.0 14 + google.golang.org/protobuf v1.31.0 12 15 gopkg.in/fsnotify.v1 v1.4.7 13 16 gopkg.in/mxpv/patreon-go.v1 v1.0.0-20171031001022-1d2f253ac700 14 17 tailscale.com v1.58.2 ··· 48 51 github.com/facebookgo/ensure v0.0.0-20200202191622-63f1cf65ac4c // indirect 49 52 github.com/facebookgo/subset v0.0.0-20200203212716-c811ad88dec4 // indirect 50 53 github.com/fxamacker/cbor/v2 v2.5.0 // indirect 51 - github.com/go-faker/faker/v4 v4.2.0 // indirect 52 54 github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect 53 55 github.com/go-git/go-billy/v5 v5.5.0 // indirect 54 56 github.com/go-ole/go-ole v1.3.0 // indirect ··· 117 119 golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect 118 120 golang.zx2c4.com/wireguard/windows v0.5.3 // indirect 119 121 google.golang.org/appengine v1.6.8 // indirect 120 - google.golang.org/protobuf v1.31.0 // indirect 121 122 gopkg.in/warnings.v0 v0.1.2 // indirect 122 123 gvisor.dev/gvisor v0.0.0-20230928000133-4fe30062272c // indirect 123 124 inet.af/peercred v0.0.0-20210906144145-0893ea02156a // indirect
+2
go.sum
··· 276 276 github.com/tailscale/wireguard-go v0.0.0-20231121184858-cc193a0b3272/go.mod h1:BOm5fXUBFM+m9woLNBoxI9TaBXXhGNP50LX/TGIvGb4= 277 277 github.com/tcnksm/go-httpstat v0.2.0 h1:rP7T5e5U2HfmOBmZzGgGZjBQ5/GluWUylujl0tJ04I0= 278 278 github.com/tcnksm/go-httpstat v0.2.0/go.mod h1:s3JVJFtQxtBEBC9dwcdTTXS9xFnM3SXAZwPG41aurT8= 279 + github.com/twitchtv/twirp v8.1.3+incompatible h1:+F4TdErPgSUbMZMwp13Q/KgDVuI7HJXP61mNV3/7iuU= 280 + github.com/twitchtv/twirp v8.1.3+incompatible/go.mod h1:RRJoFSAmTEh2weEqWtpPE3vFK5YBhA6bqp2l1kfCC5A= 279 281 github.com/u-root/u-root v0.11.0 h1:6gCZLOeRyevw7gbTwMj3fKxnr9+yHFlgF3N7udUVNO8= 280 282 github.com/u-root/u-root v0.11.0/go.mod h1:DBkDtiZyONk9hzVEdB/PWI9B4TxDkElWlVTHseglrZY= 281 283 github.com/u-root/uio v0.0.0-20230305220412-3e8cd9d6bf63 h1:YcojQL98T/OO+rybuzn2+5KrD5dBwXIvYBvQ2cD3Avg=
+3
gomod2nix.toml
··· 259 259 [mod."github.com/tcnksm/go-httpstat"] 260 260 version = "v0.2.0" 261 261 hash = "sha256-bCWn8E+DcZY6+yPu07AF3hCcDZx3CFdD74qfpDIgVqI=" 262 + [mod."github.com/twitchtv/twirp"] 263 + version = "v8.1.3+incompatible" 264 + hash = "sha256-j1h9YE3wl9h36DfPf92To1H/PwNk4CgerOARNO3HK1w=" 262 265 [mod."github.com/u-root/uio"] 263 266 version = "v0.0.0-20230305220412-3e8cd9d6bf63" 264 267 hash = "sha256-y0VT9PLROozi6wNMgnX706ifumQxlMc8y4/XZDhdfMY="
+5
internal/adminpb/generate.go
··· 1 + package adminpb 2 + 3 + func init() {} 4 + 5 + //go:generate protoc --proto_path=. --proto_path=../../pb --go_out=. --go_opt=paths=source_relative --twirp_out=. --twirp_opt=paths=source_relative internal.proto
+201
internal/adminpb/internal.pb.go
··· 1 + // Code generated by protoc-gen-go. DO NOT EDIT. 2 + // versions: 3 + // protoc-gen-go v1.32.0 4 + // protoc v4.24.4 5 + // source: internal.proto 6 + 7 + package adminpb 8 + 9 + import ( 10 + protoreflect "google.golang.org/protobuf/reflect/protoreflect" 11 + protoimpl "google.golang.org/protobuf/runtime/protoimpl" 12 + emptypb "google.golang.org/protobuf/types/known/emptypb" 13 + timestamppb "google.golang.org/protobuf/types/known/timestamppb" 14 + reflect "reflect" 15 + sync "sync" 16 + pb "xeiaso.net/v4/pb" 17 + ) 18 + 19 + const ( 20 + // Verify that this generated code is sufficiently up-to-date. 21 + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) 22 + // Verify that runtime/protoimpl is sufficiently up-to-date. 23 + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) 24 + ) 25 + 26 + type PatreonToken struct { 27 + state protoimpl.MessageState 28 + sizeCache protoimpl.SizeCache 29 + unknownFields protoimpl.UnknownFields 30 + 31 + AccessToken string `protobuf:"bytes,1,opt,name=access_token,json=accessToken,proto3" json:"access_token,omitempty"` 32 + TokenType string `protobuf:"bytes,2,opt,name=token_type,json=tokenType,proto3" json:"token_type,omitempty"` 33 + RefreshToken string `protobuf:"bytes,3,opt,name=refresh_token,json=refreshToken,proto3" json:"refresh_token,omitempty"` 34 + Expiry *timestamppb.Timestamp `protobuf:"bytes,4,opt,name=expiry,proto3" json:"expiry,omitempty"` 35 + } 36 + 37 + func (x *PatreonToken) Reset() { 38 + *x = PatreonToken{} 39 + if protoimpl.UnsafeEnabled { 40 + mi := &file_internal_proto_msgTypes[0] 41 + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 42 + ms.StoreMessageInfo(mi) 43 + } 44 + } 45 + 46 + func (x *PatreonToken) String() string { 47 + return protoimpl.X.MessageStringOf(x) 48 + } 49 + 50 + func (*PatreonToken) ProtoMessage() {} 51 + 52 + func (x *PatreonToken) ProtoReflect() protoreflect.Message { 53 + mi := &file_internal_proto_msgTypes[0] 54 + if protoimpl.UnsafeEnabled && x != nil { 55 + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 56 + if ms.LoadMessageInfo() == nil { 57 + ms.StoreMessageInfo(mi) 58 + } 59 + return ms 60 + } 61 + return mi.MessageOf(x) 62 + } 63 + 64 + // Deprecated: Use PatreonToken.ProtoReflect.Descriptor instead. 65 + func (*PatreonToken) Descriptor() ([]byte, []int) { 66 + return file_internal_proto_rawDescGZIP(), []int{0} 67 + } 68 + 69 + func (x *PatreonToken) GetAccessToken() string { 70 + if x != nil { 71 + return x.AccessToken 72 + } 73 + return "" 74 + } 75 + 76 + func (x *PatreonToken) GetTokenType() string { 77 + if x != nil { 78 + return x.TokenType 79 + } 80 + return "" 81 + } 82 + 83 + func (x *PatreonToken) GetRefreshToken() string { 84 + if x != nil { 85 + return x.RefreshToken 86 + } 87 + return "" 88 + } 89 + 90 + func (x *PatreonToken) GetExpiry() *timestamppb.Timestamp { 91 + if x != nil { 92 + return x.Expiry 93 + } 94 + return nil 95 + } 96 + 97 + var File_internal_proto protoreflect.FileDescriptor 98 + 99 + var file_internal_proto_rawDesc = []byte{ 100 + 0x0a, 0x0e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 101 + 0x12, 0x13, 0x78, 0x65, 0x69, 0x61, 0x73, 0x6f, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x69, 0x6e, 0x74, 102 + 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x1a, 0x1b, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 103 + 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x65, 0x6d, 0x70, 0x74, 0x79, 0x2e, 0x70, 0x72, 0x6f, 104 + 0x74, 0x6f, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 105 + 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 106 + 0x6f, 0x74, 0x6f, 0x1a, 0x0c, 0x78, 0x65, 0x73, 0x69, 0x74, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 107 + 0x6f, 0x22, 0xa9, 0x01, 0x0a, 0x0c, 0x50, 0x61, 0x74, 0x72, 0x65, 0x6f, 0x6e, 0x54, 0x6f, 0x6b, 108 + 0x65, 0x6e, 0x12, 0x21, 0x0a, 0x0c, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x5f, 0x74, 0x6f, 0x6b, 109 + 0x65, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 110 + 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x1d, 0x0a, 0x0a, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x5f, 0x74, 111 + 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 112 + 0x54, 0x79, 0x70, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x5f, 113 + 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x72, 0x65, 0x66, 114 + 0x72, 0x65, 0x73, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x32, 0x0a, 0x06, 0x65, 0x78, 0x70, 115 + 0x69, 0x72, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 116 + 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 117 + 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x06, 0x65, 0x78, 0x70, 0x69, 0x72, 0x79, 0x32, 0x50, 0x0a, 118 + 0x07, 0x50, 0x61, 0x74, 0x72, 0x65, 0x6f, 0x6e, 0x12, 0x45, 0x0a, 0x08, 0x47, 0x65, 0x74, 0x54, 119 + 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 120 + 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x21, 0x2e, 0x78, 121 + 0x65, 0x69, 0x61, 0x73, 0x6f, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 122 + 0x61, 0x6c, 0x2e, 0x50, 0x61, 0x74, 0x72, 0x65, 0x6f, 0x6e, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x32, 123 + 0x41, 0x0a, 0x05, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x12, 0x38, 0x0a, 0x07, 0x52, 0x65, 0x62, 0x75, 124 + 0x69, 0x6c, 0x64, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 125 + 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x15, 0x2e, 0x78, 0x65, 126 + 0x69, 0x61, 0x73, 0x6f, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x49, 0x6e, 127 + 0x66, 0x6f, 0x42, 0x20, 0x5a, 0x1e, 0x78, 0x65, 0x69, 0x61, 0x73, 0x6f, 0x2e, 0x6e, 0x65, 0x74, 128 + 0x2f, 0x76, 0x34, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x61, 0x64, 0x6d, 129 + 0x69, 0x6e, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, 130 + } 131 + 132 + var ( 133 + file_internal_proto_rawDescOnce sync.Once 134 + file_internal_proto_rawDescData = file_internal_proto_rawDesc 135 + ) 136 + 137 + func file_internal_proto_rawDescGZIP() []byte { 138 + file_internal_proto_rawDescOnce.Do(func() { 139 + file_internal_proto_rawDescData = protoimpl.X.CompressGZIP(file_internal_proto_rawDescData) 140 + }) 141 + return file_internal_proto_rawDescData 142 + } 143 + 144 + var file_internal_proto_msgTypes = make([]protoimpl.MessageInfo, 1) 145 + var file_internal_proto_goTypes = []interface{}{ 146 + (*PatreonToken)(nil), // 0: xeiaso.net.internal.PatreonToken 147 + (*timestamppb.Timestamp)(nil), // 1: google.protobuf.Timestamp 148 + (*emptypb.Empty)(nil), // 2: google.protobuf.Empty 149 + (*pb.BuildInfo)(nil), // 3: xeiaso.net.BuildInfo 150 + } 151 + var file_internal_proto_depIdxs = []int32{ 152 + 1, // 0: xeiaso.net.internal.PatreonToken.expiry:type_name -> google.protobuf.Timestamp 153 + 2, // 1: xeiaso.net.internal.Patreon.GetToken:input_type -> google.protobuf.Empty 154 + 2, // 2: xeiaso.net.internal.Admin.Rebuild:input_type -> google.protobuf.Empty 155 + 0, // 3: xeiaso.net.internal.Patreon.GetToken:output_type -> xeiaso.net.internal.PatreonToken 156 + 3, // 4: xeiaso.net.internal.Admin.Rebuild:output_type -> xeiaso.net.BuildInfo 157 + 3, // [3:5] is the sub-list for method output_type 158 + 1, // [1:3] is the sub-list for method input_type 159 + 1, // [1:1] is the sub-list for extension type_name 160 + 1, // [1:1] is the sub-list for extension extendee 161 + 0, // [0:1] is the sub-list for field type_name 162 + } 163 + 164 + func init() { file_internal_proto_init() } 165 + func file_internal_proto_init() { 166 + if File_internal_proto != nil { 167 + return 168 + } 169 + if !protoimpl.UnsafeEnabled { 170 + file_internal_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { 171 + switch v := v.(*PatreonToken); i { 172 + case 0: 173 + return &v.state 174 + case 1: 175 + return &v.sizeCache 176 + case 2: 177 + return &v.unknownFields 178 + default: 179 + return nil 180 + } 181 + } 182 + } 183 + type x struct{} 184 + out := protoimpl.TypeBuilder{ 185 + File: protoimpl.DescBuilder{ 186 + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), 187 + RawDescriptor: file_internal_proto_rawDesc, 188 + NumEnums: 0, 189 + NumMessages: 1, 190 + NumExtensions: 0, 191 + NumServices: 2, 192 + }, 193 + GoTypes: file_internal_proto_goTypes, 194 + DependencyIndexes: file_internal_proto_depIdxs, 195 + MessageInfos: file_internal_proto_msgTypes, 196 + }.Build() 197 + File_internal_proto = out.File 198 + file_internal_proto_rawDesc = nil 199 + file_internal_proto_goTypes = nil 200 + file_internal_proto_depIdxs = nil 201 + }
+22
internal/adminpb/internal.proto
··· 1 + syntax = "proto3"; 2 + package xeiaso.net.internal.admin; 3 + option go_package = "xeiaso.net/v4/internal/adminpb"; 4 + 5 + import "google/protobuf/empty.proto"; 6 + import "google/protobuf/timestamp.proto"; 7 + import "xesite.proto"; 8 + 9 + service Patreon { 10 + rpc GetToken (google.protobuf.Empty) returns (PatreonToken); 11 + } 12 + 13 + message PatreonToken { 14 + string access_token = 1; 15 + string token_type = 2; 16 + string refresh_token = 3; 17 + google.protobuf.Timestamp expiry = 4; 18 + } 19 + 20 + service Admin { 21 + rpc Rebuild(google.protobuf.Empty) returns (xeiaso.net.BuildInfo); 22 + }
+1607
internal/adminpb/internal.twirp.go
··· 1 + // Code generated by protoc-gen-twirp v8.1.3, DO NOT EDIT. 2 + // source: internal.proto 3 + 4 + package adminpb 5 + 6 + import context "context" 7 + import fmt "fmt" 8 + import http "net/http" 9 + import io "io" 10 + import json "encoding/json" 11 + import strconv "strconv" 12 + import strings "strings" 13 + 14 + import protojson "google.golang.org/protobuf/encoding/protojson" 15 + import proto "google.golang.org/protobuf/proto" 16 + import twirp "github.com/twitchtv/twirp" 17 + import ctxsetters "github.com/twitchtv/twirp/ctxsetters" 18 + 19 + import google_protobuf "google.golang.org/protobuf/types/known/emptypb" 20 + import xeiaso_net "xeiaso.net/v4/pb" 21 + 22 + import bytes "bytes" 23 + import errors "errors" 24 + import path "path" 25 + import url "net/url" 26 + 27 + // Version compatibility assertion. 28 + // If the constant is not defined in the package, that likely means 29 + // the package needs to be updated to work with this generated code. 30 + // See https://twitchtv.github.io/twirp/docs/version_matrix.html 31 + const _ = twirp.TwirpPackageMinVersion_8_1_0 32 + 33 + // ================= 34 + // Patreon Interface 35 + // ================= 36 + 37 + type Patreon interface { 38 + GetToken(context.Context, *google_protobuf.Empty) (*PatreonToken, error) 39 + } 40 + 41 + // ======================= 42 + // Patreon Protobuf Client 43 + // ======================= 44 + 45 + type patreonProtobufClient struct { 46 + client HTTPClient 47 + urls [1]string 48 + interceptor twirp.Interceptor 49 + opts twirp.ClientOptions 50 + } 51 + 52 + // NewPatreonProtobufClient creates a Protobuf client that implements the Patreon interface. 53 + // It communicates using Protobuf and can be configured with a custom HTTPClient. 54 + func NewPatreonProtobufClient(baseURL string, client HTTPClient, opts ...twirp.ClientOption) Patreon { 55 + if c, ok := client.(*http.Client); ok { 56 + client = withoutRedirects(c) 57 + } 58 + 59 + clientOpts := twirp.ClientOptions{} 60 + for _, o := range opts { 61 + o(&clientOpts) 62 + } 63 + 64 + // Using ReadOpt allows backwards and forwards compatibility with new options in the future 65 + literalURLs := false 66 + _ = clientOpts.ReadOpt("literalURLs", &literalURLs) 67 + var pathPrefix string 68 + if ok := clientOpts.ReadOpt("pathPrefix", &pathPrefix); !ok { 69 + pathPrefix = "/twirp" // default prefix 70 + } 71 + 72 + // Build method URLs: <baseURL>[<prefix>]/<package>.<Service>/<Method> 73 + serviceURL := sanitizeBaseURL(baseURL) 74 + serviceURL += baseServicePath(pathPrefix, "xeiaso.net.internal", "Patreon") 75 + urls := [1]string{ 76 + serviceURL + "GetToken", 77 + } 78 + 79 + return &patreonProtobufClient{ 80 + client: client, 81 + urls: urls, 82 + interceptor: twirp.ChainInterceptors(clientOpts.Interceptors...), 83 + opts: clientOpts, 84 + } 85 + } 86 + 87 + func (c *patreonProtobufClient) GetToken(ctx context.Context, in *google_protobuf.Empty) (*PatreonToken, error) { 88 + ctx = ctxsetters.WithPackageName(ctx, "xeiaso.net.internal") 89 + ctx = ctxsetters.WithServiceName(ctx, "Patreon") 90 + ctx = ctxsetters.WithMethodName(ctx, "GetToken") 91 + caller := c.callGetToken 92 + if c.interceptor != nil { 93 + caller = func(ctx context.Context, req *google_protobuf.Empty) (*PatreonToken, error) { 94 + resp, err := c.interceptor( 95 + func(ctx context.Context, req interface{}) (interface{}, error) { 96 + typedReq, ok := req.(*google_protobuf.Empty) 97 + if !ok { 98 + return nil, twirp.InternalError("failed type assertion req.(*google_protobuf.Empty) when calling interceptor") 99 + } 100 + return c.callGetToken(ctx, typedReq) 101 + }, 102 + )(ctx, req) 103 + if resp != nil { 104 + typedResp, ok := resp.(*PatreonToken) 105 + if !ok { 106 + return nil, twirp.InternalError("failed type assertion resp.(*PatreonToken) when calling interceptor") 107 + } 108 + return typedResp, err 109 + } 110 + return nil, err 111 + } 112 + } 113 + return caller(ctx, in) 114 + } 115 + 116 + func (c *patreonProtobufClient) callGetToken(ctx context.Context, in *google_protobuf.Empty) (*PatreonToken, error) { 117 + out := new(PatreonToken) 118 + ctx, err := doProtobufRequest(ctx, c.client, c.opts.Hooks, c.urls[0], in, out) 119 + if err != nil { 120 + twerr, ok := err.(twirp.Error) 121 + if !ok { 122 + twerr = twirp.InternalErrorWith(err) 123 + } 124 + callClientError(ctx, c.opts.Hooks, twerr) 125 + return nil, err 126 + } 127 + 128 + callClientResponseReceived(ctx, c.opts.Hooks) 129 + 130 + return out, nil 131 + } 132 + 133 + // =================== 134 + // Patreon JSON Client 135 + // =================== 136 + 137 + type patreonJSONClient struct { 138 + client HTTPClient 139 + urls [1]string 140 + interceptor twirp.Interceptor 141 + opts twirp.ClientOptions 142 + } 143 + 144 + // NewPatreonJSONClient creates a JSON client that implements the Patreon interface. 145 + // It communicates using JSON and can be configured with a custom HTTPClient. 146 + func NewPatreonJSONClient(baseURL string, client HTTPClient, opts ...twirp.ClientOption) Patreon { 147 + if c, ok := client.(*http.Client); ok { 148 + client = withoutRedirects(c) 149 + } 150 + 151 + clientOpts := twirp.ClientOptions{} 152 + for _, o := range opts { 153 + o(&clientOpts) 154 + } 155 + 156 + // Using ReadOpt allows backwards and forwards compatibility with new options in the future 157 + literalURLs := false 158 + _ = clientOpts.ReadOpt("literalURLs", &literalURLs) 159 + var pathPrefix string 160 + if ok := clientOpts.ReadOpt("pathPrefix", &pathPrefix); !ok { 161 + pathPrefix = "/twirp" // default prefix 162 + } 163 + 164 + // Build method URLs: <baseURL>[<prefix>]/<package>.<Service>/<Method> 165 + serviceURL := sanitizeBaseURL(baseURL) 166 + serviceURL += baseServicePath(pathPrefix, "xeiaso.net.internal", "Patreon") 167 + urls := [1]string{ 168 + serviceURL + "GetToken", 169 + } 170 + 171 + return &patreonJSONClient{ 172 + client: client, 173 + urls: urls, 174 + interceptor: twirp.ChainInterceptors(clientOpts.Interceptors...), 175 + opts: clientOpts, 176 + } 177 + } 178 + 179 + func (c *patreonJSONClient) GetToken(ctx context.Context, in *google_protobuf.Empty) (*PatreonToken, error) { 180 + ctx = ctxsetters.WithPackageName(ctx, "xeiaso.net.internal") 181 + ctx = ctxsetters.WithServiceName(ctx, "Patreon") 182 + ctx = ctxsetters.WithMethodName(ctx, "GetToken") 183 + caller := c.callGetToken 184 + if c.interceptor != nil { 185 + caller = func(ctx context.Context, req *google_protobuf.Empty) (*PatreonToken, error) { 186 + resp, err := c.interceptor( 187 + func(ctx context.Context, req interface{}) (interface{}, error) { 188 + typedReq, ok := req.(*google_protobuf.Empty) 189 + if !ok { 190 + return nil, twirp.InternalError("failed type assertion req.(*google_protobuf.Empty) when calling interceptor") 191 + } 192 + return c.callGetToken(ctx, typedReq) 193 + }, 194 + )(ctx, req) 195 + if resp != nil { 196 + typedResp, ok := resp.(*PatreonToken) 197 + if !ok { 198 + return nil, twirp.InternalError("failed type assertion resp.(*PatreonToken) when calling interceptor") 199 + } 200 + return typedResp, err 201 + } 202 + return nil, err 203 + } 204 + } 205 + return caller(ctx, in) 206 + } 207 + 208 + func (c *patreonJSONClient) callGetToken(ctx context.Context, in *google_protobuf.Empty) (*PatreonToken, error) { 209 + out := new(PatreonToken) 210 + ctx, err := doJSONRequest(ctx, c.client, c.opts.Hooks, c.urls[0], in, out) 211 + if err != nil { 212 + twerr, ok := err.(twirp.Error) 213 + if !ok { 214 + twerr = twirp.InternalErrorWith(err) 215 + } 216 + callClientError(ctx, c.opts.Hooks, twerr) 217 + return nil, err 218 + } 219 + 220 + callClientResponseReceived(ctx, c.opts.Hooks) 221 + 222 + return out, nil 223 + } 224 + 225 + // ====================== 226 + // Patreon Server Handler 227 + // ====================== 228 + 229 + type patreonServer struct { 230 + Patreon 231 + interceptor twirp.Interceptor 232 + hooks *twirp.ServerHooks 233 + pathPrefix string // prefix for routing 234 + jsonSkipDefaults bool // do not include unpopulated fields (default values) in the response 235 + jsonCamelCase bool // JSON fields are serialized as lowerCamelCase rather than keeping the original proto names 236 + } 237 + 238 + // NewPatreonServer builds a TwirpServer that can be used as an http.Handler to handle 239 + // HTTP requests that are routed to the right method in the provided svc implementation. 240 + // The opts are twirp.ServerOption modifiers, for example twirp.WithServerHooks(hooks). 241 + func NewPatreonServer(svc Patreon, opts ...interface{}) TwirpServer { 242 + serverOpts := newServerOpts(opts) 243 + 244 + // Using ReadOpt allows backwards and forwards compatibility with new options in the future 245 + jsonSkipDefaults := false 246 + _ = serverOpts.ReadOpt("jsonSkipDefaults", &jsonSkipDefaults) 247 + jsonCamelCase := false 248 + _ = serverOpts.ReadOpt("jsonCamelCase", &jsonCamelCase) 249 + var pathPrefix string 250 + if ok := serverOpts.ReadOpt("pathPrefix", &pathPrefix); !ok { 251 + pathPrefix = "/twirp" // default prefix 252 + } 253 + 254 + return &patreonServer{ 255 + Patreon: svc, 256 + hooks: serverOpts.Hooks, 257 + interceptor: twirp.ChainInterceptors(serverOpts.Interceptors...), 258 + pathPrefix: pathPrefix, 259 + jsonSkipDefaults: jsonSkipDefaults, 260 + jsonCamelCase: jsonCamelCase, 261 + } 262 + } 263 + 264 + // writeError writes an HTTP response with a valid Twirp error format, and triggers hooks. 265 + // If err is not a twirp.Error, it will get wrapped with twirp.InternalErrorWith(err) 266 + func (s *patreonServer) writeError(ctx context.Context, resp http.ResponseWriter, err error) { 267 + writeError(ctx, resp, err, s.hooks) 268 + } 269 + 270 + // handleRequestBodyError is used to handle error when the twirp server cannot read request 271 + func (s *patreonServer) handleRequestBodyError(ctx context.Context, resp http.ResponseWriter, msg string, err error) { 272 + if context.Canceled == ctx.Err() { 273 + s.writeError(ctx, resp, twirp.NewError(twirp.Canceled, "failed to read request: context canceled")) 274 + return 275 + } 276 + if context.DeadlineExceeded == ctx.Err() { 277 + s.writeError(ctx, resp, twirp.NewError(twirp.DeadlineExceeded, "failed to read request: deadline exceeded")) 278 + return 279 + } 280 + s.writeError(ctx, resp, twirp.WrapError(malformedRequestError(msg), err)) 281 + } 282 + 283 + // PatreonPathPrefix is a convenience constant that may identify URL paths. 284 + // Should be used with caution, it only matches routes generated by Twirp Go clients, 285 + // with the default "/twirp" prefix and default CamelCase service and method names. 286 + // More info: https://twitchtv.github.io/twirp/docs/routing.html 287 + const PatreonPathPrefix = "/twirp/xeiaso.net.internal.Patreon/" 288 + 289 + func (s *patreonServer) ServeHTTP(resp http.ResponseWriter, req *http.Request) { 290 + ctx := req.Context() 291 + ctx = ctxsetters.WithPackageName(ctx, "xeiaso.net.internal") 292 + ctx = ctxsetters.WithServiceName(ctx, "Patreon") 293 + ctx = ctxsetters.WithResponseWriter(ctx, resp) 294 + 295 + var err error 296 + ctx, err = callRequestReceived(ctx, s.hooks) 297 + if err != nil { 298 + s.writeError(ctx, resp, err) 299 + return 300 + } 301 + 302 + if req.Method != "POST" { 303 + msg := fmt.Sprintf("unsupported method %q (only POST is allowed)", req.Method) 304 + s.writeError(ctx, resp, badRouteError(msg, req.Method, req.URL.Path)) 305 + return 306 + } 307 + 308 + // Verify path format: [<prefix>]/<package>.<Service>/<Method> 309 + prefix, pkgService, method := parseTwirpPath(req.URL.Path) 310 + if pkgService != "xeiaso.net.internal.Patreon" { 311 + msg := fmt.Sprintf("no handler for path %q", req.URL.Path) 312 + s.writeError(ctx, resp, badRouteError(msg, req.Method, req.URL.Path)) 313 + return 314 + } 315 + if prefix != s.pathPrefix { 316 + msg := fmt.Sprintf("invalid path prefix %q, expected %q, on path %q", prefix, s.pathPrefix, req.URL.Path) 317 + s.writeError(ctx, resp, badRouteError(msg, req.Method, req.URL.Path)) 318 + return 319 + } 320 + 321 + switch method { 322 + case "GetToken": 323 + s.serveGetToken(ctx, resp, req) 324 + return 325 + default: 326 + msg := fmt.Sprintf("no handler for path %q", req.URL.Path) 327 + s.writeError(ctx, resp, badRouteError(msg, req.Method, req.URL.Path)) 328 + return 329 + } 330 + } 331 + 332 + func (s *patreonServer) serveGetToken(ctx context.Context, resp http.ResponseWriter, req *http.Request) { 333 + header := req.Header.Get("Content-Type") 334 + i := strings.Index(header, ";") 335 + if i == -1 { 336 + i = len(header) 337 + } 338 + switch strings.TrimSpace(strings.ToLower(header[:i])) { 339 + case "application/json": 340 + s.serveGetTokenJSON(ctx, resp, req) 341 + case "application/protobuf": 342 + s.serveGetTokenProtobuf(ctx, resp, req) 343 + default: 344 + msg := fmt.Sprintf("unexpected Content-Type: %q", req.Header.Get("Content-Type")) 345 + twerr := badRouteError(msg, req.Method, req.URL.Path) 346 + s.writeError(ctx, resp, twerr) 347 + } 348 + } 349 + 350 + func (s *patreonServer) serveGetTokenJSON(ctx context.Context, resp http.ResponseWriter, req *http.Request) { 351 + var err error 352 + ctx = ctxsetters.WithMethodName(ctx, "GetToken") 353 + ctx, err = callRequestRouted(ctx, s.hooks) 354 + if err != nil { 355 + s.writeError(ctx, resp, err) 356 + return 357 + } 358 + 359 + d := json.NewDecoder(req.Body) 360 + rawReqBody := json.RawMessage{} 361 + if err := d.Decode(&rawReqBody); err != nil { 362 + s.handleRequestBodyError(ctx, resp, "the json request could not be decoded", err) 363 + return 364 + } 365 + reqContent := new(google_protobuf.Empty) 366 + unmarshaler := protojson.UnmarshalOptions{DiscardUnknown: true} 367 + if err = unmarshaler.Unmarshal(rawReqBody, reqContent); err != nil { 368 + s.handleRequestBodyError(ctx, resp, "the json request could not be decoded", err) 369 + return 370 + } 371 + 372 + handler := s.Patreon.GetToken 373 + if s.interceptor != nil { 374 + handler = func(ctx context.Context, req *google_protobuf.Empty) (*PatreonToken, error) { 375 + resp, err := s.interceptor( 376 + func(ctx context.Context, req interface{}) (interface{}, error) { 377 + typedReq, ok := req.(*google_protobuf.Empty) 378 + if !ok { 379 + return nil, twirp.InternalError("failed type assertion req.(*google_protobuf.Empty) when calling interceptor") 380 + } 381 + return s.Patreon.GetToken(ctx, typedReq) 382 + }, 383 + )(ctx, req) 384 + if resp != nil { 385 + typedResp, ok := resp.(*PatreonToken) 386 + if !ok { 387 + return nil, twirp.InternalError("failed type assertion resp.(*PatreonToken) when calling interceptor") 388 + } 389 + return typedResp, err 390 + } 391 + return nil, err 392 + } 393 + } 394 + 395 + // Call service method 396 + var respContent *PatreonToken 397 + func() { 398 + defer ensurePanicResponses(ctx, resp, s.hooks) 399 + respContent, err = handler(ctx, reqContent) 400 + }() 401 + 402 + if err != nil { 403 + s.writeError(ctx, resp, err) 404 + return 405 + } 406 + if respContent == nil { 407 + s.writeError(ctx, resp, twirp.InternalError("received a nil *PatreonToken and nil error while calling GetToken. nil responses are not supported")) 408 + return 409 + } 410 + 411 + ctx = callResponsePrepared(ctx, s.hooks) 412 + 413 + marshaler := &protojson.MarshalOptions{UseProtoNames: !s.jsonCamelCase, EmitUnpopulated: !s.jsonSkipDefaults} 414 + respBytes, err := marshaler.Marshal(respContent) 415 + if err != nil { 416 + s.writeError(ctx, resp, wrapInternal(err, "failed to marshal json response")) 417 + return 418 + } 419 + 420 + ctx = ctxsetters.WithStatusCode(ctx, http.StatusOK) 421 + resp.Header().Set("Content-Type", "application/json") 422 + resp.Header().Set("Content-Length", strconv.Itoa(len(respBytes))) 423 + resp.WriteHeader(http.StatusOK) 424 + 425 + if n, err := resp.Write(respBytes); err != nil { 426 + msg := fmt.Sprintf("failed to write response, %d of %d bytes written: %s", n, len(respBytes), err.Error()) 427 + twerr := twirp.NewError(twirp.Unknown, msg) 428 + ctx = callError(ctx, s.hooks, twerr) 429 + } 430 + callResponseSent(ctx, s.hooks) 431 + } 432 + 433 + func (s *patreonServer) serveGetTokenProtobuf(ctx context.Context, resp http.ResponseWriter, req *http.Request) { 434 + var err error 435 + ctx = ctxsetters.WithMethodName(ctx, "GetToken") 436 + ctx, err = callRequestRouted(ctx, s.hooks) 437 + if err != nil { 438 + s.writeError(ctx, resp, err) 439 + return 440 + } 441 + 442 + buf, err := io.ReadAll(req.Body) 443 + if err != nil { 444 + s.handleRequestBodyError(ctx, resp, "failed to read request body", err) 445 + return 446 + } 447 + reqContent := new(google_protobuf.Empty) 448 + if err = proto.Unmarshal(buf, reqContent); err != nil { 449 + s.writeError(ctx, resp, malformedRequestError("the protobuf request could not be decoded")) 450 + return 451 + } 452 + 453 + handler := s.Patreon.GetToken 454 + if s.interceptor != nil { 455 + handler = func(ctx context.Context, req *google_protobuf.Empty) (*PatreonToken, error) { 456 + resp, err := s.interceptor( 457 + func(ctx context.Context, req interface{}) (interface{}, error) { 458 + typedReq, ok := req.(*google_protobuf.Empty) 459 + if !ok { 460 + return nil, twirp.InternalError("failed type assertion req.(*google_protobuf.Empty) when calling interceptor") 461 + } 462 + return s.Patreon.GetToken(ctx, typedReq) 463 + }, 464 + )(ctx, req) 465 + if resp != nil { 466 + typedResp, ok := resp.(*PatreonToken) 467 + if !ok { 468 + return nil, twirp.InternalError("failed type assertion resp.(*PatreonToken) when calling interceptor") 469 + } 470 + return typedResp, err 471 + } 472 + return nil, err 473 + } 474 + } 475 + 476 + // Call service method 477 + var respContent *PatreonToken 478 + func() { 479 + defer ensurePanicResponses(ctx, resp, s.hooks) 480 + respContent, err = handler(ctx, reqContent) 481 + }() 482 + 483 + if err != nil { 484 + s.writeError(ctx, resp, err) 485 + return 486 + } 487 + if respContent == nil { 488 + s.writeError(ctx, resp, twirp.InternalError("received a nil *PatreonToken and nil error while calling GetToken. nil responses are not supported")) 489 + return 490 + } 491 + 492 + ctx = callResponsePrepared(ctx, s.hooks) 493 + 494 + respBytes, err := proto.Marshal(respContent) 495 + if err != nil { 496 + s.writeError(ctx, resp, wrapInternal(err, "failed to marshal proto response")) 497 + return 498 + } 499 + 500 + ctx = ctxsetters.WithStatusCode(ctx, http.StatusOK) 501 + resp.Header().Set("Content-Type", "application/protobuf") 502 + resp.Header().Set("Content-Length", strconv.Itoa(len(respBytes))) 503 + resp.WriteHeader(http.StatusOK) 504 + if n, err := resp.Write(respBytes); err != nil { 505 + msg := fmt.Sprintf("failed to write response, %d of %d bytes written: %s", n, len(respBytes), err.Error()) 506 + twerr := twirp.NewError(twirp.Unknown, msg) 507 + ctx = callError(ctx, s.hooks, twerr) 508 + } 509 + callResponseSent(ctx, s.hooks) 510 + } 511 + 512 + func (s *patreonServer) ServiceDescriptor() ([]byte, int) { 513 + return twirpFileDescriptor0, 0 514 + } 515 + 516 + func (s *patreonServer) ProtocGenTwirpVersion() string { 517 + return "v8.1.3" 518 + } 519 + 520 + // PathPrefix returns the base service path, in the form: "/<prefix>/<package>.<Service>/" 521 + // that is everything in a Twirp route except for the <Method>. This can be used for routing, 522 + // for example to identify the requests that are targeted to this service in a mux. 523 + func (s *patreonServer) PathPrefix() string { 524 + return baseServicePath(s.pathPrefix, "xeiaso.net.internal", "Patreon") 525 + } 526 + 527 + // =============== 528 + // Admin Interface 529 + // =============== 530 + 531 + type Admin interface { 532 + Rebuild(context.Context, *google_protobuf.Empty) (*xeiaso_net.BuildInfo, error) 533 + } 534 + 535 + // ===================== 536 + // Admin Protobuf Client 537 + // ===================== 538 + 539 + type adminProtobufClient struct { 540 + client HTTPClient 541 + urls [1]string 542 + interceptor twirp.Interceptor 543 + opts twirp.ClientOptions 544 + } 545 + 546 + // NewAdminProtobufClient creates a Protobuf client that implements the Admin interface. 547 + // It communicates using Protobuf and can be configured with a custom HTTPClient. 548 + func NewAdminProtobufClient(baseURL string, client HTTPClient, opts ...twirp.ClientOption) Admin { 549 + if c, ok := client.(*http.Client); ok { 550 + client = withoutRedirects(c) 551 + } 552 + 553 + clientOpts := twirp.ClientOptions{} 554 + for _, o := range opts { 555 + o(&clientOpts) 556 + } 557 + 558 + // Using ReadOpt allows backwards and forwards compatibility with new options in the future 559 + literalURLs := false 560 + _ = clientOpts.ReadOpt("literalURLs", &literalURLs) 561 + var pathPrefix string 562 + if ok := clientOpts.ReadOpt("pathPrefix", &pathPrefix); !ok { 563 + pathPrefix = "/twirp" // default prefix 564 + } 565 + 566 + // Build method URLs: <baseURL>[<prefix>]/<package>.<Service>/<Method> 567 + serviceURL := sanitizeBaseURL(baseURL) 568 + serviceURL += baseServicePath(pathPrefix, "xeiaso.net.internal", "Admin") 569 + urls := [1]string{ 570 + serviceURL + "Rebuild", 571 + } 572 + 573 + return &adminProtobufClient{ 574 + client: client, 575 + urls: urls, 576 + interceptor: twirp.ChainInterceptors(clientOpts.Interceptors...), 577 + opts: clientOpts, 578 + } 579 + } 580 + 581 + func (c *adminProtobufClient) Rebuild(ctx context.Context, in *google_protobuf.Empty) (*xeiaso_net.BuildInfo, error) { 582 + ctx = ctxsetters.WithPackageName(ctx, "xeiaso.net.internal") 583 + ctx = ctxsetters.WithServiceName(ctx, "Admin") 584 + ctx = ctxsetters.WithMethodName(ctx, "Rebuild") 585 + caller := c.callRebuild 586 + if c.interceptor != nil { 587 + caller = func(ctx context.Context, req *google_protobuf.Empty) (*xeiaso_net.BuildInfo, error) { 588 + resp, err := c.interceptor( 589 + func(ctx context.Context, req interface{}) (interface{}, error) { 590 + typedReq, ok := req.(*google_protobuf.Empty) 591 + if !ok { 592 + return nil, twirp.InternalError("failed type assertion req.(*google_protobuf.Empty) when calling interceptor") 593 + } 594 + return c.callRebuild(ctx, typedReq) 595 + }, 596 + )(ctx, req) 597 + if resp != nil { 598 + typedResp, ok := resp.(*xeiaso_net.BuildInfo) 599 + if !ok { 600 + return nil, twirp.InternalError("failed type assertion resp.(*xeiaso_net.BuildInfo) when calling interceptor") 601 + } 602 + return typedResp, err 603 + } 604 + return nil, err 605 + } 606 + } 607 + return caller(ctx, in) 608 + } 609 + 610 + func (c *adminProtobufClient) callRebuild(ctx context.Context, in *google_protobuf.Empty) (*xeiaso_net.BuildInfo, error) { 611 + out := new(xeiaso_net.BuildInfo) 612 + ctx, err := doProtobufRequest(ctx, c.client, c.opts.Hooks, c.urls[0], in, out) 613 + if err != nil { 614 + twerr, ok := err.(twirp.Error) 615 + if !ok { 616 + twerr = twirp.InternalErrorWith(err) 617 + } 618 + callClientError(ctx, c.opts.Hooks, twerr) 619 + return nil, err 620 + } 621 + 622 + callClientResponseReceived(ctx, c.opts.Hooks) 623 + 624 + return out, nil 625 + } 626 + 627 + // ================= 628 + // Admin JSON Client 629 + // ================= 630 + 631 + type adminJSONClient struct { 632 + client HTTPClient 633 + urls [1]string 634 + interceptor twirp.Interceptor 635 + opts twirp.ClientOptions 636 + } 637 + 638 + // NewAdminJSONClient creates a JSON client that implements the Admin interface. 639 + // It communicates using JSON and can be configured with a custom HTTPClient. 640 + func NewAdminJSONClient(baseURL string, client HTTPClient, opts ...twirp.ClientOption) Admin { 641 + if c, ok := client.(*http.Client); ok { 642 + client = withoutRedirects(c) 643 + } 644 + 645 + clientOpts := twirp.ClientOptions{} 646 + for _, o := range opts { 647 + o(&clientOpts) 648 + } 649 + 650 + // Using ReadOpt allows backwards and forwards compatibility with new options in the future 651 + literalURLs := false 652 + _ = clientOpts.ReadOpt("literalURLs", &literalURLs) 653 + var pathPrefix string 654 + if ok := clientOpts.ReadOpt("pathPrefix", &pathPrefix); !ok { 655 + pathPrefix = "/twirp" // default prefix 656 + } 657 + 658 + // Build method URLs: <baseURL>[<prefix>]/<package>.<Service>/<Method> 659 + serviceURL := sanitizeBaseURL(baseURL) 660 + serviceURL += baseServicePath(pathPrefix, "xeiaso.net.internal", "Admin") 661 + urls := [1]string{ 662 + serviceURL + "Rebuild", 663 + } 664 + 665 + return &adminJSONClient{ 666 + client: client, 667 + urls: urls, 668 + interceptor: twirp.ChainInterceptors(clientOpts.Interceptors...), 669 + opts: clientOpts, 670 + } 671 + } 672 + 673 + func (c *adminJSONClient) Rebuild(ctx context.Context, in *google_protobuf.Empty) (*xeiaso_net.BuildInfo, error) { 674 + ctx = ctxsetters.WithPackageName(ctx, "xeiaso.net.internal") 675 + ctx = ctxsetters.WithServiceName(ctx, "Admin") 676 + ctx = ctxsetters.WithMethodName(ctx, "Rebuild") 677 + caller := c.callRebuild 678 + if c.interceptor != nil { 679 + caller = func(ctx context.Context, req *google_protobuf.Empty) (*xeiaso_net.BuildInfo, error) { 680 + resp, err := c.interceptor( 681 + func(ctx context.Context, req interface{}) (interface{}, error) { 682 + typedReq, ok := req.(*google_protobuf.Empty) 683 + if !ok { 684 + return nil, twirp.InternalError("failed type assertion req.(*google_protobuf.Empty) when calling interceptor") 685 + } 686 + return c.callRebuild(ctx, typedReq) 687 + }, 688 + )(ctx, req) 689 + if resp != nil { 690 + typedResp, ok := resp.(*xeiaso_net.BuildInfo) 691 + if !ok { 692 + return nil, twirp.InternalError("failed type assertion resp.(*xeiaso_net.BuildInfo) when calling interceptor") 693 + } 694 + return typedResp, err 695 + } 696 + return nil, err 697 + } 698 + } 699 + return caller(ctx, in) 700 + } 701 + 702 + func (c *adminJSONClient) callRebuild(ctx context.Context, in *google_protobuf.Empty) (*xeiaso_net.BuildInfo, error) { 703 + out := new(xeiaso_net.BuildInfo) 704 + ctx, err := doJSONRequest(ctx, c.client, c.opts.Hooks, c.urls[0], in, out) 705 + if err != nil { 706 + twerr, ok := err.(twirp.Error) 707 + if !ok { 708 + twerr = twirp.InternalErrorWith(err) 709 + } 710 + callClientError(ctx, c.opts.Hooks, twerr) 711 + return nil, err 712 + } 713 + 714 + callClientResponseReceived(ctx, c.opts.Hooks) 715 + 716 + return out, nil 717 + } 718 + 719 + // ==================== 720 + // Admin Server Handler 721 + // ==================== 722 + 723 + type adminServer struct { 724 + Admin 725 + interceptor twirp.Interceptor 726 + hooks *twirp.ServerHooks 727 + pathPrefix string // prefix for routing 728 + jsonSkipDefaults bool // do not include unpopulated fields (default values) in the response 729 + jsonCamelCase bool // JSON fields are serialized as lowerCamelCase rather than keeping the original proto names 730 + } 731 + 732 + // NewAdminServer builds a TwirpServer that can be used as an http.Handler to handle 733 + // HTTP requests that are routed to the right method in the provided svc implementation. 734 + // The opts are twirp.ServerOption modifiers, for example twirp.WithServerHooks(hooks). 735 + func NewAdminServer(svc Admin, opts ...interface{}) TwirpServer { 736 + serverOpts := newServerOpts(opts) 737 + 738 + // Using ReadOpt allows backwards and forwards compatibility with new options in the future 739 + jsonSkipDefaults := false 740 + _ = serverOpts.ReadOpt("jsonSkipDefaults", &jsonSkipDefaults) 741 + jsonCamelCase := false 742 + _ = serverOpts.ReadOpt("jsonCamelCase", &jsonCamelCase) 743 + var pathPrefix string 744 + if ok := serverOpts.ReadOpt("pathPrefix", &pathPrefix); !ok { 745 + pathPrefix = "/twirp" // default prefix 746 + } 747 + 748 + return &adminServer{ 749 + Admin: svc, 750 + hooks: serverOpts.Hooks, 751 + interceptor: twirp.ChainInterceptors(serverOpts.Interceptors...), 752 + pathPrefix: pathPrefix, 753 + jsonSkipDefaults: jsonSkipDefaults, 754 + jsonCamelCase: jsonCamelCase, 755 + } 756 + } 757 + 758 + // writeError writes an HTTP response with a valid Twirp error format, and triggers hooks. 759 + // If err is not a twirp.Error, it will get wrapped with twirp.InternalErrorWith(err) 760 + func (s *adminServer) writeError(ctx context.Context, resp http.ResponseWriter, err error) { 761 + writeError(ctx, resp, err, s.hooks) 762 + } 763 + 764 + // handleRequestBodyError is used to handle error when the twirp server cannot read request 765 + func (s *adminServer) handleRequestBodyError(ctx context.Context, resp http.ResponseWriter, msg string, err error) { 766 + if context.Canceled == ctx.Err() { 767 + s.writeError(ctx, resp, twirp.NewError(twirp.Canceled, "failed to read request: context canceled")) 768 + return 769 + } 770 + if context.DeadlineExceeded == ctx.Err() { 771 + s.writeError(ctx, resp, twirp.NewError(twirp.DeadlineExceeded, "failed to read request: deadline exceeded")) 772 + return 773 + } 774 + s.writeError(ctx, resp, twirp.WrapError(malformedRequestError(msg), err)) 775 + } 776 + 777 + // AdminPathPrefix is a convenience constant that may identify URL paths. 778 + // Should be used with caution, it only matches routes generated by Twirp Go clients, 779 + // with the default "/twirp" prefix and default CamelCase service and method names. 780 + // More info: https://twitchtv.github.io/twirp/docs/routing.html 781 + const AdminPathPrefix = "/twirp/xeiaso.net.internal.Admin/" 782 + 783 + func (s *adminServer) ServeHTTP(resp http.ResponseWriter, req *http.Request) { 784 + ctx := req.Context() 785 + ctx = ctxsetters.WithPackageName(ctx, "xeiaso.net.internal") 786 + ctx = ctxsetters.WithServiceName(ctx, "Admin") 787 + ctx = ctxsetters.WithResponseWriter(ctx, resp) 788 + 789 + var err error 790 + ctx, err = callRequestReceived(ctx, s.hooks) 791 + if err != nil { 792 + s.writeError(ctx, resp, err) 793 + return 794 + } 795 + 796 + if req.Method != "POST" { 797 + msg := fmt.Sprintf("unsupported method %q (only POST is allowed)", req.Method) 798 + s.writeError(ctx, resp, badRouteError(msg, req.Method, req.URL.Path)) 799 + return 800 + } 801 + 802 + // Verify path format: [<prefix>]/<package>.<Service>/<Method> 803 + prefix, pkgService, method := parseTwirpPath(req.URL.Path) 804 + if pkgService != "xeiaso.net.internal.Admin" { 805 + msg := fmt.Sprintf("no handler for path %q", req.URL.Path) 806 + s.writeError(ctx, resp, badRouteError(msg, req.Method, req.URL.Path)) 807 + return 808 + } 809 + if prefix != s.pathPrefix { 810 + msg := fmt.Sprintf("invalid path prefix %q, expected %q, on path %q", prefix, s.pathPrefix, req.URL.Path) 811 + s.writeError(ctx, resp, badRouteError(msg, req.Method, req.URL.Path)) 812 + return 813 + } 814 + 815 + switch method { 816 + case "Rebuild": 817 + s.serveRebuild(ctx, resp, req) 818 + return 819 + default: 820 + msg := fmt.Sprintf("no handler for path %q", req.URL.Path) 821 + s.writeError(ctx, resp, badRouteError(msg, req.Method, req.URL.Path)) 822 + return 823 + } 824 + } 825 + 826 + func (s *adminServer) serveRebuild(ctx context.Context, resp http.ResponseWriter, req *http.Request) { 827 + header := req.Header.Get("Content-Type") 828 + i := strings.Index(header, ";") 829 + if i == -1 { 830 + i = len(header) 831 + } 832 + switch strings.TrimSpace(strings.ToLower(header[:i])) { 833 + case "application/json": 834 + s.serveRebuildJSON(ctx, resp, req) 835 + case "application/protobuf": 836 + s.serveRebuildProtobuf(ctx, resp, req) 837 + default: 838 + msg := fmt.Sprintf("unexpected Content-Type: %q", req.Header.Get("Content-Type")) 839 + twerr := badRouteError(msg, req.Method, req.URL.Path) 840 + s.writeError(ctx, resp, twerr) 841 + } 842 + } 843 + 844 + func (s *adminServer) serveRebuildJSON(ctx context.Context, resp http.ResponseWriter, req *http.Request) { 845 + var err error 846 + ctx = ctxsetters.WithMethodName(ctx, "Rebuild") 847 + ctx, err = callRequestRouted(ctx, s.hooks) 848 + if err != nil { 849 + s.writeError(ctx, resp, err) 850 + return 851 + } 852 + 853 + d := json.NewDecoder(req.Body) 854 + rawReqBody := json.RawMessage{} 855 + if err := d.Decode(&rawReqBody); err != nil { 856 + s.handleRequestBodyError(ctx, resp, "the json request could not be decoded", err) 857 + return 858 + } 859 + reqContent := new(google_protobuf.Empty) 860 + unmarshaler := protojson.UnmarshalOptions{DiscardUnknown: true} 861 + if err = unmarshaler.Unmarshal(rawReqBody, reqContent); err != nil { 862 + s.handleRequestBodyError(ctx, resp, "the json request could not be decoded", err) 863 + return 864 + } 865 + 866 + handler := s.Admin.Rebuild 867 + if s.interceptor != nil { 868 + handler = func(ctx context.Context, req *google_protobuf.Empty) (*xeiaso_net.BuildInfo, error) { 869 + resp, err := s.interceptor( 870 + func(ctx context.Context, req interface{}) (interface{}, error) { 871 + typedReq, ok := req.(*google_protobuf.Empty) 872 + if !ok { 873 + return nil, twirp.InternalError("failed type assertion req.(*google_protobuf.Empty) when calling interceptor") 874 + } 875 + return s.Admin.Rebuild(ctx, typedReq) 876 + }, 877 + )(ctx, req) 878 + if resp != nil { 879 + typedResp, ok := resp.(*xeiaso_net.BuildInfo) 880 + if !ok { 881 + return nil, twirp.InternalError("failed type assertion resp.(*xeiaso_net.BuildInfo) when calling interceptor") 882 + } 883 + return typedResp, err 884 + } 885 + return nil, err 886 + } 887 + } 888 + 889 + // Call service method 890 + var respContent *xeiaso_net.BuildInfo 891 + func() { 892 + defer ensurePanicResponses(ctx, resp, s.hooks) 893 + respContent, err = handler(ctx, reqContent) 894 + }() 895 + 896 + if err != nil { 897 + s.writeError(ctx, resp, err) 898 + return 899 + } 900 + if respContent == nil { 901 + s.writeError(ctx, resp, twirp.InternalError("received a nil *xeiaso_net.BuildInfo and nil error while calling Rebuild. nil responses are not supported")) 902 + return 903 + } 904 + 905 + ctx = callResponsePrepared(ctx, s.hooks) 906 + 907 + marshaler := &protojson.MarshalOptions{UseProtoNames: !s.jsonCamelCase, EmitUnpopulated: !s.jsonSkipDefaults} 908 + respBytes, err := marshaler.Marshal(respContent) 909 + if err != nil { 910 + s.writeError(ctx, resp, wrapInternal(err, "failed to marshal json response")) 911 + return 912 + } 913 + 914 + ctx = ctxsetters.WithStatusCode(ctx, http.StatusOK) 915 + resp.Header().Set("Content-Type", "application/json") 916 + resp.Header().Set("Content-Length", strconv.Itoa(len(respBytes))) 917 + resp.WriteHeader(http.StatusOK) 918 + 919 + if n, err := resp.Write(respBytes); err != nil { 920 + msg := fmt.Sprintf("failed to write response, %d of %d bytes written: %s", n, len(respBytes), err.Error()) 921 + twerr := twirp.NewError(twirp.Unknown, msg) 922 + ctx = callError(ctx, s.hooks, twerr) 923 + } 924 + callResponseSent(ctx, s.hooks) 925 + } 926 + 927 + func (s *adminServer) serveRebuildProtobuf(ctx context.Context, resp http.ResponseWriter, req *http.Request) { 928 + var err error 929 + ctx = ctxsetters.WithMethodName(ctx, "Rebuild") 930 + ctx, err = callRequestRouted(ctx, s.hooks) 931 + if err != nil { 932 + s.writeError(ctx, resp, err) 933 + return 934 + } 935 + 936 + buf, err := io.ReadAll(req.Body) 937 + if err != nil { 938 + s.handleRequestBodyError(ctx, resp, "failed to read request body", err) 939 + return 940 + } 941 + reqContent := new(google_protobuf.Empty) 942 + if err = proto.Unmarshal(buf, reqContent); err != nil { 943 + s.writeError(ctx, resp, malformedRequestError("the protobuf request could not be decoded")) 944 + return 945 + } 946 + 947 + handler := s.Admin.Rebuild 948 + if s.interceptor != nil { 949 + handler = func(ctx context.Context, req *google_protobuf.Empty) (*xeiaso_net.BuildInfo, error) { 950 + resp, err := s.interceptor( 951 + func(ctx context.Context, req interface{}) (interface{}, error) { 952 + typedReq, ok := req.(*google_protobuf.Empty) 953 + if !ok { 954 + return nil, twirp.InternalError("failed type assertion req.(*google_protobuf.Empty) when calling interceptor") 955 + } 956 + return s.Admin.Rebuild(ctx, typedReq) 957 + }, 958 + )(ctx, req) 959 + if resp != nil { 960 + typedResp, ok := resp.(*xeiaso_net.BuildInfo) 961 + if !ok { 962 + return nil, twirp.InternalError("failed type assertion resp.(*xeiaso_net.BuildInfo) when calling interceptor") 963 + } 964 + return typedResp, err 965 + } 966 + return nil, err 967 + } 968 + } 969 + 970 + // Call service method 971 + var respContent *xeiaso_net.BuildInfo 972 + func() { 973 + defer ensurePanicResponses(ctx, resp, s.hooks) 974 + respContent, err = handler(ctx, reqContent) 975 + }() 976 + 977 + if err != nil { 978 + s.writeError(ctx, resp, err) 979 + return 980 + } 981 + if respContent == nil { 982 + s.writeError(ctx, resp, twirp.InternalError("received a nil *xeiaso_net.BuildInfo and nil error while calling Rebuild. nil responses are not supported")) 983 + return 984 + } 985 + 986 + ctx = callResponsePrepared(ctx, s.hooks) 987 + 988 + respBytes, err := proto.Marshal(respContent) 989 + if err != nil { 990 + s.writeError(ctx, resp, wrapInternal(err, "failed to marshal proto response")) 991 + return 992 + } 993 + 994 + ctx = ctxsetters.WithStatusCode(ctx, http.StatusOK) 995 + resp.Header().Set("Content-Type", "application/protobuf") 996 + resp.Header().Set("Content-Length", strconv.Itoa(len(respBytes))) 997 + resp.WriteHeader(http.StatusOK) 998 + if n, err := resp.Write(respBytes); err != nil { 999 + msg := fmt.Sprintf("failed to write response, %d of %d bytes written: %s", n, len(respBytes), err.Error()) 1000 + twerr := twirp.NewError(twirp.Unknown, msg) 1001 + ctx = callError(ctx, s.hooks, twerr) 1002 + } 1003 + callResponseSent(ctx, s.hooks) 1004 + } 1005 + 1006 + func (s *adminServer) ServiceDescriptor() ([]byte, int) { 1007 + return twirpFileDescriptor0, 1 1008 + } 1009 + 1010 + func (s *adminServer) ProtocGenTwirpVersion() string { 1011 + return "v8.1.3" 1012 + } 1013 + 1014 + // PathPrefix returns the base service path, in the form: "/<prefix>/<package>.<Service>/" 1015 + // that is everything in a Twirp route except for the <Method>. This can be used for routing, 1016 + // for example to identify the requests that are targeted to this service in a mux. 1017 + func (s *adminServer) PathPrefix() string { 1018 + return baseServicePath(s.pathPrefix, "xeiaso.net.internal", "Admin") 1019 + } 1020 + 1021 + // ===== 1022 + // Utils 1023 + // ===== 1024 + 1025 + // HTTPClient is the interface used by generated clients to send HTTP requests. 1026 + // It is fulfilled by *(net/http).Client, which is sufficient for most users. 1027 + // Users can provide their own implementation for special retry policies. 1028 + // 1029 + // HTTPClient implementations should not follow redirects. Redirects are 1030 + // automatically disabled if *(net/http).Client is passed to client 1031 + // constructors. See the withoutRedirects function in this file for more 1032 + // details. 1033 + type HTTPClient interface { 1034 + Do(req *http.Request) (*http.Response, error) 1035 + } 1036 + 1037 + // TwirpServer is the interface generated server structs will support: they're 1038 + // HTTP handlers with additional methods for accessing metadata about the 1039 + // service. Those accessors are a low-level API for building reflection tools. 1040 + // Most people can think of TwirpServers as just http.Handlers. 1041 + type TwirpServer interface { 1042 + http.Handler 1043 + 1044 + // ServiceDescriptor returns gzipped bytes describing the .proto file that 1045 + // this service was generated from. Once unzipped, the bytes can be 1046 + // unmarshalled as a 1047 + // google.golang.org/protobuf/types/descriptorpb.FileDescriptorProto. 1048 + // 1049 + // The returned integer is the index of this particular service within that 1050 + // FileDescriptorProto's 'Service' slice of ServiceDescriptorProtos. This is a 1051 + // low-level field, expected to be used for reflection. 1052 + ServiceDescriptor() ([]byte, int) 1053 + 1054 + // ProtocGenTwirpVersion is the semantic version string of the version of 1055 + // twirp used to generate this file. 1056 + ProtocGenTwirpVersion() string 1057 + 1058 + // PathPrefix returns the HTTP URL path prefix for all methods handled by this 1059 + // service. This can be used with an HTTP mux to route Twirp requests. 1060 + // The path prefix is in the form: "/<prefix>/<package>.<Service>/" 1061 + // that is, everything in a Twirp route except for the <Method> at the end. 1062 + PathPrefix() string 1063 + } 1064 + 1065 + func newServerOpts(opts []interface{}) *twirp.ServerOptions { 1066 + serverOpts := &twirp.ServerOptions{} 1067 + for _, opt := range opts { 1068 + switch o := opt.(type) { 1069 + case twirp.ServerOption: 1070 + o(serverOpts) 1071 + case *twirp.ServerHooks: // backwards compatibility, allow to specify hooks as an argument 1072 + twirp.WithServerHooks(o)(serverOpts) 1073 + case nil: // backwards compatibility, allow nil value for the argument 1074 + continue 1075 + default: 1076 + panic(fmt.Sprintf("Invalid option type %T, please use a twirp.ServerOption", o)) 1077 + } 1078 + } 1079 + return serverOpts 1080 + } 1081 + 1082 + // WriteError writes an HTTP response with a valid Twirp error format (code, msg, meta). 1083 + // Useful outside of the Twirp server (e.g. http middleware), but does not trigger hooks. 1084 + // If err is not a twirp.Error, it will get wrapped with twirp.InternalErrorWith(err) 1085 + func WriteError(resp http.ResponseWriter, err error) { 1086 + writeError(context.Background(), resp, err, nil) 1087 + } 1088 + 1089 + // writeError writes Twirp errors in the response and triggers hooks. 1090 + func writeError(ctx context.Context, resp http.ResponseWriter, err error, hooks *twirp.ServerHooks) { 1091 + // Convert to a twirp.Error. Non-twirp errors are converted to internal errors. 1092 + var twerr twirp.Error 1093 + if !errors.As(err, &twerr) { 1094 + twerr = twirp.InternalErrorWith(err) 1095 + } 1096 + 1097 + statusCode := twirp.ServerHTTPStatusFromErrorCode(twerr.Code()) 1098 + ctx = ctxsetters.WithStatusCode(ctx, statusCode) 1099 + ctx = callError(ctx, hooks, twerr) 1100 + 1101 + respBody := marshalErrorToJSON(twerr) 1102 + 1103 + resp.Header().Set("Content-Type", "application/json") // Error responses are always JSON 1104 + resp.Header().Set("Content-Length", strconv.Itoa(len(respBody))) 1105 + resp.WriteHeader(statusCode) // set HTTP status code and send response 1106 + 1107 + _, writeErr := resp.Write(respBody) 1108 + if writeErr != nil { 1109 + // We have three options here. We could log the error, call the Error 1110 + // hook, or just silently ignore the error. 1111 + // 1112 + // Logging is unacceptable because we don't have a user-controlled 1113 + // logger; writing out to stderr without permission is too rude. 1114 + // 1115 + // Calling the Error hook would confuse users: it would mean the Error 1116 + // hook got called twice for one request, which is likely to lead to 1117 + // duplicated log messages and metrics, no matter how well we document 1118 + // the behavior. 1119 + // 1120 + // Silently ignoring the error is our least-bad option. It's highly 1121 + // likely that the connection is broken and the original 'err' says 1122 + // so anyway. 1123 + _ = writeErr 1124 + } 1125 + 1126 + callResponseSent(ctx, hooks) 1127 + } 1128 + 1129 + // sanitizeBaseURL parses the the baseURL, and adds the "http" scheme if needed. 1130 + // If the URL is unparsable, the baseURL is returned unchanged. 1131 + func sanitizeBaseURL(baseURL string) string { 1132 + u, err := url.Parse(baseURL) 1133 + if err != nil { 1134 + return baseURL // invalid URL will fail later when making requests 1135 + } 1136 + if u.Scheme == "" { 1137 + u.Scheme = "http" 1138 + } 1139 + return u.String() 1140 + } 1141 + 1142 + // baseServicePath composes the path prefix for the service (without <Method>). 1143 + // e.g.: baseServicePath("/twirp", "my.pkg", "MyService") 1144 + // 1145 + // returns => "/twirp/my.pkg.MyService/" 1146 + // 1147 + // e.g.: baseServicePath("", "", "MyService") 1148 + // 1149 + // returns => "/MyService/" 1150 + func baseServicePath(prefix, pkg, service string) string { 1151 + fullServiceName := service 1152 + if pkg != "" { 1153 + fullServiceName = pkg + "." + service 1154 + } 1155 + return path.Join("/", prefix, fullServiceName) + "/" 1156 + } 1157 + 1158 + // parseTwirpPath extracts path components form a valid Twirp route. 1159 + // Expected format: "[<prefix>]/<package>.<Service>/<Method>" 1160 + // e.g.: prefix, pkgService, method := parseTwirpPath("/twirp/pkg.Svc/MakeHat") 1161 + func parseTwirpPath(path string) (string, string, string) { 1162 + parts := strings.Split(path, "/") 1163 + if len(parts) < 2 { 1164 + return "", "", "" 1165 + } 1166 + method := parts[len(parts)-1] 1167 + pkgService := parts[len(parts)-2] 1168 + prefix := strings.Join(parts[0:len(parts)-2], "/") 1169 + return prefix, pkgService, method 1170 + } 1171 + 1172 + // getCustomHTTPReqHeaders retrieves a copy of any headers that are set in 1173 + // a context through the twirp.WithHTTPRequestHeaders function. 1174 + // If there are no headers set, or if they have the wrong type, nil is returned. 1175 + func getCustomHTTPReqHeaders(ctx context.Context) http.Header { 1176 + header, ok := twirp.HTTPRequestHeaders(ctx) 1177 + if !ok || header == nil { 1178 + return nil 1179 + } 1180 + copied := make(http.Header) 1181 + for k, vv := range header { 1182 + if vv == nil { 1183 + copied[k] = nil 1184 + continue 1185 + } 1186 + copied[k] = make([]string, len(vv)) 1187 + copy(copied[k], vv) 1188 + } 1189 + return copied 1190 + } 1191 + 1192 + // newRequest makes an http.Request from a client, adding common headers. 1193 + func newRequest(ctx context.Context, url string, reqBody io.Reader, contentType string) (*http.Request, error) { 1194 + req, err := http.NewRequest("POST", url, reqBody) 1195 + if err != nil { 1196 + return nil, err 1197 + } 1198 + req = req.WithContext(ctx) 1199 + if customHeader := getCustomHTTPReqHeaders(ctx); customHeader != nil { 1200 + req.Header = customHeader 1201 + } 1202 + req.Header.Set("Accept", contentType) 1203 + req.Header.Set("Content-Type", contentType) 1204 + req.Header.Set("Twirp-Version", "v8.1.3") 1205 + return req, nil 1206 + } 1207 + 1208 + // JSON serialization for errors 1209 + type twerrJSON struct { 1210 + Code string `json:"code"` 1211 + Msg string `json:"msg"` 1212 + Meta map[string]string `json:"meta,omitempty"` 1213 + } 1214 + 1215 + // marshalErrorToJSON returns JSON from a twirp.Error, that can be used as HTTP error response body. 1216 + // If serialization fails, it will use a descriptive Internal error instead. 1217 + func marshalErrorToJSON(twerr twirp.Error) []byte { 1218 + // make sure that msg is not too large 1219 + msg := twerr.Msg() 1220 + if len(msg) > 1e6 { 1221 + msg = msg[:1e6] 1222 + } 1223 + 1224 + tj := twerrJSON{ 1225 + Code: string(twerr.Code()), 1226 + Msg: msg, 1227 + Meta: twerr.MetaMap(), 1228 + } 1229 + 1230 + buf, err := json.Marshal(&tj) 1231 + if err != nil { 1232 + buf = []byte("{\"type\": \"" + twirp.Internal + "\", \"msg\": \"There was an error but it could not be serialized into JSON\"}") // fallback 1233 + } 1234 + 1235 + return buf 1236 + } 1237 + 1238 + // errorFromResponse builds a twirp.Error from a non-200 HTTP response. 1239 + // If the response has a valid serialized Twirp error, then it's returned. 1240 + // If not, the response status code is used to generate a similar twirp 1241 + // error. See twirpErrorFromIntermediary for more info on intermediary errors. 1242 + func errorFromResponse(resp *http.Response) twirp.Error { 1243 + statusCode := resp.StatusCode 1244 + statusText := http.StatusText(statusCode) 1245 + 1246 + if isHTTPRedirect(statusCode) { 1247 + // Unexpected redirect: it must be an error from an intermediary. 1248 + // Twirp clients don't follow redirects automatically, Twirp only handles 1249 + // POST requests, redirects should only happen on GET and HEAD requests. 1250 + location := resp.Header.Get("Location") 1251 + msg := fmt.Sprintf("unexpected HTTP status code %d %q received, Location=%q", statusCode, statusText, location) 1252 + return twirpErrorFromIntermediary(statusCode, msg, location) 1253 + } 1254 + 1255 + respBodyBytes, err := io.ReadAll(resp.Body) 1256 + if err != nil { 1257 + return wrapInternal(err, "failed to read server error response body") 1258 + } 1259 + 1260 + var tj twerrJSON 1261 + dec := json.NewDecoder(bytes.NewReader(respBodyBytes)) 1262 + dec.DisallowUnknownFields() 1263 + if err := dec.Decode(&tj); err != nil || tj.Code == "" { 1264 + // Invalid JSON response; it must be an error from an intermediary. 1265 + msg := fmt.Sprintf("Error from intermediary with HTTP status code %d %q", statusCode, statusText) 1266 + return twirpErrorFromIntermediary(statusCode, msg, string(respBodyBytes)) 1267 + } 1268 + 1269 + errorCode := twirp.ErrorCode(tj.Code) 1270 + if !twirp.IsValidErrorCode(errorCode) { 1271 + msg := "invalid type returned from server error response: " + tj.Code 1272 + return twirp.InternalError(msg).WithMeta("body", string(respBodyBytes)) 1273 + } 1274 + 1275 + twerr := twirp.NewError(errorCode, tj.Msg) 1276 + for k, v := range tj.Meta { 1277 + twerr = twerr.WithMeta(k, v) 1278 + } 1279 + return twerr 1280 + } 1281 + 1282 + // twirpErrorFromIntermediary maps HTTP errors from non-twirp sources to twirp errors. 1283 + // The mapping is similar to gRPC: https://github.com/grpc/grpc/blob/master/doc/http-grpc-status-mapping.md. 1284 + // Returned twirp Errors have some additional metadata for inspection. 1285 + func twirpErrorFromIntermediary(status int, msg string, bodyOrLocation string) twirp.Error { 1286 + var code twirp.ErrorCode 1287 + if isHTTPRedirect(status) { // 3xx 1288 + code = twirp.Internal 1289 + } else { 1290 + switch status { 1291 + case 400: // Bad Request 1292 + code = twirp.Internal 1293 + case 401: // Unauthorized 1294 + code = twirp.Unauthenticated 1295 + case 403: // Forbidden 1296 + code = twirp.PermissionDenied 1297 + case 404: // Not Found 1298 + code = twirp.BadRoute 1299 + case 429: // Too Many Requests 1300 + code = twirp.ResourceExhausted 1301 + case 502, 503, 504: // Bad Gateway, Service Unavailable, Gateway Timeout 1302 + code = twirp.Unavailable 1303 + default: // All other codes 1304 + code = twirp.Unknown 1305 + } 1306 + } 1307 + 1308 + twerr := twirp.NewError(code, msg) 1309 + twerr = twerr.WithMeta("http_error_from_intermediary", "true") // to easily know if this error was from intermediary 1310 + twerr = twerr.WithMeta("status_code", strconv.Itoa(status)) 1311 + if isHTTPRedirect(status) { 1312 + twerr = twerr.WithMeta("location", bodyOrLocation) 1313 + } else { 1314 + twerr = twerr.WithMeta("body", bodyOrLocation) 1315 + } 1316 + return twerr 1317 + } 1318 + 1319 + func isHTTPRedirect(status int) bool { 1320 + return status >= 300 && status <= 399 1321 + } 1322 + 1323 + // wrapInternal wraps an error with a prefix as an Internal error. 1324 + // The original error cause is accessible by github.com/pkg/errors.Cause. 1325 + func wrapInternal(err error, prefix string) twirp.Error { 1326 + return twirp.InternalErrorWith(&wrappedError{prefix: prefix, cause: err}) 1327 + } 1328 + 1329 + type wrappedError struct { 1330 + prefix string 1331 + cause error 1332 + } 1333 + 1334 + func (e *wrappedError) Error() string { return e.prefix + ": " + e.cause.Error() } 1335 + func (e *wrappedError) Unwrap() error { return e.cause } // for go1.13 + errors.Is/As 1336 + func (e *wrappedError) Cause() error { return e.cause } // for github.com/pkg/errors 1337 + 1338 + // ensurePanicResponses makes sure that rpc methods causing a panic still result in a Twirp Internal 1339 + // error response (status 500), and error hooks are properly called with the panic wrapped as an error. 1340 + // The panic is re-raised so it can be handled normally with middleware. 1341 + func ensurePanicResponses(ctx context.Context, resp http.ResponseWriter, hooks *twirp.ServerHooks) { 1342 + if r := recover(); r != nil { 1343 + // Wrap the panic as an error so it can be passed to error hooks. 1344 + // The original error is accessible from error hooks, but not visible in the response. 1345 + err := errFromPanic(r) 1346 + twerr := &internalWithCause{msg: "Internal service panic", cause: err} 1347 + // Actually write the error 1348 + writeError(ctx, resp, twerr, hooks) 1349 + // If possible, flush the error to the wire. 1350 + f, ok := resp.(http.Flusher) 1351 + if ok { 1352 + f.Flush() 1353 + } 1354 + 1355 + panic(r) 1356 + } 1357 + } 1358 + 1359 + // errFromPanic returns the typed error if the recovered panic is an error, otherwise formats as error. 1360 + func errFromPanic(p interface{}) error { 1361 + if err, ok := p.(error); ok { 1362 + return err 1363 + } 1364 + return fmt.Errorf("panic: %v", p) 1365 + } 1366 + 1367 + // internalWithCause is a Twirp Internal error wrapping an original error cause, 1368 + // but the original error message is not exposed on Msg(). The original error 1369 + // can be checked with go1.13+ errors.Is/As, and also by (github.com/pkg/errors).Unwrap 1370 + type internalWithCause struct { 1371 + msg string 1372 + cause error 1373 + } 1374 + 1375 + func (e *internalWithCause) Unwrap() error { return e.cause } // for go1.13 + errors.Is/As 1376 + func (e *internalWithCause) Cause() error { return e.cause } // for github.com/pkg/errors 1377 + func (e *internalWithCause) Error() string { return e.msg + ": " + e.cause.Error() } 1378 + func (e *internalWithCause) Code() twirp.ErrorCode { return twirp.Internal } 1379 + func (e *internalWithCause) Msg() string { return e.msg } 1380 + func (e *internalWithCause) Meta(key string) string { return "" } 1381 + func (e *internalWithCause) MetaMap() map[string]string { return nil } 1382 + func (e *internalWithCause) WithMeta(key string, val string) twirp.Error { return e } 1383 + 1384 + // malformedRequestError is used when the twirp server cannot unmarshal a request 1385 + func malformedRequestError(msg string) twirp.Error { 1386 + return twirp.NewError(twirp.Malformed, msg) 1387 + } 1388 + 1389 + // badRouteError is used when the twirp server cannot route a request 1390 + func badRouteError(msg string, method, url string) twirp.Error { 1391 + err := twirp.NewError(twirp.BadRoute, msg) 1392 + err = err.WithMeta("twirp_invalid_route", method+" "+url) 1393 + return err 1394 + } 1395 + 1396 + // withoutRedirects makes sure that the POST request can not be redirected. 1397 + // The standard library will, by default, redirect requests (including POSTs) if it gets a 302 or 1398 + // 303 response, and also 301s in go1.8. It redirects by making a second request, changing the 1399 + // method to GET and removing the body. This produces very confusing error messages, so instead we 1400 + // set a redirect policy that always errors. This stops Go from executing the redirect. 1401 + // 1402 + // We have to be a little careful in case the user-provided http.Client has its own CheckRedirect 1403 + // policy - if so, we'll run through that policy first. 1404 + // 1405 + // Because this requires modifying the http.Client, we make a new copy of the client and return it. 1406 + func withoutRedirects(in *http.Client) *http.Client { 1407 + copy := *in 1408 + copy.CheckRedirect = func(req *http.Request, via []*http.Request) error { 1409 + if in.CheckRedirect != nil { 1410 + // Run the input's redirect if it exists, in case it has side effects, but ignore any error it 1411 + // returns, since we want to use ErrUseLastResponse. 1412 + err := in.CheckRedirect(req, via) 1413 + _ = err // Silly, but this makes sure generated code passes errcheck -blank, which some people use. 1414 + } 1415 + return http.ErrUseLastResponse 1416 + } 1417 + return &copy 1418 + } 1419 + 1420 + // doProtobufRequest makes a Protobuf request to the remote Twirp service. 1421 + func doProtobufRequest(ctx context.Context, client HTTPClient, hooks *twirp.ClientHooks, url string, in, out proto.Message) (_ context.Context, err error) { 1422 + reqBodyBytes, err := proto.Marshal(in) 1423 + if err != nil { 1424 + return ctx, wrapInternal(err, "failed to marshal proto request") 1425 + } 1426 + reqBody := bytes.NewBuffer(reqBodyBytes) 1427 + if err = ctx.Err(); err != nil { 1428 + return ctx, wrapInternal(err, "aborted because context was done") 1429 + } 1430 + 1431 + req, err := newRequest(ctx, url, reqBody, "application/protobuf") 1432 + if err != nil { 1433 + return ctx, wrapInternal(err, "could not build request") 1434 + } 1435 + ctx, err = callClientRequestPrepared(ctx, hooks, req) 1436 + if err != nil { 1437 + return ctx, err 1438 + } 1439 + 1440 + req = req.WithContext(ctx) 1441 + resp, err := client.Do(req) 1442 + if err != nil { 1443 + return ctx, wrapInternal(err, "failed to do request") 1444 + } 1445 + defer func() { _ = resp.Body.Close() }() 1446 + 1447 + if err = ctx.Err(); err != nil { 1448 + return ctx, wrapInternal(err, "aborted because context was done") 1449 + } 1450 + 1451 + if resp.StatusCode != 200 { 1452 + return ctx, errorFromResponse(resp) 1453 + } 1454 + 1455 + respBodyBytes, err := io.ReadAll(resp.Body) 1456 + if err != nil { 1457 + return ctx, wrapInternal(err, "failed to read response body") 1458 + } 1459 + if err = ctx.Err(); err != nil { 1460 + return ctx, wrapInternal(err, "aborted because context was done") 1461 + } 1462 + 1463 + if err = proto.Unmarshal(respBodyBytes, out); err != nil { 1464 + return ctx, wrapInternal(err, "failed to unmarshal proto response") 1465 + } 1466 + return ctx, nil 1467 + } 1468 + 1469 + // doJSONRequest makes a JSON request to the remote Twirp service. 1470 + func doJSONRequest(ctx context.Context, client HTTPClient, hooks *twirp.ClientHooks, url string, in, out proto.Message) (_ context.Context, err error) { 1471 + marshaler := &protojson.MarshalOptions{UseProtoNames: true} 1472 + reqBytes, err := marshaler.Marshal(in) 1473 + if err != nil { 1474 + return ctx, wrapInternal(err, "failed to marshal json request") 1475 + } 1476 + if err = ctx.Err(); err != nil { 1477 + return ctx, wrapInternal(err, "aborted because context was done") 1478 + } 1479 + 1480 + req, err := newRequest(ctx, url, bytes.NewReader(reqBytes), "application/json") 1481 + if err != nil { 1482 + return ctx, wrapInternal(err, "could not build request") 1483 + } 1484 + ctx, err = callClientRequestPrepared(ctx, hooks, req) 1485 + if err != nil { 1486 + return ctx, err 1487 + } 1488 + 1489 + req = req.WithContext(ctx) 1490 + resp, err := client.Do(req) 1491 + if err != nil { 1492 + return ctx, wrapInternal(err, "failed to do request") 1493 + } 1494 + 1495 + defer func() { 1496 + cerr := resp.Body.Close() 1497 + if err == nil && cerr != nil { 1498 + err = wrapInternal(cerr, "failed to close response body") 1499 + } 1500 + }() 1501 + 1502 + if err = ctx.Err(); err != nil { 1503 + return ctx, wrapInternal(err, "aborted because context was done") 1504 + } 1505 + 1506 + if resp.StatusCode != 200 { 1507 + return ctx, errorFromResponse(resp) 1508 + } 1509 + 1510 + d := json.NewDecoder(resp.Body) 1511 + rawRespBody := json.RawMessage{} 1512 + if err := d.Decode(&rawRespBody); err != nil { 1513 + return ctx, wrapInternal(err, "failed to unmarshal json response") 1514 + } 1515 + unmarshaler := protojson.UnmarshalOptions{DiscardUnknown: true} 1516 + if err = unmarshaler.Unmarshal(rawRespBody, out); err != nil { 1517 + return ctx, wrapInternal(err, "failed to unmarshal json response") 1518 + } 1519 + if err = ctx.Err(); err != nil { 1520 + return ctx, wrapInternal(err, "aborted because context was done") 1521 + } 1522 + return ctx, nil 1523 + } 1524 + 1525 + // Call twirp.ServerHooks.RequestReceived if the hook is available 1526 + func callRequestReceived(ctx context.Context, h *twirp.ServerHooks) (context.Context, error) { 1527 + if h == nil || h.RequestReceived == nil { 1528 + return ctx, nil 1529 + } 1530 + return h.RequestReceived(ctx) 1531 + } 1532 + 1533 + // Call twirp.ServerHooks.RequestRouted if the hook is available 1534 + func callRequestRouted(ctx context.Context, h *twirp.ServerHooks) (context.Context, error) { 1535 + if h == nil || h.RequestRouted == nil { 1536 + return ctx, nil 1537 + } 1538 + return h.RequestRouted(ctx) 1539 + } 1540 + 1541 + // Call twirp.ServerHooks.ResponsePrepared if the hook is available 1542 + func callResponsePrepared(ctx context.Context, h *twirp.ServerHooks) context.Context { 1543 + if h == nil || h.ResponsePrepared == nil { 1544 + return ctx 1545 + } 1546 + return h.ResponsePrepared(ctx) 1547 + } 1548 + 1549 + // Call twirp.ServerHooks.ResponseSent if the hook is available 1550 + func callResponseSent(ctx context.Context, h *twirp.ServerHooks) { 1551 + if h == nil || h.ResponseSent == nil { 1552 + return 1553 + } 1554 + h.ResponseSent(ctx) 1555 + } 1556 + 1557 + // Call twirp.ServerHooks.Error if the hook is available 1558 + func callError(ctx context.Context, h *twirp.ServerHooks, err twirp.Error) context.Context { 1559 + if h == nil || h.Error == nil { 1560 + return ctx 1561 + } 1562 + return h.Error(ctx, err) 1563 + } 1564 + 1565 + func callClientResponseReceived(ctx context.Context, h *twirp.ClientHooks) { 1566 + if h == nil || h.ResponseReceived == nil { 1567 + return 1568 + } 1569 + h.ResponseReceived(ctx) 1570 + } 1571 + 1572 + func callClientRequestPrepared(ctx context.Context, h *twirp.ClientHooks, req *http.Request) (context.Context, error) { 1573 + if h == nil || h.RequestPrepared == nil { 1574 + return ctx, nil 1575 + } 1576 + return h.RequestPrepared(ctx, req) 1577 + } 1578 + 1579 + func callClientError(ctx context.Context, h *twirp.ClientHooks, err twirp.Error) { 1580 + if h == nil || h.Error == nil { 1581 + return 1582 + } 1583 + h.Error(ctx, err) 1584 + } 1585 + 1586 + var twirpFileDescriptor0 = []byte{ 1587 + // 291 bytes of a gzipped FileDescriptorProto 1588 + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x74, 0x90, 0x3d, 0x4f, 0xc3, 0x30, 1589 + 0x10, 0x86, 0x15, 0x3e, 0x5a, 0xea, 0x06, 0x86, 0x20, 0x50, 0x15, 0x04, 0xb4, 0xb0, 0x74, 0x72, 1590 + 0xa4, 0xc0, 0xc0, 0xda, 0x4a, 0x15, 0x62, 0xab, 0xa2, 0x4c, 0x2c, 0x95, 0xd3, 0x5e, 0x8a, 0x45, 1591 + 0x62, 0x5b, 0xf6, 0x15, 0x25, 0x3f, 0x89, 0x7f, 0x89, 0x62, 0x27, 0xa2, 0xe2, 0x63, 0xf4, 0x7b, 1592 + 0xcf, 0x3d, 0x79, 0x73, 0xe4, 0x8c, 0x0b, 0x04, 0x2d, 0x58, 0x41, 0x95, 0x96, 0x28, 0x83, 0xf3, 1593 + 0x0a, 0x38, 0x33, 0x92, 0x0a, 0x40, 0xda, 0x8d, 0xc2, 0xab, 0xad, 0x94, 0xdb, 0x02, 0x22, 0x8b, 1594 + 0x64, 0xbb, 0x3c, 0x82, 0x52, 0x61, 0xed, 0x36, 0xc2, 0xdb, 0x9f, 0x43, 0xe4, 0x25, 0x18, 0x64, 1595 + 0xa5, 0x6a, 0x01, 0xbf, 0x02, 0xc3, 0x11, 0xdc, 0xeb, 0xee, 0xd3, 0x23, 0xfe, 0x92, 0xa1, 0x06, 1596 + 0x29, 0x52, 0xf9, 0x0e, 0x22, 0x98, 0x10, 0x9f, 0xad, 0xd7, 0x60, 0xcc, 0x0a, 0x9b, 0xf7, 0xc8, 1597 + 0x1b, 0x7b, 0xd3, 0x41, 0x32, 0x74, 0x99, 0x43, 0xae, 0x09, 0xb1, 0xb3, 0x15, 0xd6, 0x0a, 0x46, 1598 + 0x07, 0x16, 0x18, 0xd8, 0x24, 0xad, 0x15, 0x04, 0xf7, 0xe4, 0x54, 0x43, 0xae, 0xc1, 0xbc, 0xb5, 1599 + 0x8a, 0x43, 0x4b, 0xf8, 0x6d, 0xe8, 0x1c, 0x31, 0xe9, 0x41, 0xa5, 0xb8, 0xae, 0x47, 0x47, 0x63, 1600 + 0x6f, 0x3a, 0x8c, 0x43, 0xea, 0x7a, 0xd3, 0xae, 0x37, 0x4d, 0xbb, 0xde, 0x49, 0x4b, 0xc6, 0x4b, 1601 + 0xd2, 0x6f, 0xab, 0x06, 0x0b, 0x72, 0xf2, 0x0c, 0xe8, 0x54, 0x97, 0xbf, 0x56, 0x17, 0xcd, 0x3d, 1602 + 0xc2, 0x09, 0xfd, 0xe3, 0x78, 0x74, 0xff, 0x67, 0xe3, 0x19, 0x39, 0x9e, 0x6d, 0x4a, 0x2e, 0x82, 1603 + 0x27, 0xd2, 0x4f, 0x20, 0xdb, 0xf1, 0x62, 0xf3, 0xaf, 0xee, 0x62, 0x5f, 0x37, 0x6f, 0xd0, 0x17, 1604 + 0x91, 0xcb, 0xf9, 0xf8, 0xf5, 0xe6, 0x3b, 0x8f, 0x3e, 0x1e, 0xa3, 0xee, 0x4b, 0x11, 0x6b, 0xcc, 1605 + 0x2a, 0xcb, 0x7a, 0x56, 0xf4, 0xf0, 0x15, 0x00, 0x00, 0xff, 0xff, 0x82, 0x33, 0x91, 0x6c, 0xdc, 1606 + 0x01, 0x00, 0x00, 1607 + }
+13 -11
internal/saasproxytoken/tokensource.go
··· 1 1 package saasproxytoken 2 2 3 3 import ( 4 - "encoding/json" 4 + "context" 5 5 "net/http" 6 6 "sync" 7 7 "time" 8 8 9 9 "golang.org/x/oauth2" 10 - "within.website/x/web" 10 + "google.golang.org/protobuf/types/known/emptypb" 11 + "xeiaso.net/v4/internal/adminpb" 11 12 ) 12 13 13 14 type remoteTokenSource struct { ··· 15 16 lock sync.Mutex 16 17 remoteURL string 17 18 httpClient *http.Client 19 + ptc adminpb.Patreon 18 20 } 19 21 20 22 func (r *remoteTokenSource) fetchToken() (*oauth2.Token, error) { 21 - resp, err := r.httpClient.Get(r.remoteURL) 23 + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) 24 + defer cancel() 25 + 26 + resp, err := r.ptc.GetToken(ctx, &emptypb.Empty{}) 22 27 if err != nil { 23 28 return nil, err 24 29 } 25 - defer resp.Body.Close() 26 - 27 - if resp.StatusCode != http.StatusOK { 28 - return nil, web.NewError(http.StatusOK, resp) 29 - } 30 30 31 31 var tok oauth2.Token 32 - if err := json.NewDecoder(resp.Body).Decode(&tok); err != nil { 33 - return nil, err 34 - } 32 + tok.AccessToken = resp.AccessToken 33 + tok.TokenType = resp.TokenType 34 + tok.RefreshToken = resp.RefreshToken 35 + tok.Expiry = resp.Expiry.AsTime() 35 36 36 37 return &tok, nil 37 38 } ··· 64 65 return &remoteTokenSource{ 65 66 remoteURL: remoteURL, 66 67 httpClient: httpClient, 68 + ptc: adminpb.NewPatreonProtobufClient(remoteURL, httpClient), 67 69 } 68 70 }
+5
pb/generate.go
··· 1 + package pb 2 + 3 + func init() {} 4 + 5 + //go:generate protoc --proto_path=. --go_out=. --go_opt=paths=source_relative --twirp_out=. --twirp_opt=paths=source_relative xesite.proto
+189
pb/xesite.pb.go
··· 1 + // Code generated by protoc-gen-go. DO NOT EDIT. 2 + // versions: 3 + // protoc-gen-go v1.32.0 4 + // protoc v4.24.4 5 + // source: xesite.proto 6 + 7 + package pb 8 + 9 + import ( 10 + protoreflect "google.golang.org/protobuf/reflect/protoreflect" 11 + protoimpl "google.golang.org/protobuf/runtime/protoimpl" 12 + emptypb "google.golang.org/protobuf/types/known/emptypb" 13 + timestamppb "google.golang.org/protobuf/types/known/timestamppb" 14 + reflect "reflect" 15 + sync "sync" 16 + ) 17 + 18 + const ( 19 + // Verify that this generated code is sufficiently up-to-date. 20 + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) 21 + // Verify that runtime/protoimpl is sufficiently up-to-date. 22 + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) 23 + ) 24 + 25 + type BuildInfo struct { 26 + state protoimpl.MessageState 27 + sizeCache protoimpl.SizeCache 28 + unknownFields protoimpl.UnknownFields 29 + 30 + Commit string `protobuf:"bytes,1,opt,name=commit,proto3" json:"commit,omitempty"` 31 + BuildTime *timestamppb.Timestamp `protobuf:"bytes,2,opt,name=build_time,json=buildTime,proto3" json:"build_time,omitempty"` 32 + GoVersion string `protobuf:"bytes,3,opt,name=go_version,json=goVersion,proto3" json:"go_version,omitempty"` 33 + DenoVersion string `protobuf:"bytes,4,opt,name=deno_version,json=denoVersion,proto3" json:"deno_version,omitempty"` 34 + } 35 + 36 + func (x *BuildInfo) Reset() { 37 + *x = BuildInfo{} 38 + if protoimpl.UnsafeEnabled { 39 + mi := &file_xesite_proto_msgTypes[0] 40 + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 41 + ms.StoreMessageInfo(mi) 42 + } 43 + } 44 + 45 + func (x *BuildInfo) String() string { 46 + return protoimpl.X.MessageStringOf(x) 47 + } 48 + 49 + func (*BuildInfo) ProtoMessage() {} 50 + 51 + func (x *BuildInfo) ProtoReflect() protoreflect.Message { 52 + mi := &file_xesite_proto_msgTypes[0] 53 + if protoimpl.UnsafeEnabled && x != nil { 54 + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 55 + if ms.LoadMessageInfo() == nil { 56 + ms.StoreMessageInfo(mi) 57 + } 58 + return ms 59 + } 60 + return mi.MessageOf(x) 61 + } 62 + 63 + // Deprecated: Use BuildInfo.ProtoReflect.Descriptor instead. 64 + func (*BuildInfo) Descriptor() ([]byte, []int) { 65 + return file_xesite_proto_rawDescGZIP(), []int{0} 66 + } 67 + 68 + func (x *BuildInfo) GetCommit() string { 69 + if x != nil { 70 + return x.Commit 71 + } 72 + return "" 73 + } 74 + 75 + func (x *BuildInfo) GetBuildTime() *timestamppb.Timestamp { 76 + if x != nil { 77 + return x.BuildTime 78 + } 79 + return nil 80 + } 81 + 82 + func (x *BuildInfo) GetGoVersion() string { 83 + if x != nil { 84 + return x.GoVersion 85 + } 86 + return "" 87 + } 88 + 89 + func (x *BuildInfo) GetDenoVersion() string { 90 + if x != nil { 91 + return x.DenoVersion 92 + } 93 + return "" 94 + } 95 + 96 + var File_xesite_proto protoreflect.FileDescriptor 97 + 98 + var file_xesite_proto_rawDesc = []byte{ 99 + 0x0a, 0x0c, 0x78, 0x65, 0x73, 0x69, 0x74, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0a, 100 + 0x78, 0x65, 0x69, 0x61, 0x73, 0x6f, 0x2e, 0x6e, 0x65, 0x74, 0x1a, 0x1b, 0x67, 0x6f, 0x6f, 0x67, 101 + 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x65, 0x6d, 0x70, 0x74, 102 + 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 103 + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 104 + 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xa0, 0x01, 0x0a, 0x09, 0x42, 0x75, 0x69, 105 + 0x6c, 0x64, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x16, 0x0a, 0x06, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 106 + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x12, 0x39, 107 + 0x0a, 0x0a, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 108 + 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 109 + 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 110 + 0x62, 0x75, 0x69, 0x6c, 0x64, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x67, 0x6f, 0x5f, 111 + 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x67, 112 + 0x6f, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x21, 0x0a, 0x0c, 0x64, 0x65, 0x6e, 0x6f, 113 + 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 114 + 0x64, 0x65, 0x6e, 0x6f, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x32, 0x41, 0x0a, 0x04, 0x4c, 115 + 0x75, 0x6d, 0x65, 0x12, 0x39, 0x0a, 0x08, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 116 + 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 117 + 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x15, 0x2e, 0x78, 0x65, 0x69, 0x61, 0x73, 0x6f, 118 + 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x49, 0x6e, 0x66, 0x6f, 0x42, 0x12, 119 + 0x5a, 0x10, 0x78, 0x65, 0x69, 0x61, 0x73, 0x6f, 0x2e, 0x6e, 0x65, 0x74, 0x2f, 0x76, 0x34, 0x2f, 120 + 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, 121 + } 122 + 123 + var ( 124 + file_xesite_proto_rawDescOnce sync.Once 125 + file_xesite_proto_rawDescData = file_xesite_proto_rawDesc 126 + ) 127 + 128 + func file_xesite_proto_rawDescGZIP() []byte { 129 + file_xesite_proto_rawDescOnce.Do(func() { 130 + file_xesite_proto_rawDescData = protoimpl.X.CompressGZIP(file_xesite_proto_rawDescData) 131 + }) 132 + return file_xesite_proto_rawDescData 133 + } 134 + 135 + var file_xesite_proto_msgTypes = make([]protoimpl.MessageInfo, 1) 136 + var file_xesite_proto_goTypes = []interface{}{ 137 + (*BuildInfo)(nil), // 0: xeiaso.net.BuildInfo 138 + (*timestamppb.Timestamp)(nil), // 1: google.protobuf.Timestamp 139 + (*emptypb.Empty)(nil), // 2: google.protobuf.Empty 140 + } 141 + var file_xesite_proto_depIdxs = []int32{ 142 + 1, // 0: xeiaso.net.BuildInfo.build_time:type_name -> google.protobuf.Timestamp 143 + 2, // 1: xeiaso.net.Lume.Metadata:input_type -> google.protobuf.Empty 144 + 0, // 2: xeiaso.net.Lume.Metadata:output_type -> xeiaso.net.BuildInfo 145 + 2, // [2:3] is the sub-list for method output_type 146 + 1, // [1:2] is the sub-list for method input_type 147 + 1, // [1:1] is the sub-list for extension type_name 148 + 1, // [1:1] is the sub-list for extension extendee 149 + 0, // [0:1] is the sub-list for field type_name 150 + } 151 + 152 + func init() { file_xesite_proto_init() } 153 + func file_xesite_proto_init() { 154 + if File_xesite_proto != nil { 155 + return 156 + } 157 + if !protoimpl.UnsafeEnabled { 158 + file_xesite_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { 159 + switch v := v.(*BuildInfo); i { 160 + case 0: 161 + return &v.state 162 + case 1: 163 + return &v.sizeCache 164 + case 2: 165 + return &v.unknownFields 166 + default: 167 + return nil 168 + } 169 + } 170 + } 171 + type x struct{} 172 + out := protoimpl.TypeBuilder{ 173 + File: protoimpl.DescBuilder{ 174 + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), 175 + RawDescriptor: file_xesite_proto_rawDesc, 176 + NumEnums: 0, 177 + NumMessages: 1, 178 + NumExtensions: 0, 179 + NumServices: 1, 180 + }, 181 + GoTypes: file_xesite_proto_goTypes, 182 + DependencyIndexes: file_xesite_proto_depIdxs, 183 + MessageInfos: file_xesite_proto_msgTypes, 184 + }.Build() 185 + File_xesite_proto = out.File 186 + file_xesite_proto_rawDesc = nil 187 + file_xesite_proto_goTypes = nil 188 + file_xesite_proto_depIdxs = nil 189 + }
+17
pb/xesite.proto
··· 1 + syntax = "proto3"; 2 + package xeiaso.net; 3 + option go_package = "xeiaso.net/v4/pb"; 4 + 5 + import "google/protobuf/empty.proto"; 6 + import "google/protobuf/timestamp.proto"; 7 + 8 + service Lume { 9 + rpc Metadata(google.protobuf.Empty) returns (BuildInfo); 10 + } 11 + 12 + message BuildInfo { 13 + string commit = 1; 14 + google.protobuf.Timestamp build_time = 2; 15 + string go_version = 3; 16 + string deno_version = 4; 17 + }
+1109
pb/xesite.twirp.go
··· 1 + // Code generated by protoc-gen-twirp v8.1.3, DO NOT EDIT. 2 + // source: xesite.proto 3 + 4 + package pb 5 + 6 + import context "context" 7 + import fmt "fmt" 8 + import http "net/http" 9 + import io "io" 10 + import json "encoding/json" 11 + import strconv "strconv" 12 + import strings "strings" 13 + 14 + import protojson "google.golang.org/protobuf/encoding/protojson" 15 + import proto "google.golang.org/protobuf/proto" 16 + import twirp "github.com/twitchtv/twirp" 17 + import ctxsetters "github.com/twitchtv/twirp/ctxsetters" 18 + 19 + import google_protobuf "google.golang.org/protobuf/types/known/emptypb" 20 + 21 + import bytes "bytes" 22 + import errors "errors" 23 + import path "path" 24 + import url "net/url" 25 + 26 + // Version compatibility assertion. 27 + // If the constant is not defined in the package, that likely means 28 + // the package needs to be updated to work with this generated code. 29 + // See https://twitchtv.github.io/twirp/docs/version_matrix.html 30 + const _ = twirp.TwirpPackageMinVersion_8_1_0 31 + 32 + // ============== 33 + // Lume Interface 34 + // ============== 35 + 36 + type Lume interface { 37 + Metadata(context.Context, *google_protobuf.Empty) (*BuildInfo, error) 38 + } 39 + 40 + // ==================== 41 + // Lume Protobuf Client 42 + // ==================== 43 + 44 + type lumeProtobufClient struct { 45 + client HTTPClient 46 + urls [1]string 47 + interceptor twirp.Interceptor 48 + opts twirp.ClientOptions 49 + } 50 + 51 + // NewLumeProtobufClient creates a Protobuf client that implements the Lume interface. 52 + // It communicates using Protobuf and can be configured with a custom HTTPClient. 53 + func NewLumeProtobufClient(baseURL string, client HTTPClient, opts ...twirp.ClientOption) Lume { 54 + if c, ok := client.(*http.Client); ok { 55 + client = withoutRedirects(c) 56 + } 57 + 58 + clientOpts := twirp.ClientOptions{} 59 + for _, o := range opts { 60 + o(&clientOpts) 61 + } 62 + 63 + // Using ReadOpt allows backwards and forwards compatibility with new options in the future 64 + literalURLs := false 65 + _ = clientOpts.ReadOpt("literalURLs", &literalURLs) 66 + var pathPrefix string 67 + if ok := clientOpts.ReadOpt("pathPrefix", &pathPrefix); !ok { 68 + pathPrefix = "/twirp" // default prefix 69 + } 70 + 71 + // Build method URLs: <baseURL>[<prefix>]/<package>.<Service>/<Method> 72 + serviceURL := sanitizeBaseURL(baseURL) 73 + serviceURL += baseServicePath(pathPrefix, "xeiaso.net", "Lume") 74 + urls := [1]string{ 75 + serviceURL + "Metadata", 76 + } 77 + 78 + return &lumeProtobufClient{ 79 + client: client, 80 + urls: urls, 81 + interceptor: twirp.ChainInterceptors(clientOpts.Interceptors...), 82 + opts: clientOpts, 83 + } 84 + } 85 + 86 + func (c *lumeProtobufClient) Metadata(ctx context.Context, in *google_protobuf.Empty) (*BuildInfo, error) { 87 + ctx = ctxsetters.WithPackageName(ctx, "xeiaso.net") 88 + ctx = ctxsetters.WithServiceName(ctx, "Lume") 89 + ctx = ctxsetters.WithMethodName(ctx, "Metadata") 90 + caller := c.callMetadata 91 + if c.interceptor != nil { 92 + caller = func(ctx context.Context, req *google_protobuf.Empty) (*BuildInfo, error) { 93 + resp, err := c.interceptor( 94 + func(ctx context.Context, req interface{}) (interface{}, error) { 95 + typedReq, ok := req.(*google_protobuf.Empty) 96 + if !ok { 97 + return nil, twirp.InternalError("failed type assertion req.(*google_protobuf.Empty) when calling interceptor") 98 + } 99 + return c.callMetadata(ctx, typedReq) 100 + }, 101 + )(ctx, req) 102 + if resp != nil { 103 + typedResp, ok := resp.(*BuildInfo) 104 + if !ok { 105 + return nil, twirp.InternalError("failed type assertion resp.(*BuildInfo) when calling interceptor") 106 + } 107 + return typedResp, err 108 + } 109 + return nil, err 110 + } 111 + } 112 + return caller(ctx, in) 113 + } 114 + 115 + func (c *lumeProtobufClient) callMetadata(ctx context.Context, in *google_protobuf.Empty) (*BuildInfo, error) { 116 + out := new(BuildInfo) 117 + ctx, err := doProtobufRequest(ctx, c.client, c.opts.Hooks, c.urls[0], in, out) 118 + if err != nil { 119 + twerr, ok := err.(twirp.Error) 120 + if !ok { 121 + twerr = twirp.InternalErrorWith(err) 122 + } 123 + callClientError(ctx, c.opts.Hooks, twerr) 124 + return nil, err 125 + } 126 + 127 + callClientResponseReceived(ctx, c.opts.Hooks) 128 + 129 + return out, nil 130 + } 131 + 132 + // ================ 133 + // Lume JSON Client 134 + // ================ 135 + 136 + type lumeJSONClient struct { 137 + client HTTPClient 138 + urls [1]string 139 + interceptor twirp.Interceptor 140 + opts twirp.ClientOptions 141 + } 142 + 143 + // NewLumeJSONClient creates a JSON client that implements the Lume interface. 144 + // It communicates using JSON and can be configured with a custom HTTPClient. 145 + func NewLumeJSONClient(baseURL string, client HTTPClient, opts ...twirp.ClientOption) Lume { 146 + if c, ok := client.(*http.Client); ok { 147 + client = withoutRedirects(c) 148 + } 149 + 150 + clientOpts := twirp.ClientOptions{} 151 + for _, o := range opts { 152 + o(&clientOpts) 153 + } 154 + 155 + // Using ReadOpt allows backwards and forwards compatibility with new options in the future 156 + literalURLs := false 157 + _ = clientOpts.ReadOpt("literalURLs", &literalURLs) 158 + var pathPrefix string 159 + if ok := clientOpts.ReadOpt("pathPrefix", &pathPrefix); !ok { 160 + pathPrefix = "/twirp" // default prefix 161 + } 162 + 163 + // Build method URLs: <baseURL>[<prefix>]/<package>.<Service>/<Method> 164 + serviceURL := sanitizeBaseURL(baseURL) 165 + serviceURL += baseServicePath(pathPrefix, "xeiaso.net", "Lume") 166 + urls := [1]string{ 167 + serviceURL + "Metadata", 168 + } 169 + 170 + return &lumeJSONClient{ 171 + client: client, 172 + urls: urls, 173 + interceptor: twirp.ChainInterceptors(clientOpts.Interceptors...), 174 + opts: clientOpts, 175 + } 176 + } 177 + 178 + func (c *lumeJSONClient) Metadata(ctx context.Context, in *google_protobuf.Empty) (*BuildInfo, error) { 179 + ctx = ctxsetters.WithPackageName(ctx, "xeiaso.net") 180 + ctx = ctxsetters.WithServiceName(ctx, "Lume") 181 + ctx = ctxsetters.WithMethodName(ctx, "Metadata") 182 + caller := c.callMetadata 183 + if c.interceptor != nil { 184 + caller = func(ctx context.Context, req *google_protobuf.Empty) (*BuildInfo, error) { 185 + resp, err := c.interceptor( 186 + func(ctx context.Context, req interface{}) (interface{}, error) { 187 + typedReq, ok := req.(*google_protobuf.Empty) 188 + if !ok { 189 + return nil, twirp.InternalError("failed type assertion req.(*google_protobuf.Empty) when calling interceptor") 190 + } 191 + return c.callMetadata(ctx, typedReq) 192 + }, 193 + )(ctx, req) 194 + if resp != nil { 195 + typedResp, ok := resp.(*BuildInfo) 196 + if !ok { 197 + return nil, twirp.InternalError("failed type assertion resp.(*BuildInfo) when calling interceptor") 198 + } 199 + return typedResp, err 200 + } 201 + return nil, err 202 + } 203 + } 204 + return caller(ctx, in) 205 + } 206 + 207 + func (c *lumeJSONClient) callMetadata(ctx context.Context, in *google_protobuf.Empty) (*BuildInfo, error) { 208 + out := new(BuildInfo) 209 + ctx, err := doJSONRequest(ctx, c.client, c.opts.Hooks, c.urls[0], in, out) 210 + if err != nil { 211 + twerr, ok := err.(twirp.Error) 212 + if !ok { 213 + twerr = twirp.InternalErrorWith(err) 214 + } 215 + callClientError(ctx, c.opts.Hooks, twerr) 216 + return nil, err 217 + } 218 + 219 + callClientResponseReceived(ctx, c.opts.Hooks) 220 + 221 + return out, nil 222 + } 223 + 224 + // =================== 225 + // Lume Server Handler 226 + // =================== 227 + 228 + type lumeServer struct { 229 + Lume 230 + interceptor twirp.Interceptor 231 + hooks *twirp.ServerHooks 232 + pathPrefix string // prefix for routing 233 + jsonSkipDefaults bool // do not include unpopulated fields (default values) in the response 234 + jsonCamelCase bool // JSON fields are serialized as lowerCamelCase rather than keeping the original proto names 235 + } 236 + 237 + // NewLumeServer builds a TwirpServer that can be used as an http.Handler to handle 238 + // HTTP requests that are routed to the right method in the provided svc implementation. 239 + // The opts are twirp.ServerOption modifiers, for example twirp.WithServerHooks(hooks). 240 + func NewLumeServer(svc Lume, opts ...interface{}) TwirpServer { 241 + serverOpts := newServerOpts(opts) 242 + 243 + // Using ReadOpt allows backwards and forwards compatibility with new options in the future 244 + jsonSkipDefaults := false 245 + _ = serverOpts.ReadOpt("jsonSkipDefaults", &jsonSkipDefaults) 246 + jsonCamelCase := false 247 + _ = serverOpts.ReadOpt("jsonCamelCase", &jsonCamelCase) 248 + var pathPrefix string 249 + if ok := serverOpts.ReadOpt("pathPrefix", &pathPrefix); !ok { 250 + pathPrefix = "/twirp" // default prefix 251 + } 252 + 253 + return &lumeServer{ 254 + Lume: svc, 255 + hooks: serverOpts.Hooks, 256 + interceptor: twirp.ChainInterceptors(serverOpts.Interceptors...), 257 + pathPrefix: pathPrefix, 258 + jsonSkipDefaults: jsonSkipDefaults, 259 + jsonCamelCase: jsonCamelCase, 260 + } 261 + } 262 + 263 + // writeError writes an HTTP response with a valid Twirp error format, and triggers hooks. 264 + // If err is not a twirp.Error, it will get wrapped with twirp.InternalErrorWith(err) 265 + func (s *lumeServer) writeError(ctx context.Context, resp http.ResponseWriter, err error) { 266 + writeError(ctx, resp, err, s.hooks) 267 + } 268 + 269 + // handleRequestBodyError is used to handle error when the twirp server cannot read request 270 + func (s *lumeServer) handleRequestBodyError(ctx context.Context, resp http.ResponseWriter, msg string, err error) { 271 + if context.Canceled == ctx.Err() { 272 + s.writeError(ctx, resp, twirp.NewError(twirp.Canceled, "failed to read request: context canceled")) 273 + return 274 + } 275 + if context.DeadlineExceeded == ctx.Err() { 276 + s.writeError(ctx, resp, twirp.NewError(twirp.DeadlineExceeded, "failed to read request: deadline exceeded")) 277 + return 278 + } 279 + s.writeError(ctx, resp, twirp.WrapError(malformedRequestError(msg), err)) 280 + } 281 + 282 + // LumePathPrefix is a convenience constant that may identify URL paths. 283 + // Should be used with caution, it only matches routes generated by Twirp Go clients, 284 + // with the default "/twirp" prefix and default CamelCase service and method names. 285 + // More info: https://twitchtv.github.io/twirp/docs/routing.html 286 + const LumePathPrefix = "/twirp/xeiaso.net.Lume/" 287 + 288 + func (s *lumeServer) ServeHTTP(resp http.ResponseWriter, req *http.Request) { 289 + ctx := req.Context() 290 + ctx = ctxsetters.WithPackageName(ctx, "xeiaso.net") 291 + ctx = ctxsetters.WithServiceName(ctx, "Lume") 292 + ctx = ctxsetters.WithResponseWriter(ctx, resp) 293 + 294 + var err error 295 + ctx, err = callRequestReceived(ctx, s.hooks) 296 + if err != nil { 297 + s.writeError(ctx, resp, err) 298 + return 299 + } 300 + 301 + if req.Method != "POST" { 302 + msg := fmt.Sprintf("unsupported method %q (only POST is allowed)", req.Method) 303 + s.writeError(ctx, resp, badRouteError(msg, req.Method, req.URL.Path)) 304 + return 305 + } 306 + 307 + // Verify path format: [<prefix>]/<package>.<Service>/<Method> 308 + prefix, pkgService, method := parseTwirpPath(req.URL.Path) 309 + if pkgService != "xeiaso.net.Lume" { 310 + msg := fmt.Sprintf("no handler for path %q", req.URL.Path) 311 + s.writeError(ctx, resp, badRouteError(msg, req.Method, req.URL.Path)) 312 + return 313 + } 314 + if prefix != s.pathPrefix { 315 + msg := fmt.Sprintf("invalid path prefix %q, expected %q, on path %q", prefix, s.pathPrefix, req.URL.Path) 316 + s.writeError(ctx, resp, badRouteError(msg, req.Method, req.URL.Path)) 317 + return 318 + } 319 + 320 + switch method { 321 + case "Metadata": 322 + s.serveMetadata(ctx, resp, req) 323 + return 324 + default: 325 + msg := fmt.Sprintf("no handler for path %q", req.URL.Path) 326 + s.writeError(ctx, resp, badRouteError(msg, req.Method, req.URL.Path)) 327 + return 328 + } 329 + } 330 + 331 + func (s *lumeServer) serveMetadata(ctx context.Context, resp http.ResponseWriter, req *http.Request) { 332 + header := req.Header.Get("Content-Type") 333 + i := strings.Index(header, ";") 334 + if i == -1 { 335 + i = len(header) 336 + } 337 + switch strings.TrimSpace(strings.ToLower(header[:i])) { 338 + case "application/json": 339 + s.serveMetadataJSON(ctx, resp, req) 340 + case "application/protobuf": 341 + s.serveMetadataProtobuf(ctx, resp, req) 342 + default: 343 + msg := fmt.Sprintf("unexpected Content-Type: %q", req.Header.Get("Content-Type")) 344 + twerr := badRouteError(msg, req.Method, req.URL.Path) 345 + s.writeError(ctx, resp, twerr) 346 + } 347 + } 348 + 349 + func (s *lumeServer) serveMetadataJSON(ctx context.Context, resp http.ResponseWriter, req *http.Request) { 350 + var err error 351 + ctx = ctxsetters.WithMethodName(ctx, "Metadata") 352 + ctx, err = callRequestRouted(ctx, s.hooks) 353 + if err != nil { 354 + s.writeError(ctx, resp, err) 355 + return 356 + } 357 + 358 + d := json.NewDecoder(req.Body) 359 + rawReqBody := json.RawMessage{} 360 + if err := d.Decode(&rawReqBody); err != nil { 361 + s.handleRequestBodyError(ctx, resp, "the json request could not be decoded", err) 362 + return 363 + } 364 + reqContent := new(google_protobuf.Empty) 365 + unmarshaler := protojson.UnmarshalOptions{DiscardUnknown: true} 366 + if err = unmarshaler.Unmarshal(rawReqBody, reqContent); err != nil { 367 + s.handleRequestBodyError(ctx, resp, "the json request could not be decoded", err) 368 + return 369 + } 370 + 371 + handler := s.Lume.Metadata 372 + if s.interceptor != nil { 373 + handler = func(ctx context.Context, req *google_protobuf.Empty) (*BuildInfo, error) { 374 + resp, err := s.interceptor( 375 + func(ctx context.Context, req interface{}) (interface{}, error) { 376 + typedReq, ok := req.(*google_protobuf.Empty) 377 + if !ok { 378 + return nil, twirp.InternalError("failed type assertion req.(*google_protobuf.Empty) when calling interceptor") 379 + } 380 + return s.Lume.Metadata(ctx, typedReq) 381 + }, 382 + )(ctx, req) 383 + if resp != nil { 384 + typedResp, ok := resp.(*BuildInfo) 385 + if !ok { 386 + return nil, twirp.InternalError("failed type assertion resp.(*BuildInfo) when calling interceptor") 387 + } 388 + return typedResp, err 389 + } 390 + return nil, err 391 + } 392 + } 393 + 394 + // Call service method 395 + var respContent *BuildInfo 396 + func() { 397 + defer ensurePanicResponses(ctx, resp, s.hooks) 398 + respContent, err = handler(ctx, reqContent) 399 + }() 400 + 401 + if err != nil { 402 + s.writeError(ctx, resp, err) 403 + return 404 + } 405 + if respContent == nil { 406 + s.writeError(ctx, resp, twirp.InternalError("received a nil *BuildInfo and nil error while calling Metadata. nil responses are not supported")) 407 + return 408 + } 409 + 410 + ctx = callResponsePrepared(ctx, s.hooks) 411 + 412 + marshaler := &protojson.MarshalOptions{UseProtoNames: !s.jsonCamelCase, EmitUnpopulated: !s.jsonSkipDefaults} 413 + respBytes, err := marshaler.Marshal(respContent) 414 + if err != nil { 415 + s.writeError(ctx, resp, wrapInternal(err, "failed to marshal json response")) 416 + return 417 + } 418 + 419 + ctx = ctxsetters.WithStatusCode(ctx, http.StatusOK) 420 + resp.Header().Set("Content-Type", "application/json") 421 + resp.Header().Set("Content-Length", strconv.Itoa(len(respBytes))) 422 + resp.WriteHeader(http.StatusOK) 423 + 424 + if n, err := resp.Write(respBytes); err != nil { 425 + msg := fmt.Sprintf("failed to write response, %d of %d bytes written: %s", n, len(respBytes), err.Error()) 426 + twerr := twirp.NewError(twirp.Unknown, msg) 427 + ctx = callError(ctx, s.hooks, twerr) 428 + } 429 + callResponseSent(ctx, s.hooks) 430 + } 431 + 432 + func (s *lumeServer) serveMetadataProtobuf(ctx context.Context, resp http.ResponseWriter, req *http.Request) { 433 + var err error 434 + ctx = ctxsetters.WithMethodName(ctx, "Metadata") 435 + ctx, err = callRequestRouted(ctx, s.hooks) 436 + if err != nil { 437 + s.writeError(ctx, resp, err) 438 + return 439 + } 440 + 441 + buf, err := io.ReadAll(req.Body) 442 + if err != nil { 443 + s.handleRequestBodyError(ctx, resp, "failed to read request body", err) 444 + return 445 + } 446 + reqContent := new(google_protobuf.Empty) 447 + if err = proto.Unmarshal(buf, reqContent); err != nil { 448 + s.writeError(ctx, resp, malformedRequestError("the protobuf request could not be decoded")) 449 + return 450 + } 451 + 452 + handler := s.Lume.Metadata 453 + if s.interceptor != nil { 454 + handler = func(ctx context.Context, req *google_protobuf.Empty) (*BuildInfo, error) { 455 + resp, err := s.interceptor( 456 + func(ctx context.Context, req interface{}) (interface{}, error) { 457 + typedReq, ok := req.(*google_protobuf.Empty) 458 + if !ok { 459 + return nil, twirp.InternalError("failed type assertion req.(*google_protobuf.Empty) when calling interceptor") 460 + } 461 + return s.Lume.Metadata(ctx, typedReq) 462 + }, 463 + )(ctx, req) 464 + if resp != nil { 465 + typedResp, ok := resp.(*BuildInfo) 466 + if !ok { 467 + return nil, twirp.InternalError("failed type assertion resp.(*BuildInfo) when calling interceptor") 468 + } 469 + return typedResp, err 470 + } 471 + return nil, err 472 + } 473 + } 474 + 475 + // Call service method 476 + var respContent *BuildInfo 477 + func() { 478 + defer ensurePanicResponses(ctx, resp, s.hooks) 479 + respContent, err = handler(ctx, reqContent) 480 + }() 481 + 482 + if err != nil { 483 + s.writeError(ctx, resp, err) 484 + return 485 + } 486 + if respContent == nil { 487 + s.writeError(ctx, resp, twirp.InternalError("received a nil *BuildInfo and nil error while calling Metadata. nil responses are not supported")) 488 + return 489 + } 490 + 491 + ctx = callResponsePrepared(ctx, s.hooks) 492 + 493 + respBytes, err := proto.Marshal(respContent) 494 + if err != nil { 495 + s.writeError(ctx, resp, wrapInternal(err, "failed to marshal proto response")) 496 + return 497 + } 498 + 499 + ctx = ctxsetters.WithStatusCode(ctx, http.StatusOK) 500 + resp.Header().Set("Content-Type", "application/protobuf") 501 + resp.Header().Set("Content-Length", strconv.Itoa(len(respBytes))) 502 + resp.WriteHeader(http.StatusOK) 503 + if n, err := resp.Write(respBytes); err != nil { 504 + msg := fmt.Sprintf("failed to write response, %d of %d bytes written: %s", n, len(respBytes), err.Error()) 505 + twerr := twirp.NewError(twirp.Unknown, msg) 506 + ctx = callError(ctx, s.hooks, twerr) 507 + } 508 + callResponseSent(ctx, s.hooks) 509 + } 510 + 511 + func (s *lumeServer) ServiceDescriptor() ([]byte, int) { 512 + return twirpFileDescriptor0, 0 513 + } 514 + 515 + func (s *lumeServer) ProtocGenTwirpVersion() string { 516 + return "v8.1.3" 517 + } 518 + 519 + // PathPrefix returns the base service path, in the form: "/<prefix>/<package>.<Service>/" 520 + // that is everything in a Twirp route except for the <Method>. This can be used for routing, 521 + // for example to identify the requests that are targeted to this service in a mux. 522 + func (s *lumeServer) PathPrefix() string { 523 + return baseServicePath(s.pathPrefix, "xeiaso.net", "Lume") 524 + } 525 + 526 + // ===== 527 + // Utils 528 + // ===== 529 + 530 + // HTTPClient is the interface used by generated clients to send HTTP requests. 531 + // It is fulfilled by *(net/http).Client, which is sufficient for most users. 532 + // Users can provide their own implementation for special retry policies. 533 + // 534 + // HTTPClient implementations should not follow redirects. Redirects are 535 + // automatically disabled if *(net/http).Client is passed to client 536 + // constructors. See the withoutRedirects function in this file for more 537 + // details. 538 + type HTTPClient interface { 539 + Do(req *http.Request) (*http.Response, error) 540 + } 541 + 542 + // TwirpServer is the interface generated server structs will support: they're 543 + // HTTP handlers with additional methods for accessing metadata about the 544 + // service. Those accessors are a low-level API for building reflection tools. 545 + // Most people can think of TwirpServers as just http.Handlers. 546 + type TwirpServer interface { 547 + http.Handler 548 + 549 + // ServiceDescriptor returns gzipped bytes describing the .proto file that 550 + // this service was generated from. Once unzipped, the bytes can be 551 + // unmarshalled as a 552 + // google.golang.org/protobuf/types/descriptorpb.FileDescriptorProto. 553 + // 554 + // The returned integer is the index of this particular service within that 555 + // FileDescriptorProto's 'Service' slice of ServiceDescriptorProtos. This is a 556 + // low-level field, expected to be used for reflection. 557 + ServiceDescriptor() ([]byte, int) 558 + 559 + // ProtocGenTwirpVersion is the semantic version string of the version of 560 + // twirp used to generate this file. 561 + ProtocGenTwirpVersion() string 562 + 563 + // PathPrefix returns the HTTP URL path prefix for all methods handled by this 564 + // service. This can be used with an HTTP mux to route Twirp requests. 565 + // The path prefix is in the form: "/<prefix>/<package>.<Service>/" 566 + // that is, everything in a Twirp route except for the <Method> at the end. 567 + PathPrefix() string 568 + } 569 + 570 + func newServerOpts(opts []interface{}) *twirp.ServerOptions { 571 + serverOpts := &twirp.ServerOptions{} 572 + for _, opt := range opts { 573 + switch o := opt.(type) { 574 + case twirp.ServerOption: 575 + o(serverOpts) 576 + case *twirp.ServerHooks: // backwards compatibility, allow to specify hooks as an argument 577 + twirp.WithServerHooks(o)(serverOpts) 578 + case nil: // backwards compatibility, allow nil value for the argument 579 + continue 580 + default: 581 + panic(fmt.Sprintf("Invalid option type %T, please use a twirp.ServerOption", o)) 582 + } 583 + } 584 + return serverOpts 585 + } 586 + 587 + // WriteError writes an HTTP response with a valid Twirp error format (code, msg, meta). 588 + // Useful outside of the Twirp server (e.g. http middleware), but does not trigger hooks. 589 + // If err is not a twirp.Error, it will get wrapped with twirp.InternalErrorWith(err) 590 + func WriteError(resp http.ResponseWriter, err error) { 591 + writeError(context.Background(), resp, err, nil) 592 + } 593 + 594 + // writeError writes Twirp errors in the response and triggers hooks. 595 + func writeError(ctx context.Context, resp http.ResponseWriter, err error, hooks *twirp.ServerHooks) { 596 + // Convert to a twirp.Error. Non-twirp errors are converted to internal errors. 597 + var twerr twirp.Error 598 + if !errors.As(err, &twerr) { 599 + twerr = twirp.InternalErrorWith(err) 600 + } 601 + 602 + statusCode := twirp.ServerHTTPStatusFromErrorCode(twerr.Code()) 603 + ctx = ctxsetters.WithStatusCode(ctx, statusCode) 604 + ctx = callError(ctx, hooks, twerr) 605 + 606 + respBody := marshalErrorToJSON(twerr) 607 + 608 + resp.Header().Set("Content-Type", "application/json") // Error responses are always JSON 609 + resp.Header().Set("Content-Length", strconv.Itoa(len(respBody))) 610 + resp.WriteHeader(statusCode) // set HTTP status code and send response 611 + 612 + _, writeErr := resp.Write(respBody) 613 + if writeErr != nil { 614 + // We have three options here. We could log the error, call the Error 615 + // hook, or just silently ignore the error. 616 + // 617 + // Logging is unacceptable because we don't have a user-controlled 618 + // logger; writing out to stderr without permission is too rude. 619 + // 620 + // Calling the Error hook would confuse users: it would mean the Error 621 + // hook got called twice for one request, which is likely to lead to 622 + // duplicated log messages and metrics, no matter how well we document 623 + // the behavior. 624 + // 625 + // Silently ignoring the error is our least-bad option. It's highly 626 + // likely that the connection is broken and the original 'err' says 627 + // so anyway. 628 + _ = writeErr 629 + } 630 + 631 + callResponseSent(ctx, hooks) 632 + } 633 + 634 + // sanitizeBaseURL parses the the baseURL, and adds the "http" scheme if needed. 635 + // If the URL is unparsable, the baseURL is returned unchanged. 636 + func sanitizeBaseURL(baseURL string) string { 637 + u, err := url.Parse(baseURL) 638 + if err != nil { 639 + return baseURL // invalid URL will fail later when making requests 640 + } 641 + if u.Scheme == "" { 642 + u.Scheme = "http" 643 + } 644 + return u.String() 645 + } 646 + 647 + // baseServicePath composes the path prefix for the service (without <Method>). 648 + // e.g.: baseServicePath("/twirp", "my.pkg", "MyService") 649 + // 650 + // returns => "/twirp/my.pkg.MyService/" 651 + // 652 + // e.g.: baseServicePath("", "", "MyService") 653 + // 654 + // returns => "/MyService/" 655 + func baseServicePath(prefix, pkg, service string) string { 656 + fullServiceName := service 657 + if pkg != "" { 658 + fullServiceName = pkg + "." + service 659 + } 660 + return path.Join("/", prefix, fullServiceName) + "/" 661 + } 662 + 663 + // parseTwirpPath extracts path components form a valid Twirp route. 664 + // Expected format: "[<prefix>]/<package>.<Service>/<Method>" 665 + // e.g.: prefix, pkgService, method := parseTwirpPath("/twirp/pkg.Svc/MakeHat") 666 + func parseTwirpPath(path string) (string, string, string) { 667 + parts := strings.Split(path, "/") 668 + if len(parts) < 2 { 669 + return "", "", "" 670 + } 671 + method := parts[len(parts)-1] 672 + pkgService := parts[len(parts)-2] 673 + prefix := strings.Join(parts[0:len(parts)-2], "/") 674 + return prefix, pkgService, method 675 + } 676 + 677 + // getCustomHTTPReqHeaders retrieves a copy of any headers that are set in 678 + // a context through the twirp.WithHTTPRequestHeaders function. 679 + // If there are no headers set, or if they have the wrong type, nil is returned. 680 + func getCustomHTTPReqHeaders(ctx context.Context) http.Header { 681 + header, ok := twirp.HTTPRequestHeaders(ctx) 682 + if !ok || header == nil { 683 + return nil 684 + } 685 + copied := make(http.Header) 686 + for k, vv := range header { 687 + if vv == nil { 688 + copied[k] = nil 689 + continue 690 + } 691 + copied[k] = make([]string, len(vv)) 692 + copy(copied[k], vv) 693 + } 694 + return copied 695 + } 696 + 697 + // newRequest makes an http.Request from a client, adding common headers. 698 + func newRequest(ctx context.Context, url string, reqBody io.Reader, contentType string) (*http.Request, error) { 699 + req, err := http.NewRequest("POST", url, reqBody) 700 + if err != nil { 701 + return nil, err 702 + } 703 + req = req.WithContext(ctx) 704 + if customHeader := getCustomHTTPReqHeaders(ctx); customHeader != nil { 705 + req.Header = customHeader 706 + } 707 + req.Header.Set("Accept", contentType) 708 + req.Header.Set("Content-Type", contentType) 709 + req.Header.Set("Twirp-Version", "v8.1.3") 710 + return req, nil 711 + } 712 + 713 + // JSON serialization for errors 714 + type twerrJSON struct { 715 + Code string `json:"code"` 716 + Msg string `json:"msg"` 717 + Meta map[string]string `json:"meta,omitempty"` 718 + } 719 + 720 + // marshalErrorToJSON returns JSON from a twirp.Error, that can be used as HTTP error response body. 721 + // If serialization fails, it will use a descriptive Internal error instead. 722 + func marshalErrorToJSON(twerr twirp.Error) []byte { 723 + // make sure that msg is not too large 724 + msg := twerr.Msg() 725 + if len(msg) > 1e6 { 726 + msg = msg[:1e6] 727 + } 728 + 729 + tj := twerrJSON{ 730 + Code: string(twerr.Code()), 731 + Msg: msg, 732 + Meta: twerr.MetaMap(), 733 + } 734 + 735 + buf, err := json.Marshal(&tj) 736 + if err != nil { 737 + buf = []byte("{\"type\": \"" + twirp.Internal + "\", \"msg\": \"There was an error but it could not be serialized into JSON\"}") // fallback 738 + } 739 + 740 + return buf 741 + } 742 + 743 + // errorFromResponse builds a twirp.Error from a non-200 HTTP response. 744 + // If the response has a valid serialized Twirp error, then it's returned. 745 + // If not, the response status code is used to generate a similar twirp 746 + // error. See twirpErrorFromIntermediary for more info on intermediary errors. 747 + func errorFromResponse(resp *http.Response) twirp.Error { 748 + statusCode := resp.StatusCode 749 + statusText := http.StatusText(statusCode) 750 + 751 + if isHTTPRedirect(statusCode) { 752 + // Unexpected redirect: it must be an error from an intermediary. 753 + // Twirp clients don't follow redirects automatically, Twirp only handles 754 + // POST requests, redirects should only happen on GET and HEAD requests. 755 + location := resp.Header.Get("Location") 756 + msg := fmt.Sprintf("unexpected HTTP status code %d %q received, Location=%q", statusCode, statusText, location) 757 + return twirpErrorFromIntermediary(statusCode, msg, location) 758 + } 759 + 760 + respBodyBytes, err := io.ReadAll(resp.Body) 761 + if err != nil { 762 + return wrapInternal(err, "failed to read server error response body") 763 + } 764 + 765 + var tj twerrJSON 766 + dec := json.NewDecoder(bytes.NewReader(respBodyBytes)) 767 + dec.DisallowUnknownFields() 768 + if err := dec.Decode(&tj); err != nil || tj.Code == "" { 769 + // Invalid JSON response; it must be an error from an intermediary. 770 + msg := fmt.Sprintf("Error from intermediary with HTTP status code %d %q", statusCode, statusText) 771 + return twirpErrorFromIntermediary(statusCode, msg, string(respBodyBytes)) 772 + } 773 + 774 + errorCode := twirp.ErrorCode(tj.Code) 775 + if !twirp.IsValidErrorCode(errorCode) { 776 + msg := "invalid type returned from server error response: " + tj.Code 777 + return twirp.InternalError(msg).WithMeta("body", string(respBodyBytes)) 778 + } 779 + 780 + twerr := twirp.NewError(errorCode, tj.Msg) 781 + for k, v := range tj.Meta { 782 + twerr = twerr.WithMeta(k, v) 783 + } 784 + return twerr 785 + } 786 + 787 + // twirpErrorFromIntermediary maps HTTP errors from non-twirp sources to twirp errors. 788 + // The mapping is similar to gRPC: https://github.com/grpc/grpc/blob/master/doc/http-grpc-status-mapping.md. 789 + // Returned twirp Errors have some additional metadata for inspection. 790 + func twirpErrorFromIntermediary(status int, msg string, bodyOrLocation string) twirp.Error { 791 + var code twirp.ErrorCode 792 + if isHTTPRedirect(status) { // 3xx 793 + code = twirp.Internal 794 + } else { 795 + switch status { 796 + case 400: // Bad Request 797 + code = twirp.Internal 798 + case 401: // Unauthorized 799 + code = twirp.Unauthenticated 800 + case 403: // Forbidden 801 + code = twirp.PermissionDenied 802 + case 404: // Not Found 803 + code = twirp.BadRoute 804 + case 429: // Too Many Requests 805 + code = twirp.ResourceExhausted 806 + case 502, 503, 504: // Bad Gateway, Service Unavailable, Gateway Timeout 807 + code = twirp.Unavailable 808 + default: // All other codes 809 + code = twirp.Unknown 810 + } 811 + } 812 + 813 + twerr := twirp.NewError(code, msg) 814 + twerr = twerr.WithMeta("http_error_from_intermediary", "true") // to easily know if this error was from intermediary 815 + twerr = twerr.WithMeta("status_code", strconv.Itoa(status)) 816 + if isHTTPRedirect(status) { 817 + twerr = twerr.WithMeta("location", bodyOrLocation) 818 + } else { 819 + twerr = twerr.WithMeta("body", bodyOrLocation) 820 + } 821 + return twerr 822 + } 823 + 824 + func isHTTPRedirect(status int) bool { 825 + return status >= 300 && status <= 399 826 + } 827 + 828 + // wrapInternal wraps an error with a prefix as an Internal error. 829 + // The original error cause is accessible by github.com/pkg/errors.Cause. 830 + func wrapInternal(err error, prefix string) twirp.Error { 831 + return twirp.InternalErrorWith(&wrappedError{prefix: prefix, cause: err}) 832 + } 833 + 834 + type wrappedError struct { 835 + prefix string 836 + cause error 837 + } 838 + 839 + func (e *wrappedError) Error() string { return e.prefix + ": " + e.cause.Error() } 840 + func (e *wrappedError) Unwrap() error { return e.cause } // for go1.13 + errors.Is/As 841 + func (e *wrappedError) Cause() error { return e.cause } // for github.com/pkg/errors 842 + 843 + // ensurePanicResponses makes sure that rpc methods causing a panic still result in a Twirp Internal 844 + // error response (status 500), and error hooks are properly called with the panic wrapped as an error. 845 + // The panic is re-raised so it can be handled normally with middleware. 846 + func ensurePanicResponses(ctx context.Context, resp http.ResponseWriter, hooks *twirp.ServerHooks) { 847 + if r := recover(); r != nil { 848 + // Wrap the panic as an error so it can be passed to error hooks. 849 + // The original error is accessible from error hooks, but not visible in the response. 850 + err := errFromPanic(r) 851 + twerr := &internalWithCause{msg: "Internal service panic", cause: err} 852 + // Actually write the error 853 + writeError(ctx, resp, twerr, hooks) 854 + // If possible, flush the error to the wire. 855 + f, ok := resp.(http.Flusher) 856 + if ok { 857 + f.Flush() 858 + } 859 + 860 + panic(r) 861 + } 862 + } 863 + 864 + // errFromPanic returns the typed error if the recovered panic is an error, otherwise formats as error. 865 + func errFromPanic(p interface{}) error { 866 + if err, ok := p.(error); ok { 867 + return err 868 + } 869 + return fmt.Errorf("panic: %v", p) 870 + } 871 + 872 + // internalWithCause is a Twirp Internal error wrapping an original error cause, 873 + // but the original error message is not exposed on Msg(). The original error 874 + // can be checked with go1.13+ errors.Is/As, and also by (github.com/pkg/errors).Unwrap 875 + type internalWithCause struct { 876 + msg string 877 + cause error 878 + } 879 + 880 + func (e *internalWithCause) Unwrap() error { return e.cause } // for go1.13 + errors.Is/As 881 + func (e *internalWithCause) Cause() error { return e.cause } // for github.com/pkg/errors 882 + func (e *internalWithCause) Error() string { return e.msg + ": " + e.cause.Error() } 883 + func (e *internalWithCause) Code() twirp.ErrorCode { return twirp.Internal } 884 + func (e *internalWithCause) Msg() string { return e.msg } 885 + func (e *internalWithCause) Meta(key string) string { return "" } 886 + func (e *internalWithCause) MetaMap() map[string]string { return nil } 887 + func (e *internalWithCause) WithMeta(key string, val string) twirp.Error { return e } 888 + 889 + // malformedRequestError is used when the twirp server cannot unmarshal a request 890 + func malformedRequestError(msg string) twirp.Error { 891 + return twirp.NewError(twirp.Malformed, msg) 892 + } 893 + 894 + // badRouteError is used when the twirp server cannot route a request 895 + func badRouteError(msg string, method, url string) twirp.Error { 896 + err := twirp.NewError(twirp.BadRoute, msg) 897 + err = err.WithMeta("twirp_invalid_route", method+" "+url) 898 + return err 899 + } 900 + 901 + // withoutRedirects makes sure that the POST request can not be redirected. 902 + // The standard library will, by default, redirect requests (including POSTs) if it gets a 302 or 903 + // 303 response, and also 301s in go1.8. It redirects by making a second request, changing the 904 + // method to GET and removing the body. This produces very confusing error messages, so instead we 905 + // set a redirect policy that always errors. This stops Go from executing the redirect. 906 + // 907 + // We have to be a little careful in case the user-provided http.Client has its own CheckRedirect 908 + // policy - if so, we'll run through that policy first. 909 + // 910 + // Because this requires modifying the http.Client, we make a new copy of the client and return it. 911 + func withoutRedirects(in *http.Client) *http.Client { 912 + copy := *in 913 + copy.CheckRedirect = func(req *http.Request, via []*http.Request) error { 914 + if in.CheckRedirect != nil { 915 + // Run the input's redirect if it exists, in case it has side effects, but ignore any error it 916 + // returns, since we want to use ErrUseLastResponse. 917 + err := in.CheckRedirect(req, via) 918 + _ = err // Silly, but this makes sure generated code passes errcheck -blank, which some people use. 919 + } 920 + return http.ErrUseLastResponse 921 + } 922 + return &copy 923 + } 924 + 925 + // doProtobufRequest makes a Protobuf request to the remote Twirp service. 926 + func doProtobufRequest(ctx context.Context, client HTTPClient, hooks *twirp.ClientHooks, url string, in, out proto.Message) (_ context.Context, err error) { 927 + reqBodyBytes, err := proto.Marshal(in) 928 + if err != nil { 929 + return ctx, wrapInternal(err, "failed to marshal proto request") 930 + } 931 + reqBody := bytes.NewBuffer(reqBodyBytes) 932 + if err = ctx.Err(); err != nil { 933 + return ctx, wrapInternal(err, "aborted because context was done") 934 + } 935 + 936 + req, err := newRequest(ctx, url, reqBody, "application/protobuf") 937 + if err != nil { 938 + return ctx, wrapInternal(err, "could not build request") 939 + } 940 + ctx, err = callClientRequestPrepared(ctx, hooks, req) 941 + if err != nil { 942 + return ctx, err 943 + } 944 + 945 + req = req.WithContext(ctx) 946 + resp, err := client.Do(req) 947 + if err != nil { 948 + return ctx, wrapInternal(err, "failed to do request") 949 + } 950 + defer func() { _ = resp.Body.Close() }() 951 + 952 + if err = ctx.Err(); err != nil { 953 + return ctx, wrapInternal(err, "aborted because context was done") 954 + } 955 + 956 + if resp.StatusCode != 200 { 957 + return ctx, errorFromResponse(resp) 958 + } 959 + 960 + respBodyBytes, err := io.ReadAll(resp.Body) 961 + if err != nil { 962 + return ctx, wrapInternal(err, "failed to read response body") 963 + } 964 + if err = ctx.Err(); err != nil { 965 + return ctx, wrapInternal(err, "aborted because context was done") 966 + } 967 + 968 + if err = proto.Unmarshal(respBodyBytes, out); err != nil { 969 + return ctx, wrapInternal(err, "failed to unmarshal proto response") 970 + } 971 + return ctx, nil 972 + } 973 + 974 + // doJSONRequest makes a JSON request to the remote Twirp service. 975 + func doJSONRequest(ctx context.Context, client HTTPClient, hooks *twirp.ClientHooks, url string, in, out proto.Message) (_ context.Context, err error) { 976 + marshaler := &protojson.MarshalOptions{UseProtoNames: true} 977 + reqBytes, err := marshaler.Marshal(in) 978 + if err != nil { 979 + return ctx, wrapInternal(err, "failed to marshal json request") 980 + } 981 + if err = ctx.Err(); err != nil { 982 + return ctx, wrapInternal(err, "aborted because context was done") 983 + } 984 + 985 + req, err := newRequest(ctx, url, bytes.NewReader(reqBytes), "application/json") 986 + if err != nil { 987 + return ctx, wrapInternal(err, "could not build request") 988 + } 989 + ctx, err = callClientRequestPrepared(ctx, hooks, req) 990 + if err != nil { 991 + return ctx, err 992 + } 993 + 994 + req = req.WithContext(ctx) 995 + resp, err := client.Do(req) 996 + if err != nil { 997 + return ctx, wrapInternal(err, "failed to do request") 998 + } 999 + 1000 + defer func() { 1001 + cerr := resp.Body.Close() 1002 + if err == nil && cerr != nil { 1003 + err = wrapInternal(cerr, "failed to close response body") 1004 + } 1005 + }() 1006 + 1007 + if err = ctx.Err(); err != nil { 1008 + return ctx, wrapInternal(err, "aborted because context was done") 1009 + } 1010 + 1011 + if resp.StatusCode != 200 { 1012 + return ctx, errorFromResponse(resp) 1013 + } 1014 + 1015 + d := json.NewDecoder(resp.Body) 1016 + rawRespBody := json.RawMessage{} 1017 + if err := d.Decode(&rawRespBody); err != nil { 1018 + return ctx, wrapInternal(err, "failed to unmarshal json response") 1019 + } 1020 + unmarshaler := protojson.UnmarshalOptions{DiscardUnknown: true} 1021 + if err = unmarshaler.Unmarshal(rawRespBody, out); err != nil { 1022 + return ctx, wrapInternal(err, "failed to unmarshal json response") 1023 + } 1024 + if err = ctx.Err(); err != nil { 1025 + return ctx, wrapInternal(err, "aborted because context was done") 1026 + } 1027 + return ctx, nil 1028 + } 1029 + 1030 + // Call twirp.ServerHooks.RequestReceived if the hook is available 1031 + func callRequestReceived(ctx context.Context, h *twirp.ServerHooks) (context.Context, error) { 1032 + if h == nil || h.RequestReceived == nil { 1033 + return ctx, nil 1034 + } 1035 + return h.RequestReceived(ctx) 1036 + } 1037 + 1038 + // Call twirp.ServerHooks.RequestRouted if the hook is available 1039 + func callRequestRouted(ctx context.Context, h *twirp.ServerHooks) (context.Context, error) { 1040 + if h == nil || h.RequestRouted == nil { 1041 + return ctx, nil 1042 + } 1043 + return h.RequestRouted(ctx) 1044 + } 1045 + 1046 + // Call twirp.ServerHooks.ResponsePrepared if the hook is available 1047 + func callResponsePrepared(ctx context.Context, h *twirp.ServerHooks) context.Context { 1048 + if h == nil || h.ResponsePrepared == nil { 1049 + return ctx 1050 + } 1051 + return h.ResponsePrepared(ctx) 1052 + } 1053 + 1054 + // Call twirp.ServerHooks.ResponseSent if the hook is available 1055 + func callResponseSent(ctx context.Context, h *twirp.ServerHooks) { 1056 + if h == nil || h.ResponseSent == nil { 1057 + return 1058 + } 1059 + h.ResponseSent(ctx) 1060 + } 1061 + 1062 + // Call twirp.ServerHooks.Error if the hook is available 1063 + func callError(ctx context.Context, h *twirp.ServerHooks, err twirp.Error) context.Context { 1064 + if h == nil || h.Error == nil { 1065 + return ctx 1066 + } 1067 + return h.Error(ctx, err) 1068 + } 1069 + 1070 + func callClientResponseReceived(ctx context.Context, h *twirp.ClientHooks) { 1071 + if h == nil || h.ResponseReceived == nil { 1072 + return 1073 + } 1074 + h.ResponseReceived(ctx) 1075 + } 1076 + 1077 + func callClientRequestPrepared(ctx context.Context, h *twirp.ClientHooks, req *http.Request) (context.Context, error) { 1078 + if h == nil || h.RequestPrepared == nil { 1079 + return ctx, nil 1080 + } 1081 + return h.RequestPrepared(ctx, req) 1082 + } 1083 + 1084 + func callClientError(ctx context.Context, h *twirp.ClientHooks, err twirp.Error) { 1085 + if h == nil || h.Error == nil { 1086 + return 1087 + } 1088 + h.Error(ctx, err) 1089 + } 1090 + 1091 + var twirpFileDescriptor0 = []byte{ 1092 + // 246 bytes of a gzipped FileDescriptorProto 1093 + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x64, 0x90, 0xc1, 0x4a, 0xc3, 0x40, 1094 + 0x10, 0x86, 0x89, 0x96, 0x62, 0xa6, 0x3d, 0xc8, 0x80, 0x25, 0x44, 0xc4, 0xea, 0xa9, 0xa7, 0x0d, 1095 + 0x54, 0x2f, 0x3d, 0x5a, 0xf0, 0x20, 0xe8, 0xa5, 0x88, 0x07, 0x2f, 0x65, 0x63, 0xa6, 0x61, 0xa1, 1096 + 0x9b, 0x09, 0xcd, 0xa4, 0xd4, 0xb7, 0xf1, 0x51, 0x65, 0x77, 0x1b, 0x03, 0xf6, 0x38, 0xff, 0x7c, 1097 + 0xf3, 0x2d, 0xff, 0xc2, 0xf8, 0x40, 0x8d, 0x11, 0x52, 0xf5, 0x8e, 0x85, 0x11, 0x0e, 0x64, 0x74, 1098 + 0xc3, 0xaa, 0x22, 0x49, 0xaf, 0x4b, 0xe6, 0x72, 0x4b, 0x99, 0xdf, 0xe4, 0xed, 0x26, 0x23, 0x5b, 1099 + 0xcb, 0x77, 0x00, 0xd3, 0xdb, 0xff, 0x4b, 0x31, 0x96, 0x1a, 0xd1, 0xb6, 0x0e, 0xc0, 0xfd, 0x4f, 1100 + 0x04, 0xf1, 0xb2, 0x35, 0xdb, 0xe2, 0xa5, 0xda, 0x30, 0x4e, 0x60, 0xf8, 0xc5, 0xd6, 0x1a, 0x49, 1101 + 0xa2, 0x69, 0x34, 0x8b, 0x57, 0xc7, 0x09, 0x17, 0x00, 0xb9, 0x83, 0xd6, 0xee, 0x3c, 0x39, 0x9b, 1102 + 0x46, 0xb3, 0xd1, 0x3c, 0x55, 0xc1, 0xad, 0x3a, 0xb7, 0x7a, 0xef, 0xdc, 0xab, 0xd8, 0xd3, 0x6e, 1103 + 0xc6, 0x1b, 0x80, 0x92, 0xd7, 0x7b, 0xda, 0x35, 0x86, 0xab, 0xe4, 0xdc, 0x6b, 0xe3, 0x92, 0x3f, 1104 + 0x42, 0x80, 0x77, 0x30, 0x2e, 0xa8, 0xea, 0x81, 0x81, 0x07, 0x46, 0x2e, 0x3b, 0x22, 0xf3, 0x27, 1105 + 0x18, 0xbc, 0xb6, 0x96, 0x70, 0x01, 0x17, 0x6f, 0x24, 0xba, 0xd0, 0xa2, 0x71, 0x72, 0xf2, 0xf8, 1106 + 0xb3, 0x6b, 0x9d, 0x5e, 0xa9, 0xfe, 0x67, 0xd4, 0x5f, 0xaf, 0x25, 0x7e, 0x5e, 0xf6, 0x79, 0xb6, 1107 + 0x7f, 0xcc, 0xea, 0x3c, 0x1f, 0xfa, 0xd3, 0x87, 0xdf, 0x00, 0x00, 0x00, 0xff, 0xff, 0x93, 0x4b, 1108 + 0xaa, 0x5a, 0x5a, 0x01, 0x00, 0x00, 1109 + }