├── LICENSE ├── README.md ├── chapter_2_io_in_go ├── alternative_text_encodings │ └── main.go ├── backoffexample │ └── main.go ├── benchmarking │ ├── encoding_test.go │ └── messages │ │ ├── messages.pb.go │ │ └── messages.proto ├── concurrent │ └── main.go ├── copy │ └── main.go ├── csvexample │ └── main.go ├── delimited │ └── main.go ├── dns │ └── main.go ├── files │ └── main.go ├── fixedsize │ ├── client │ │ └── main.go │ └── server │ │ └── main.go ├── gobexample │ └── main.go ├── jsondecode │ └── main.go ├── jsonencode │ └── main.go ├── jsonerror │ └── error.go ├── jsonexample │ └── main.go ├── maxconnections │ ├── client │ │ └── main.go │ └── server │ │ └── main.go ├── networkerrors │ ├── main.go │ └── server │ │ └── main.go ├── numberencoding │ └── main.go ├── outboundtcp │ └── main.go ├── pprofexample │ ├── client │ │ ├── client │ │ └── main.go │ └── server │ │ ├── main.go │ │ └── server ├── protocolbuffers │ ├── main.go │ └── messages │ │ ├── messages.pb.go │ │ └── messages.proto ├── scanexample │ └── main.go ├── semaphoreexample │ ├── client │ │ └── main.go │ └── server │ │ └── main.go ├── udpexample │ ├── receiver │ │ └── main.go │ └── sender │ │ └── main.go ├── validation │ └── main.go └── variable │ └── main.go ├── chapter_3_applications ├── graphdata │ ├── client │ │ └── main.go │ ├── generated.go │ ├── gqlgen.yml │ ├── graphstore │ │ ├── node.go │ │ └── store.go │ ├── models_gen.go │ ├── resolver.go │ ├── schema.graphql │ └── server │ │ └── server.go ├── graphql │ ├── docker-compose.yml │ ├── gqlid │ │ ├── id.go │ │ └── id_test.go │ └── schema │ │ ├── companyloader_gen.go │ │ ├── dataloader.go │ │ ├── generated.go │ │ ├── gqlgen.yml │ │ ├── gqlgen.yml.old │ │ ├── models_gen.go │ │ ├── order.go │ │ ├── resolver.go │ │ ├── schema.graphql │ │ └── server │ │ └── main.go ├── grpc │ ├── client │ │ └── main.go │ ├── interface │ │ ├── order.proto │ │ └── order │ │ │ └── order.pb.go │ └── server │ │ └── main.go ├── restexample │ ├── client │ │ ├── client.go │ │ └── cmd │ │ │ └── main.go │ ├── resources │ │ └── company │ │ │ └── structure.go │ └── server │ │ ├── cmd │ │ └── main.go │ │ ├── handler.go │ │ ├── handler_test.go │ │ └── inmemorystore.go ├── router │ ├── router.go │ ├── router_test.go │ └── server │ │ └── main.go ├── tcpcopy │ ├── encryption │ │ └── encrypt.go │ ├── main.go │ ├── receive │ │ └── main.go │ ├── send │ │ └── main.go │ └── wordlist │ │ ├── pgp.go │ │ └── pgp_test.go ├── tls │ ├── cert.crt │ ├── cert.key │ ├── client │ │ └── main.go │ └── main.go └── udpclock │ ├── client │ └── main.go │ ├── docker-compose.yml │ └── server │ └── main.go ├── chapter_4_distributed_applications ├── flowlog │ ├── compare │ │ └── main.go │ ├── parse.go │ └── parse_test.go ├── jwtexample │ ├── client │ │ └── main.go │ ├── issuer │ │ ├── example_private.pem │ │ └── main.go │ └── server │ │ ├── example_public.pem │ │ └── main.go ├── portscandetector │ └── main.go ├── portscanner │ └── main.go ├── proxy │ └── main.go ├── queue │ └── main.go ├── queue_full │ ├── cmd │ │ └── main.go │ └── queue.go ├── rproxy │ ├── balancer.go │ ├── cache.go │ ├── main.go │ └── testserver │ │ └── main.go ├── sharding │ ├── main.go │ └── main_test.go ├── spotifydl │ └── main.go ├── stream │ ├── cmd │ │ └── main.go │ └── window.go └── surveillance │ ├── download.txt │ └── main.go └── chapter_5_monitoring_and_failures ├── dropout └── main.go └── timeout └── main.go /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Packt 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Hands-On-Networking-with-Go-Programming 2 | Hands-On Networking with Go Programming by Packt Publishing 3 | -------------------------------------------------------------------------------- /chapter_2_io_in_go/alternative_text_encodings/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "io" 7 | "os" 8 | 9 | "golang.org/x/text/encoding/simplifiedchinese" 10 | 11 | "golang.org/x/text/encoding/japanese" 12 | ) 13 | 14 | func main() { 15 | convertToShiftJIS() 16 | convertFromGB2312UTF8() 17 | } 18 | 19 | func convertToShiftJIS() { 20 | // Convert from UTF-8 to Shift JIS 21 | src := bytes.NewBuffer([]byte{0xe3, 0x82, 0xa2, 0xe3, 0x82, 0xbf, 0xe3, 0x83, 0xaa}) // アタリ in UTF-8 encoding 22 | // Create an encoder. 23 | e := japanese.ShiftJIS.NewEncoder() 24 | // Wrap the output with the encoder. 25 | dst := new(bytes.Buffer) 26 | // Copy from the source to the destination, via the encoder. 27 | _, err := io.Copy(e.Writer(dst), src) 28 | if err != nil { 29 | fmt.Println("encoding error:", err) 30 | os.Exit(1) 31 | } 32 | // Print out the hex. 33 | for _, b := range dst.Bytes() { 34 | fmt.Printf("0x%2x ", b) 35 | } 36 | fmt.Println() 37 | // Outputs: 0x83 0x41 0x83 0x5e 0x83 0x8a 38 | } 39 | 40 | func convertFromGB2312UTF8() { 41 | src := bytes.NewBuffer([]byte{0xd6, 0xd0, 0xb9, 0xfa}) // 中国 in GB2312 encoding 42 | // Create a decoder. 43 | e := simplifiedchinese.GBK.NewDecoder() 44 | // Copy from the source to Stdout, via the decoder. 45 | _, err := io.Copy(os.Stdout, e.Reader(src)) 46 | if err != nil { 47 | fmt.Println("decoding error:", err) 48 | os.Exit(1) 49 | } 50 | // Outputs: 中国 51 | } 52 | -------------------------------------------------------------------------------- /chapter_2_io_in_go/backoffexample/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | 7 | "github.com/cenkalti/backoff" 8 | ) 9 | 10 | func main() { 11 | var attempt int 12 | lastAttempt := time.Now() 13 | operation := func() error { 14 | attempt++ 15 | fmt.Println("Attempt", attempt, time.Now().Sub(lastAttempt)) 16 | lastAttempt = time.Now() 17 | return fmt.Errorf("failed again") 18 | } 19 | bo := backoff.WithMaxTries(backoff.NewExponentialBackOff(), 10) 20 | err := backoff.Retry(operation, bo) 21 | if err != nil { 22 | fmt.Println(err) 23 | return 24 | } 25 | fmt.Println("OK") 26 | } 27 | -------------------------------------------------------------------------------- /chapter_2_io_in_go/benchmarking/encoding_test.go: -------------------------------------------------------------------------------- 1 | package benchmarking 2 | 3 | import ( 4 | "bytes" 5 | "encoding/gob" 6 | "encoding/json" 7 | "testing" 8 | "time" 9 | 10 | "github.com/PacktPublishing/Hands-On-Networking-with-Go-Programming/chapter_2_io_in_go/benchmarking/messages" 11 | "github.com/golang/protobuf/proto" 12 | "github.com/golang/protobuf/ptypes" 13 | ) 14 | 15 | type order struct { 16 | ID int64 `json:"id"` 17 | Customer customer `json:"customer"` 18 | Items []item `json:"items"` 19 | OrderDate time.Time `json:"date"` 20 | } 21 | 22 | type customer struct { 23 | ID int64 `json:"id"` 24 | FirstName string `json:"firstName"` 25 | LastName string `json:"lastName"` 26 | Email string `json:"email"` 27 | } 28 | 29 | type item struct { 30 | ID int64 `json:"id"` 31 | URL string `json:"url"` 32 | Desc string `json:"desc"` 33 | Cost int64 `json:"cost"` 34 | } 35 | 36 | var testOrder = order{ 37 | ID: 1, 38 | Customer: customer{ 39 | ID: 2, 40 | Email: "customer@example.com", 41 | FirstName: "Joe", 42 | LastName: "Tegmark", 43 | }, 44 | Items: []item{ 45 | item{ 46 | ID: 3, 47 | URL: "https://example.com/products/3", 48 | Desc: "item 3", 49 | Cost: 1553, 50 | }, 51 | item{ 52 | ID: 4, 53 | URL: "https://example.com/products/4", 54 | Desc: "item 4", 55 | Cost: 12378, 56 | }, 57 | item{ 58 | ID: 5, 59 | URL: "https://example.com/products/5", 60 | Desc: "item 5", 61 | Cost: 112, 62 | }, 63 | }, 64 | OrderDate: time.Now(), 65 | } 66 | 67 | func BenchmarkJSONEncoding(b *testing.B) { 68 | b.ReportAllocs() 69 | for n := 0; n < b.N; n++ { 70 | var buf bytes.Buffer 71 | enc := json.NewEncoder(&buf) 72 | if err := enc.Encode(testOrder); err != nil { 73 | b.Error(err) 74 | } 75 | } 76 | } 77 | 78 | func BenchmarkGobEncoding(b *testing.B) { 79 | b.ReportAllocs() 80 | for n := 0; n < b.N; n++ { 81 | var buf bytes.Buffer 82 | enc := gob.NewEncoder(&buf) 83 | if err := enc.Encode(testOrder); err != nil { 84 | b.Error(err) 85 | } 86 | } 87 | } 88 | 89 | func BenchmarkProtobufEncoding(b *testing.B) { 90 | getItems := func(items []item) (op []*messages.Item) { 91 | for _, itm := range items { 92 | op = append(op, &messages.Item{ 93 | ItemId: itm.ID, 94 | Url: itm.URL, 95 | Description: itm.Desc, 96 | Cost: itm.Cost, 97 | }) 98 | } 99 | return 100 | } 101 | ts, err := ptypes.TimestampProto(testOrder.OrderDate) 102 | if err != nil { 103 | b.Fatal(err) 104 | } 105 | m := messages.Order{ 106 | OrderId: testOrder.ID, 107 | Customer: &messages.Customer{ 108 | CustomerId: testOrder.Customer.ID, 109 | FirstName: testOrder.Customer.FirstName, 110 | LastName: testOrder.Customer.LastName, 111 | Email: testOrder.Customer.Email, 112 | }, 113 | Items: getItems(testOrder.Items), 114 | OrderDate: ts, 115 | } 116 | b.ReportAllocs() 117 | for n := 0; n < b.N; n++ { 118 | data, err := proto.Marshal(&m) 119 | if err != nil { 120 | b.Error(err) 121 | } 122 | if len(data) == 0 { 123 | b.Error("no bytes written") 124 | } 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /chapter_2_io_in_go/benchmarking/messages/messages.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go. DO NOT EDIT. 2 | // source: messages.proto 3 | 4 | package messages 5 | 6 | import ( 7 | fmt "fmt" 8 | proto "github.com/golang/protobuf/proto" 9 | timestamp "github.com/golang/protobuf/ptypes/timestamp" 10 | math "math" 11 | ) 12 | 13 | // Reference imports to suppress errors if they are not otherwise used. 14 | var _ = proto.Marshal 15 | var _ = fmt.Errorf 16 | var _ = math.Inf 17 | 18 | // This is a compile-time assertion to ensure that this generated file 19 | // is compatible with the proto package it is being compiled against. 20 | // A compilation error at this line likely means your copy of the 21 | // proto package needs to be updated. 22 | const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package 23 | 24 | type Order struct { 25 | OrderId int64 `protobuf:"varint,1,opt,name=order_id,json=orderId,proto3" json:"order_id,omitempty"` 26 | Customer *Customer `protobuf:"bytes,2,opt,name=customer,proto3" json:"customer,omitempty"` 27 | Items []*Item `protobuf:"bytes,3,rep,name=items,proto3" json:"items,omitempty"` 28 | OrderDate *timestamp.Timestamp `protobuf:"bytes,4,opt,name=order_date,json=orderDate,proto3" json:"order_date,omitempty"` 29 | XXX_NoUnkeyedLiteral struct{} `json:"-"` 30 | XXX_unrecognized []byte `json:"-"` 31 | XXX_sizecache int32 `json:"-"` 32 | } 33 | 34 | func (m *Order) Reset() { *m = Order{} } 35 | func (m *Order) String() string { return proto.CompactTextString(m) } 36 | func (*Order) ProtoMessage() {} 37 | func (*Order) Descriptor() ([]byte, []int) { 38 | return fileDescriptor_4dc296cbfe5ffcd5, []int{0} 39 | } 40 | 41 | func (m *Order) XXX_Unmarshal(b []byte) error { 42 | return xxx_messageInfo_Order.Unmarshal(m, b) 43 | } 44 | func (m *Order) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { 45 | return xxx_messageInfo_Order.Marshal(b, m, deterministic) 46 | } 47 | func (m *Order) XXX_Merge(src proto.Message) { 48 | xxx_messageInfo_Order.Merge(m, src) 49 | } 50 | func (m *Order) XXX_Size() int { 51 | return xxx_messageInfo_Order.Size(m) 52 | } 53 | func (m *Order) XXX_DiscardUnknown() { 54 | xxx_messageInfo_Order.DiscardUnknown(m) 55 | } 56 | 57 | var xxx_messageInfo_Order proto.InternalMessageInfo 58 | 59 | func (m *Order) GetOrderId() int64 { 60 | if m != nil { 61 | return m.OrderId 62 | } 63 | return 0 64 | } 65 | 66 | func (m *Order) GetCustomer() *Customer { 67 | if m != nil { 68 | return m.Customer 69 | } 70 | return nil 71 | } 72 | 73 | func (m *Order) GetItems() []*Item { 74 | if m != nil { 75 | return m.Items 76 | } 77 | return nil 78 | } 79 | 80 | func (m *Order) GetOrderDate() *timestamp.Timestamp { 81 | if m != nil { 82 | return m.OrderDate 83 | } 84 | return nil 85 | } 86 | 87 | type Customer struct { 88 | CustomerId int64 `protobuf:"varint,1,opt,name=customer_id,json=customerId,proto3" json:"customer_id,omitempty"` 89 | FirstName string `protobuf:"bytes,2,opt,name=first_name,json=firstName,proto3" json:"first_name,omitempty"` 90 | LastName string `protobuf:"bytes,3,opt,name=last_name,json=lastName,proto3" json:"last_name,omitempty"` 91 | Email string `protobuf:"bytes,4,opt,name=email,proto3" json:"email,omitempty"` 92 | XXX_NoUnkeyedLiteral struct{} `json:"-"` 93 | XXX_unrecognized []byte `json:"-"` 94 | XXX_sizecache int32 `json:"-"` 95 | } 96 | 97 | func (m *Customer) Reset() { *m = Customer{} } 98 | func (m *Customer) String() string { return proto.CompactTextString(m) } 99 | func (*Customer) ProtoMessage() {} 100 | func (*Customer) Descriptor() ([]byte, []int) { 101 | return fileDescriptor_4dc296cbfe5ffcd5, []int{1} 102 | } 103 | 104 | func (m *Customer) XXX_Unmarshal(b []byte) error { 105 | return xxx_messageInfo_Customer.Unmarshal(m, b) 106 | } 107 | func (m *Customer) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { 108 | return xxx_messageInfo_Customer.Marshal(b, m, deterministic) 109 | } 110 | func (m *Customer) XXX_Merge(src proto.Message) { 111 | xxx_messageInfo_Customer.Merge(m, src) 112 | } 113 | func (m *Customer) XXX_Size() int { 114 | return xxx_messageInfo_Customer.Size(m) 115 | } 116 | func (m *Customer) XXX_DiscardUnknown() { 117 | xxx_messageInfo_Customer.DiscardUnknown(m) 118 | } 119 | 120 | var xxx_messageInfo_Customer proto.InternalMessageInfo 121 | 122 | func (m *Customer) GetCustomerId() int64 { 123 | if m != nil { 124 | return m.CustomerId 125 | } 126 | return 0 127 | } 128 | 129 | func (m *Customer) GetFirstName() string { 130 | if m != nil { 131 | return m.FirstName 132 | } 133 | return "" 134 | } 135 | 136 | func (m *Customer) GetLastName() string { 137 | if m != nil { 138 | return m.LastName 139 | } 140 | return "" 141 | } 142 | 143 | func (m *Customer) GetEmail() string { 144 | if m != nil { 145 | return m.Email 146 | } 147 | return "" 148 | } 149 | 150 | type Item struct { 151 | ItemId int64 `protobuf:"varint,1,opt,name=item_id,json=itemId,proto3" json:"item_id,omitempty"` 152 | Url string `protobuf:"bytes,2,opt,name=url,proto3" json:"url,omitempty"` 153 | Description string `protobuf:"bytes,3,opt,name=description,proto3" json:"description,omitempty"` 154 | Cost int64 `protobuf:"varint,4,opt,name=cost,proto3" json:"cost,omitempty"` 155 | XXX_NoUnkeyedLiteral struct{} `json:"-"` 156 | XXX_unrecognized []byte `json:"-"` 157 | XXX_sizecache int32 `json:"-"` 158 | } 159 | 160 | func (m *Item) Reset() { *m = Item{} } 161 | func (m *Item) String() string { return proto.CompactTextString(m) } 162 | func (*Item) ProtoMessage() {} 163 | func (*Item) Descriptor() ([]byte, []int) { 164 | return fileDescriptor_4dc296cbfe5ffcd5, []int{2} 165 | } 166 | 167 | func (m *Item) XXX_Unmarshal(b []byte) error { 168 | return xxx_messageInfo_Item.Unmarshal(m, b) 169 | } 170 | func (m *Item) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { 171 | return xxx_messageInfo_Item.Marshal(b, m, deterministic) 172 | } 173 | func (m *Item) XXX_Merge(src proto.Message) { 174 | xxx_messageInfo_Item.Merge(m, src) 175 | } 176 | func (m *Item) XXX_Size() int { 177 | return xxx_messageInfo_Item.Size(m) 178 | } 179 | func (m *Item) XXX_DiscardUnknown() { 180 | xxx_messageInfo_Item.DiscardUnknown(m) 181 | } 182 | 183 | var xxx_messageInfo_Item proto.InternalMessageInfo 184 | 185 | func (m *Item) GetItemId() int64 { 186 | if m != nil { 187 | return m.ItemId 188 | } 189 | return 0 190 | } 191 | 192 | func (m *Item) GetUrl() string { 193 | if m != nil { 194 | return m.Url 195 | } 196 | return "" 197 | } 198 | 199 | func (m *Item) GetDescription() string { 200 | if m != nil { 201 | return m.Description 202 | } 203 | return "" 204 | } 205 | 206 | func (m *Item) GetCost() int64 { 207 | if m != nil { 208 | return m.Cost 209 | } 210 | return 0 211 | } 212 | 213 | func init() { 214 | proto.RegisterType((*Order)(nil), "Order") 215 | proto.RegisterType((*Customer)(nil), "Customer") 216 | proto.RegisterType((*Item)(nil), "Item") 217 | } 218 | 219 | func init() { proto.RegisterFile("messages.proto", fileDescriptor_4dc296cbfe5ffcd5) } 220 | 221 | var fileDescriptor_4dc296cbfe5ffcd5 = []byte{ 222 | // 297 bytes of a gzipped FileDescriptorProto 223 | 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x4c, 0x90, 0x41, 0x4b, 0xc3, 0x40, 224 | 0x10, 0x85, 0x89, 0x69, 0xda, 0x64, 0x02, 0x22, 0x83, 0x60, 0x6c, 0x91, 0x86, 0x82, 0xd0, 0x53, 225 | 0x0a, 0xf5, 0xe4, 0x59, 0x2f, 0xb9, 0x28, 0x2c, 0xde, 0xcb, 0xb6, 0x3b, 0x0d, 0x0b, 0xd9, 0x6e, 226 | 0xd9, 0xdd, 0x1e, 0xfd, 0x31, 0xfe, 0x53, 0xc9, 0x24, 0xb1, 0xde, 0xe6, 0xbd, 0xb7, 0xcc, 0xfb, 227 | 0x66, 0xe1, 0xd6, 0x90, 0xf7, 0xb2, 0x21, 0x5f, 0x9d, 0x9d, 0x0d, 0x76, 0xbe, 0x6c, 0xac, 0x6d, 228 | 0x5a, 0xda, 0xb0, 0xda, 0x5f, 0x8e, 0x9b, 0xa0, 0x0d, 0xf9, 0x20, 0xcd, 0xb9, 0x7f, 0xb0, 0xfa, 229 | 0x89, 0x20, 0xf9, 0x74, 0x8a, 0x1c, 0x3e, 0x42, 0x6a, 0xbb, 0x61, 0xa7, 0x55, 0x11, 0x95, 0xd1, 230 | 0x3a, 0x16, 0x33, 0xd6, 0xb5, 0xc2, 0x67, 0x48, 0x0f, 0x17, 0x1f, 0xac, 0x21, 0x57, 0xdc, 0x94, 231 | 0xd1, 0x3a, 0xdf, 0x66, 0xd5, 0xdb, 0x60, 0x88, 0xbf, 0x08, 0x17, 0x90, 0xe8, 0x40, 0xc6, 0x17, 232 | 0x71, 0x19, 0xaf, 0xf3, 0x6d, 0x52, 0xd5, 0x81, 0x8c, 0xe8, 0x3d, 0x7c, 0x05, 0xe8, 0xd7, 0x2b, 233 | 0x19, 0xa8, 0x98, 0xf0, 0x96, 0x79, 0xd5, 0xe3, 0x55, 0x23, 0x5e, 0xf5, 0x35, 0xe2, 0x89, 0x8c, 234 | 0x5f, 0xbf, 0xcb, 0x40, 0xab, 0x6f, 0x48, 0xc7, 0x36, 0x5c, 0x42, 0x3e, 0xf6, 0x5d, 0x41, 0x61, 235 | 0xb4, 0x6a, 0x85, 0x4f, 0x00, 0x47, 0xed, 0x7c, 0xd8, 0x9d, 0xa4, 0x21, 0xa6, 0xcd, 0x44, 0xc6, 236 | 0xce, 0x87, 0x34, 0x84, 0x0b, 0xc8, 0x5a, 0x39, 0xa6, 0x31, 0xa7, 0x69, 0x67, 0x70, 0x78, 0x0f, 237 | 0x09, 0x19, 0xa9, 0x5b, 0xc6, 0xcb, 0x44, 0x2f, 0x56, 0x0d, 0x4c, 0xba, 0x43, 0xf0, 0x01, 0x66, 238 | 0xdd, 0x29, 0xd7, 0xda, 0x69, 0x27, 0x6b, 0x85, 0x77, 0x10, 0x5f, 0x5c, 0x3b, 0x74, 0x75, 0x23, 239 | 0x96, 0x90, 0x2b, 0xf2, 0x07, 0xa7, 0xcf, 0x41, 0xdb, 0xd3, 0xd0, 0xf3, 0xdf, 0x42, 0x84, 0xc9, 240 | 0xc1, 0xfa, 0xc0, 0x4d, 0xb1, 0xe0, 0x79, 0x3f, 0xe5, 0x6f, 0x78, 0xf9, 0x0d, 0x00, 0x00, 0xff, 241 | 0xff, 0xf0, 0x8e, 0xc7, 0xdc, 0xc5, 0x01, 0x00, 0x00, 242 | } 243 | -------------------------------------------------------------------------------- /chapter_2_io_in_go/benchmarking/messages/messages.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | import "google/protobuf/timestamp.proto"; 4 | 5 | message Order { 6 | int64 order_id = 1; 7 | Customer customer = 2; 8 | repeated Item items = 3; 9 | google.protobuf.Timestamp order_date = 4; 10 | } 11 | 12 | message Customer { 13 | int64 customer_id = 1; 14 | string first_name = 2; 15 | string last_name = 3; 16 | string email = 4; 17 | } 18 | 19 | message Item { 20 | int64 item_id = 1; 21 | string url = 2; 22 | string description = 3; 23 | int64 cost = 4; 24 | } -------------------------------------------------------------------------------- /chapter_2_io_in_go/concurrent/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "time" 7 | ) 8 | 9 | type result struct { 10 | URL string 11 | Status int 12 | TimeTaken time.Duration 13 | Err error 14 | } 15 | 16 | func main() { 17 | urls := []string{"http://google.com", "http://wikipedia.com"} 18 | results := make(chan result) 19 | for _, u := range urls { 20 | u := u 21 | go func() { 22 | start := time.Now() 23 | resp, err := http.Get(u) 24 | r := result{ 25 | URL: u, 26 | Err: err, 27 | TimeTaken: time.Now().Sub(start), 28 | } 29 | if err == nil { 30 | r.Status = resp.StatusCode 31 | } 32 | results <- r 33 | }() 34 | } 35 | for i := 0; i < len(urls); i++ { 36 | r := <-results 37 | fmt.Println(r) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /chapter_2_io_in_go/copy/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "io" 7 | "log" 8 | "os" 9 | ) 10 | 11 | var sourceFlag = flag.String("source", "", "The source path") 12 | var targetFlag = flag.String("target", "", "The target path") 13 | 14 | func main() { 15 | flag.Parse() 16 | if *sourceFlag == "" { 17 | fmt.Println("Missing source flag") 18 | os.Exit(1) 19 | } 20 | if *targetFlag == "" { 21 | fmt.Println("Missing target flag") 22 | os.Exit(1) 23 | } 24 | 25 | // Check that the source file exists. 26 | if _, err := os.Stat(*sourceFlag); err == os.ErrNotExist { 27 | fmt.Println("Source file not found") 28 | os.Exit(1) 29 | } 30 | 31 | copy(*sourceFlag, *targetFlag) 32 | } 33 | 34 | func copy(from, to string) { 35 | src, err := os.Open(from) 36 | if err != nil { 37 | fmt.Println("couldn't open source", err) 38 | os.Exit(1) 39 | } 40 | defer src.Close() 41 | dst, err := os.Create(to) 42 | if err != nil { 43 | log.Println("couldn't create target", err) 44 | os.Exit(1) 45 | } 46 | defer dst.Close() 47 | _, err = io.Copy(dst, src) 48 | if err != nil { 49 | log.Println("couldn't copy data", err) 50 | os.Exit(1) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /chapter_2_io_in_go/csvexample/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "encoding/csv" 6 | "fmt" 7 | "io" 8 | "os" 9 | "strconv" 10 | "sync" 11 | ) 12 | 13 | const data = `Name,Age 14 | A1,10 15 | A2,20 16 | A3,30 17 | A4,40 18 | A5,50` 19 | 20 | func main() { 21 | // You can get an io.Reader from many sources in Go. For example, you'll get a reader back 22 | // from a HTTP request or from S3. 23 | br := bytes.NewReader([]byte(data)) 24 | 25 | // The io.Reader can be passed straight to the csv.NewReader function. This means that you 26 | // don't need to read all of the data into RAM in one go, and can start processing records 27 | // before you've finished transferring all of the data. 28 | r := csv.NewReader(br) 29 | var record []string 30 | var err error 31 | // You might want to skip the first record if it contains headings. 32 | // Simplest way is to just do `r.Read` once. 33 | _, err = r.Read() 34 | if err != nil { 35 | fmt.Println("failed to read CSV headings", err) 36 | // It's not usually a good idea to do this, since it stops your entire process server, but for an 37 | // example program, it's OK. 38 | os.Exit(1) 39 | } 40 | 41 | // A typical pattern here is to start one or more worker routines to process the records, using 42 | // a channel to marshal the communication. A channel is basically queue. This one will block on input 43 | // i.e. code will wait for the queue to empty before pushing another item into the queue. 44 | records := make(chan Record) 45 | 46 | // The WaitGroup can be used to wait for processes to complete. It's basically a thread-safe counter. 47 | workerCount := 2 48 | var wg sync.WaitGroup 49 | wg.Add(workerCount) 50 | for i := 0; i < workerCount; i++ { 51 | // The go statement starts this function as a new goroutine which will run asynchronously. 52 | // The range over the channel means that it will run until the channel is closed. 53 | // Go can run hundreds of thousands of goroutines concurrently, but for making calls out to 54 | // databases etc., we'll probably want to limit concurrency. One simple way is to only start 55 | // a few workers, but there's also a semaphore package. 56 | 57 | // Note that the anonymous function that we're starting as a goroutine can have parameters passed 58 | // to it, but can also take closures (access variables in the parent scope). It's important to 59 | // use variables in the parent scope in a thread-safe way, so typically Go's concurrency features 60 | // are used to do this. From highest level to lowest level there are channels, mutexes and atomic 61 | // operations. 62 | go func(workerIndex int) { 63 | // The defer statement means that whatever path your code takes, wg.Done 64 | // will always be called when this function ends. It's going to end when the 65 | // channel is closed by the CSV reading code coming up next. 66 | defer wg.Done() 67 | for r := range records { 68 | //TODO: Process the record, I'll just print it. 69 | fmt.Printf("Index: %d: Name: %s, Age: %d\n", workerIndex, r.Name, r.Age) 70 | } 71 | }(i) 72 | } 73 | 74 | // Start processing the CSV data. 75 | for record, err = r.Read(); err != io.EOF; record, err = r.Read() { 76 | // Push the record to the channel and keep reading. One of the workers will collect it. 77 | age, err := strconv.ParseInt(record[1], 10, 64) 78 | if err != nil { 79 | // TODO: Decide what to do with the fact that we can't parse this record, e.g. skip it. 80 | } 81 | records <- NewRecord(record[0], int(age)) 82 | } 83 | // Close the channel to let the workers know to exit their work. 84 | close(records) 85 | // At the end, you always get an io.EOF "error" to tell you you've reached the end of the 86 | // file. 87 | if err != io.EOF { 88 | // Do something if it's not EOF. 89 | fmt.Println("failed to read CSV data", err) 90 | } 91 | // Wait for the routines to finish. 92 | wg.Wait() 93 | fmt.Println("Done") 94 | } 95 | 96 | // NewRecord creates a new Record with required fields populated. 97 | func NewRecord(name string, age int) Record { 98 | return Record{ 99 | Name: name, 100 | Age: age, 101 | } 102 | } 103 | 104 | // Record within the CSV. 105 | type Record struct { 106 | Name string 107 | Age int 108 | } 109 | -------------------------------------------------------------------------------- /chapter_2_io_in_go/delimited/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "fmt" 7 | ) 8 | 9 | func main() { 10 | var buf bytes.Buffer 11 | err := binary.Write(&buf, binary.LittleEndian, VariableWidth{ 12 | Fixed: 1, 13 | // Variable: "this is a string", 14 | Variable: 3, 15 | }) 16 | if err != nil { 17 | fmt.Println(err) 18 | return 19 | } 20 | } 21 | 22 | type VariableWidth struct { 23 | Fixed int64 24 | Variable int64 25 | } 26 | -------------------------------------------------------------------------------- /chapter_2_io_in_go/dns/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "flag" 6 | "fmt" 7 | "net" 8 | ) 9 | 10 | var hostFlag = flag.String("host", "google.com", "The DNS name of the host to lookup IP addresses for") 11 | 12 | func main() { 13 | flag.Parse() 14 | host := *hostFlag 15 | 16 | var r net.Resolver 17 | r.Dial = func(ctx context.Context, network, address string) (net.Conn, error) { 18 | fmt.Println("was going to use DNS server at", address) 19 | // Google's is 8.8.8.8 20 | // OpenDNS at 208.67.222.123 21 | return net.Dial(network, "208.67.222.123:53") 22 | } 23 | r.PreferGo = true 24 | res, err := r.LookupHost(context.Background(), host) 25 | fmt.Println("IP Addresses", res, err) 26 | } 27 | -------------------------------------------------------------------------------- /chapter_2_io_in_go/files/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "os" 7 | "path" 8 | ) 9 | 10 | func main() { 11 | createFile() 12 | readFile() 13 | editFile() 14 | deleteFile() 15 | } 16 | 17 | func createFile() { 18 | f, err := os.Create("example.txt") 19 | if err != nil { 20 | log.Println("failed to create file", err) 21 | return 22 | } 23 | defer f.Close() 24 | _, err = f.WriteString("File contents") 25 | if err != nil { 26 | log.Println("failed to write to file", err) 27 | } 28 | } 29 | 30 | func readFile() { 31 | f, err := os.Open("example.txt") 32 | if err != nil { 33 | log.Println("failed to open file", err) 34 | return 35 | } 36 | defer f.Close() 37 | data := make([]byte, 1024) 38 | n, err := f.Read(data) 39 | if err != nil { 40 | log.Println("failed to read data", err) 41 | return 42 | } 43 | fmt.Printf("Read %d bytes: %s\n", n, string(data[:n])) 44 | } 45 | 46 | func editFile() { 47 | f, err := os.OpenFile("example.txt", os.O_RDWR, 0) 48 | if err != nil { 49 | log.Println("failed to open file", err) 50 | return 51 | } 52 | defer f.Close() 53 | data := make([]byte, 1024) 54 | n, err := f.Read(data) 55 | if err != nil { 56 | log.Println("failed to read data", err) 57 | return 58 | } 59 | fmt.Printf("Read %d bytes: %s\n", n, string(data[:n])) 60 | _, err = f.WriteString("\nRead the data, but also added some") 61 | if err != nil { 62 | log.Println("failed to write data", err) 63 | return 64 | } 65 | } 66 | 67 | func deleteFile() { 68 | if err := os.Remove("example.txt"); err != nil { 69 | log.Println("failed to delete file", err) 70 | } 71 | } 72 | 73 | func pathJoinSplit() { 74 | joined := path.Join("/users/me", "more/paths", "filename.txt") 75 | dir, filename := path.Split(joined) 76 | fmt.Println(dir) 77 | fmt.Println(filename) 78 | } 79 | -------------------------------------------------------------------------------- /chapter_2_io_in_go/fixedsize/client/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/binary" 5 | "fmt" 6 | "math" 7 | "net" 8 | "os" 9 | "time" 10 | ) 11 | 12 | func main() { 13 | conn, err := net.Dial("tcp", "127.0.0.1:9092") 14 | if err != nil { 15 | fmt.Println("error connecting", err) 16 | os.Exit(1) 17 | } 18 | defer conn.Close() 19 | _, err = conn.Write([]byte("github.com/a-h/timeseries/v1")) 20 | if err != nil { 21 | fmt.Println("error sending preamble", err) 22 | os.Exit(1) 23 | } 24 | ok := make([]byte, 2) 25 | read, err := conn.Read(ok) 26 | if err != nil { 27 | fmt.Println("error reading preamble", err) 28 | os.Exit(1) 29 | } 30 | if read != len(ok) || !isOK(ok) { 31 | fmt.Println("invalid preamble returned") 32 | os.Exit(1) 33 | } 34 | // Send some samples. 35 | for x := 0.0; x < 2.0; x += 0.1 { 36 | d := Data{ 37 | SeriesID: 1, 38 | Timestamp: time.Now().UnixNano(), 39 | Value: math.Sin(10.0 * x), 40 | } 41 | err = binary.Write(conn, binary.LittleEndian, d) 42 | if err != nil { 43 | fmt.Println("failed to write to server", err) 44 | os.Exit(1) 45 | } 46 | } 47 | } 48 | 49 | type Data struct { 50 | SeriesID byte 51 | Timestamp int64 52 | Value float64 53 | } 54 | 55 | func isOK(ok []byte) bool { 56 | return ok[0] == 'O' && ok[1] == 'K' 57 | } 58 | -------------------------------------------------------------------------------- /chapter_2_io_in_go/fixedsize/server/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "encoding/binary" 7 | "fmt" 8 | "net" 9 | "os" 10 | "os/signal" 11 | "sync" 12 | "syscall" 13 | "time" 14 | ) 15 | 16 | func main() { 17 | sigs := make(chan os.Signal) 18 | signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) 19 | 20 | list, err := net.Listen("tcp", "127.0.0.1:9092") 21 | if err != nil { 22 | fmt.Println("error creating listener", err) 23 | os.Exit(1) 24 | } 25 | 26 | ctx, cancel := context.WithCancel(context.Background()) 27 | var wg sync.WaitGroup 28 | 29 | go func() { 30 | for { 31 | conn, err := list.Accept() 32 | if err != nil { 33 | fmt.Println("error accepting connection", err) 34 | continue 35 | } 36 | go handleConnection(ctx, &wg, conn) 37 | } 38 | }() 39 | 40 | <-sigs 41 | cancel() 42 | wg.Wait() 43 | } 44 | 45 | var expectedPreamble = "github.com/a-h/timeseries/v1" 46 | 47 | func handleConnection(ctx context.Context, wg *sync.WaitGroup, conn net.Conn) { 48 | wg.Add(1) 49 | defer wg.Done() 50 | defer conn.Close() 51 | 52 | // Handshake with the client. 53 | preamble := make([]byte, len(expectedPreamble)) 54 | read, err := conn.Read(preamble) 55 | if err != nil { 56 | fmt.Println("error reading from connection") 57 | return 58 | } 59 | if read != len(expectedPreamble) { 60 | fmt.Println("client sent unexpected message") 61 | return 62 | } 63 | _, err = conn.Write([]byte("OK")) 64 | if err != nil { 65 | fmt.Println("error during handshake") 66 | return 67 | } 68 | 69 | // Start reading normally. 70 | dataSize := 1 + 8 + 8 // one byte for the series, one for the time (int64) and one for the data (float64) 71 | data := make([]byte, dataSize) 72 | 73 | for { 74 | select { 75 | case <-ctx.Done(): 76 | return 77 | default: 78 | if read, err = conn.Read(data); err != nil || read != dataSize { 79 | fmt.Println("communication stopped") 80 | return 81 | } 82 | // Read the data 83 | rdr := bytes.NewReader(data) 84 | var d Data 85 | err = binary.Read(rdr, binary.LittleEndian, &d) 86 | if err != nil { 87 | fmt.Println("failed to read into Data", err) 88 | return 89 | } 90 | fmt.Printf("%s: %d %v %v\n", conn.RemoteAddr().String(), 91 | int(d.SeriesID), 92 | time.Unix(0, d.Timestamp), 93 | d.Value) 94 | } 95 | } 96 | } 97 | 98 | type Data struct { 99 | SeriesID byte 100 | Timestamp int64 101 | Value float64 102 | } 103 | -------------------------------------------------------------------------------- /chapter_2_io_in_go/gobexample/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "encoding/gob" 6 | "fmt" 7 | "strings" 8 | ) 9 | 10 | type book struct { 11 | Name string `gob:"n"` 12 | Stars int `gob:"s"` 13 | } 14 | 15 | func main() { 16 | // The io.Writer to write to. 17 | var w bytes.Buffer 18 | b := book{ 19 | Name: "Go Network Programming", 20 | Stars: 5, 21 | } 22 | e := gob.NewEncoder(&w) 23 | err := e.Encode(b) 24 | if err != nil { 25 | fmt.Println("error encoding Gob:", err) 26 | return 27 | } 28 | fmt.Println("Bytes written:", w.Len()) 29 | fmt.Println(string(w.Bytes())) 30 | } 31 | 32 | func hex(v []byte) string { 33 | s := make([]string, len(v)) 34 | for i, v := range v { 35 | s[i] = fmt.Sprintf("0x%02x", v) 36 | } 37 | return strings.Join(s, " ") 38 | } 39 | -------------------------------------------------------------------------------- /chapter_2_io_in_go/jsondecode/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | ) 8 | 9 | type book struct { 10 | Name string `json:"name"` 11 | Stars int `json:"stars"` 12 | } 13 | 14 | func main() { 15 | j := `{ 16 | "name": "Go Network Programming", 17 | "stars": 5 18 | }` 19 | buf := bytes.NewBufferString(j) 20 | 21 | var b book 22 | d := json.NewDecoder(buf) 23 | err := d.Decode(&b) 24 | if err != nil { 25 | fmt.Println("error decoding JSON:", err) 26 | return 27 | } 28 | fmt.Println("Book Name:", b.Name) 29 | fmt.Println("Book Stars:", b.Stars) 30 | } 31 | -------------------------------------------------------------------------------- /chapter_2_io_in_go/jsonencode/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | ) 8 | 9 | type book struct { 10 | Name string `json:"name"` 11 | Stars int `json:"stars"` 12 | } 13 | 14 | func main() { 15 | // The io.Writer to write to. 16 | var w bytes.Buffer 17 | b := book{ 18 | Name: "Go Network Programming", 19 | Stars: 5, 20 | } 21 | e := json.NewEncoder(&w) 22 | err := e.Encode(b) 23 | if err != nil { 24 | fmt.Println("error encoding JSON:", err) 25 | return 26 | } 27 | fmt.Println("Bytes written:", w.Len()) 28 | } 29 | -------------------------------------------------------------------------------- /chapter_2_io_in_go/jsonerror/error.go: -------------------------------------------------------------------------------- 1 | package jsonerror 2 | 3 | import "time" 4 | 5 | type HTTPError struct { 6 | RequestID string `json:"rid"` 7 | Time time.Time `json:"t"` 8 | Issuer string `json:"iss"` 9 | ClientID string `json:"clientId"` 10 | UserID string `json:"userId"` 11 | Status int `json:"status"` 12 | Code string `json:"code"` 13 | Message string `json:"msg"` 14 | } 15 | -------------------------------------------------------------------------------- /chapter_2_io_in_go/jsonexample/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "os" 7 | ) 8 | 9 | type Book struct { 10 | Name string `json:"name"` 11 | Stars int `json:"stars"` 12 | } 13 | 14 | func main() { 15 | b := Book{ 16 | Name: "Book", 17 | Stars: 2, 18 | } 19 | j, err := json.Marshal(b) 20 | if err != nil { 21 | fmt.Println("Error marshalling Book data into JSON", err) 22 | os.Exit(1) 23 | } 24 | fmt.Println(string(j)) 25 | 26 | var bk Book 27 | bookJSON := `{"name":"other book","stars":3}` 28 | err = json.Unmarshal([]byte(bookJSON), &bk) 29 | if err != nil { 30 | fmt.Println("Error unmarshalling Book data", err) 31 | os.Exit(1) 32 | } 33 | fmt.Printf("%s", bk.Name) 34 | } 35 | -------------------------------------------------------------------------------- /chapter_2_io_in_go/maxconnections/client/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | "time" 7 | ) 8 | 9 | func main() { 10 | var id int 11 | for i := 0; i < 100000; i++ { 12 | go connect(&id) 13 | time.Sleep(time.Millisecond * 1) 14 | } 15 | } 16 | 17 | func connect(id *int) { 18 | conn, err := net.Dial("tcp", "localhost:8181") 19 | if err != nil { 20 | fmt.Println("Error connecting:", err) 21 | return 22 | } 23 | defer conn.Close() 24 | *id++ 25 | fmt.Println("Connected:", *id) 26 | for { 27 | conn.Write([]byte("a")) 28 | time.Sleep(time.Minute) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /chapter_2_io_in_go/maxconnections/server/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | "os" 7 | "sync/atomic" 8 | "time" 9 | ) 10 | 11 | func main() { 12 | l, err := net.ListenTCP("tcp", &net.TCPAddr{Port: 8181}) 13 | if err != nil { 14 | fmt.Println(err) 15 | os.Exit(1) 16 | } 17 | var connections uint64 18 | for { 19 | conn, err := l.AcceptTCP() 20 | if err != nil && err.Error() != "use of closed network connection" { 21 | fmt.Println("Error:", err) 22 | continue 23 | } 24 | count := atomic.AddUint64(&connections, 1) 25 | data := make([]byte, 1) 26 | go func(c uint64) { 27 | defer conn.Close() 28 | fmt.Println("Accepted connection", c) 29 | conn.Read(data) 30 | time.Sleep(time.Hour) 31 | }(count) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /chapter_2_io_in_go/networkerrors/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | "time" 7 | ) 8 | 9 | func main() { 10 | conn, err := net.DialTimeout("tcp", "127.0.0.1:9999", time.Second*5) 11 | if err != nil { 12 | fmt.Println("failed to connect") 13 | return 14 | } 15 | err = conn.SetDeadline(time.Now().Add(time.Second * 5)) 16 | if err != nil { 17 | fmt.Println("error setting deadline", err) 18 | return 19 | } 20 | start := time.Now() 21 | fmt.Println("Connected") 22 | defer func() { 23 | fmt.Println("Completed in", time.Now().Sub(start)) 24 | }() 25 | expectedToRead := 2 26 | data := make([]byte, 2) 27 | read, err := conn.Read(data) 28 | if err != nil { 29 | fmt.Println(err) 30 | return 31 | } 32 | if read != expectedToRead { 33 | fmt.Println("expected to read 2 bytes, read", read) 34 | return 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /chapter_2_io_in_go/networkerrors/server/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | "os" 7 | "os/signal" 8 | "syscall" 9 | "time" 10 | ) 11 | 12 | func main() { 13 | l, err := net.Listen("tcp", "127.0.0.1:9999") 14 | if err != nil { 15 | fmt.Println("error listening", err) 16 | os.Exit(1) 17 | } 18 | 19 | go func() { 20 | for { 21 | conn, err := l.Accept() 22 | if err != nil { 23 | fmt.Println("error accepting", err) 24 | continue 25 | } 26 | go func(c net.Conn) { 27 | fmt.Println("Connection received, waiting 10 seconds") 28 | time.Sleep(time.Second * 10) 29 | c.Write([]byte("OK")) 30 | fmt.Println("Closing connection") 31 | c.Close() 32 | }(conn) 33 | } 34 | }() 35 | 36 | sigs := make(chan os.Signal) 37 | signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) 38 | <-sigs 39 | // conn.SetKeepAlive() 40 | // conn.SetKeepAlivePeriod() 41 | // conn.SetLinger() 42 | // conn.SetNoDelay() 43 | // conn.SetReadBuffer() 44 | // conn.SetReadDeadline() 45 | } 46 | -------------------------------------------------------------------------------- /chapter_2_io_in_go/numberencoding/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "fmt" 7 | "math" 8 | "strings" 9 | ) 10 | 11 | func main() { 12 | fmt.Println("int64") 13 | ints := []int64{-2048, -1048, -512, -256, -128, -1, 0, 1, 128, 256, 512, 1024, 2048} 14 | for _, i := range ints { 15 | var b bytes.Buffer 16 | binary.Write(&b, binary.LittleEndian, i) 17 | bs := b.Bytes() 18 | fmt.Printf("%6d | %s | %s\n", i, hex(bs), bits(bs)) 19 | } 20 | fmt.Println() 21 | 22 | fmt.Println("float64") 23 | floats := []float64{math.NaN(), math.Inf(-1), -128, -2.5, 2, -1, -0.5, 0, 0.5, 1, 2, 2.5, 128, math.Inf(1)} 24 | for _, f := range floats { 25 | var b bytes.Buffer 26 | binary.Write(&b, binary.LittleEndian, f) 27 | bs := b.Bytes() 28 | fmt.Printf("%7.2f | %s | %s\n", f, hex(bs), bits(bs)) 29 | } 30 | fmt.Println() 31 | 32 | fmt.Println("bool") 33 | bools := []bool{false, true} 34 | for _, bl := range bools { 35 | var b bytes.Buffer 36 | binary.Write(&b, binary.LittleEndian, bl) 37 | bs := b.Bytes() 38 | fmt.Printf("%7v | %s | %s\n", bl, hex(bs), bits(bs)) 39 | } 40 | fmt.Println() 41 | } 42 | 43 | func hex(v []byte) string { 44 | s := make([]string, len(v)) 45 | for i, v := range v { 46 | s[i] = fmt.Sprintf("0x%02x", v) 47 | } 48 | return strings.Join(s, " ") 49 | } 50 | 51 | func bits(v []byte) string { 52 | s := make([]string, len(v)) 53 | for i, v := range v { 54 | s[i] = fmt.Sprintf("%08b", v) 55 | } 56 | return strings.Join(s, " ") 57 | } 58 | -------------------------------------------------------------------------------- /chapter_2_io_in_go/outboundtcp/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "io" 7 | "net" 8 | "os" 9 | ) 10 | 11 | func main() { 12 | conn, err := net.Dial("tcp", "127.0.0.1:8001") 13 | if err != nil { 14 | fmt.Println("error connecting", err) 15 | os.Exit(1) 16 | } 17 | conn.Write([]byte("GET / HTTP/1.0\n\n")) 18 | data, err := readAll(conn) 19 | fmt.Println(string(data), err) 20 | } 21 | 22 | func readAll(r io.Reader) (data []byte, err error) { 23 | var b bytes.Buffer 24 | buf := make([]byte, 32*1024) 25 | var read int 26 | for { 27 | read, err = r.Read(buf) 28 | if err != nil && err != io.EOF { 29 | return 30 | } 31 | if read == 0 { 32 | break 33 | } 34 | b.Write(buf[:read]) 35 | } 36 | err = nil 37 | data = b.Bytes() 38 | return 39 | } 40 | -------------------------------------------------------------------------------- /chapter_2_io_in_go/pprofexample/client/client: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Hands-On-Networking-with-Go-Programming/d724b4bfee04c86000e263bd04b5f86c480f1d79/chapter_2_io_in_go/pprofexample/client/client -------------------------------------------------------------------------------- /chapter_2_io_in_go/pprofexample/client/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | "time" 7 | ) 8 | 9 | func main() { 10 | for { 11 | sayHello() 12 | time.Sleep(time.Second) 13 | } 14 | } 15 | 16 | func sayHello() { 17 | conn, err := net.Dial("tcp", "localhost:8181") 18 | if err != nil { 19 | fmt.Println("Error connecting:", err) 20 | return 21 | } 22 | defer conn.Close() 23 | conn.Write([]byte("Hello")) 24 | } 25 | -------------------------------------------------------------------------------- /chapter_2_io_in_go/pprofexample/server/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "net" 7 | "net/http" 8 | _ "net/http/pprof" // Required for profiling. 9 | "os" 10 | "sync/atomic" 11 | ) 12 | 13 | func main() { 14 | // Start the profiler. 15 | go func() { 16 | log.Println(http.ListenAndServe("localhost:6060", nil)) 17 | }() 18 | 19 | l, err := net.ListenTCP("tcp", &net.TCPAddr{Port: 8181}) 20 | if err != nil { 21 | fmt.Println(err) 22 | os.Exit(1) 23 | } 24 | var connections uint64 25 | for { 26 | conn, err := l.AcceptTCP() 27 | if err != nil && err.Error() != "use of closed network connection" { 28 | fmt.Println("Error:", err) 29 | continue 30 | } 31 | count := atomic.AddUint64(&connections, 1) 32 | data := make([]byte, 1) 33 | go func(c uint64) { 34 | defer conn.Close() 35 | fmt.Println("Accepted connection", c) 36 | conn.Read(data) 37 | }(count) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /chapter_2_io_in_go/pprofexample/server/server: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Hands-On-Networking-with-Go-Programming/d724b4bfee04c86000e263bd04b5f86c480f1d79/chapter_2_io_in_go/pprofexample/server/server -------------------------------------------------------------------------------- /chapter_2_io_in_go/protocolbuffers/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "strings" 7 | "time" 8 | 9 | "github.com/PacktPublishing/Hands-On-Networking-with-Go-Programming/chapter_2_io_in_go/protocolbuffers/messages" 10 | "github.com/golang/protobuf/proto" 11 | "github.com/golang/protobuf/ptypes" 12 | ) 13 | 14 | func main() { 15 | var r messages.SearchResponse 16 | ts, err := ptypes.TimestampProto(time.Now()) 17 | if err != nil { 18 | fmt.Println("unable to create timestamp", err) 19 | } 20 | r.Modified = ts 21 | r.Results = []*messages.SearchResult{ 22 | &messages.SearchResult{ 23 | Id: 1, 24 | Description: "description", 25 | Url: "https://example.com/description/1", 26 | }, 27 | } 28 | bytes, err := proto.Marshal(&r) 29 | if err != nil { 30 | fmt.Println(err) 31 | os.Exit(1) 32 | } 33 | // Binary format. 34 | // 0x0a 0x32 0x08 0x01 0x12 0x21 0x68 0x74 0x74 0x70 0x73 0x3a 0x2f 0x2f 0x65 0x78 0x61 0x6d 0x70 0x6c 35 | // 0x65 0x2e 0x63 0x6f 0x6d 0x2f 0x64 0x65 0x73 0x63 0x72 0x69 0x70 0x74 0x69 0x6f 0x6e 0x2f 0x31 0x1a 36 | // 0x0b 0x64 0x65 0x73 0x63 0x72 0x69 0x70 0x74 0x69 0x6f 0x6e 0x12 0x0b 0x08 0xbe 0xe6 0x8a 0xe4 0x05 37 | // 0x10 0xb8 0x97 0xc6 0x3c 38 | fmt.Println(bytes) 39 | // Readable version 40 | // results: < 41 | // id: 1 42 | // url: "https://example.com/description/1" 43 | // description: "description" 44 | // > 45 | // modified: < 46 | // seconds: 1552069438 47 | // nanos: 126979000 48 | // > 49 | proto.MarshalText(os.Stdout, &r) 50 | 51 | // Unmarshal back. 52 | var unmarshalled messages.SearchResponse 53 | err = proto.Unmarshal(bytes, &unmarshalled) 54 | if err != nil { 55 | fmt.Println(err) 56 | os.Exit(1) 57 | } 58 | // {[id:1 url:"https://example.com/description/1" description:"description" ] seconds:1552069617 nanos:315187000 {} [] 0} 59 | fmt.Println(unmarshalled) 60 | } 61 | 62 | func hex(v []byte) string { 63 | s := make([]string, len(v)) 64 | for i, v := range v { 65 | s[i] = fmt.Sprintf("0x%02x", v) 66 | } 67 | return strings.Join(s, " ") 68 | } 69 | -------------------------------------------------------------------------------- /chapter_2_io_in_go/protocolbuffers/messages/messages.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go. DO NOT EDIT. 2 | // source: messages.proto 3 | 4 | package messages 5 | 6 | import ( 7 | fmt "fmt" 8 | proto "github.com/golang/protobuf/proto" 9 | timestamp "github.com/golang/protobuf/ptypes/timestamp" 10 | math "math" 11 | ) 12 | 13 | // Reference imports to suppress errors if they are not otherwise used. 14 | var _ = proto.Marshal 15 | var _ = fmt.Errorf 16 | var _ = math.Inf 17 | 18 | // This is a compile-time assertion to ensure that this generated file 19 | // is compatible with the proto package it is being compiled against. 20 | // A compilation error at this line likely means your copy of the 21 | // proto package needs to be updated. 22 | const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package 23 | 24 | type SearchRequest struct { 25 | Query string `protobuf:"bytes,1,opt,name=query,proto3" json:"query,omitempty"` 26 | PageNumber int32 `protobuf:"varint,2,opt,name=page_number,json=pageNumber,proto3" json:"page_number,omitempty"` 27 | ResultPerPage int32 `protobuf:"varint,3,opt,name=result_per_page,json=resultPerPage,proto3" json:"result_per_page,omitempty"` 28 | XXX_NoUnkeyedLiteral struct{} `json:"-"` 29 | XXX_unrecognized []byte `json:"-"` 30 | XXX_sizecache int32 `json:"-"` 31 | } 32 | 33 | func (m *SearchRequest) Reset() { *m = SearchRequest{} } 34 | func (m *SearchRequest) String() string { return proto.CompactTextString(m) } 35 | func (*SearchRequest) ProtoMessage() {} 36 | func (*SearchRequest) Descriptor() ([]byte, []int) { 37 | return fileDescriptor_4dc296cbfe5ffcd5, []int{0} 38 | } 39 | 40 | func (m *SearchRequest) XXX_Unmarshal(b []byte) error { 41 | return xxx_messageInfo_SearchRequest.Unmarshal(m, b) 42 | } 43 | func (m *SearchRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { 44 | return xxx_messageInfo_SearchRequest.Marshal(b, m, deterministic) 45 | } 46 | func (m *SearchRequest) XXX_Merge(src proto.Message) { 47 | xxx_messageInfo_SearchRequest.Merge(m, src) 48 | } 49 | func (m *SearchRequest) XXX_Size() int { 50 | return xxx_messageInfo_SearchRequest.Size(m) 51 | } 52 | func (m *SearchRequest) XXX_DiscardUnknown() { 53 | xxx_messageInfo_SearchRequest.DiscardUnknown(m) 54 | } 55 | 56 | var xxx_messageInfo_SearchRequest proto.InternalMessageInfo 57 | 58 | func (m *SearchRequest) GetQuery() string { 59 | if m != nil { 60 | return m.Query 61 | } 62 | return "" 63 | } 64 | 65 | func (m *SearchRequest) GetPageNumber() int32 { 66 | if m != nil { 67 | return m.PageNumber 68 | } 69 | return 0 70 | } 71 | 72 | func (m *SearchRequest) GetResultPerPage() int32 { 73 | if m != nil { 74 | return m.ResultPerPage 75 | } 76 | return 0 77 | } 78 | 79 | type SearchResponse struct { 80 | Results []*SearchResult `protobuf:"bytes,1,rep,name=results,proto3" json:"results,omitempty"` 81 | Modified *timestamp.Timestamp `protobuf:"bytes,2,opt,name=modified,proto3" json:"modified,omitempty"` 82 | XXX_NoUnkeyedLiteral struct{} `json:"-"` 83 | XXX_unrecognized []byte `json:"-"` 84 | XXX_sizecache int32 `json:"-"` 85 | } 86 | 87 | func (m *SearchResponse) Reset() { *m = SearchResponse{} } 88 | func (m *SearchResponse) String() string { return proto.CompactTextString(m) } 89 | func (*SearchResponse) ProtoMessage() {} 90 | func (*SearchResponse) Descriptor() ([]byte, []int) { 91 | return fileDescriptor_4dc296cbfe5ffcd5, []int{1} 92 | } 93 | 94 | func (m *SearchResponse) XXX_Unmarshal(b []byte) error { 95 | return xxx_messageInfo_SearchResponse.Unmarshal(m, b) 96 | } 97 | func (m *SearchResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { 98 | return xxx_messageInfo_SearchResponse.Marshal(b, m, deterministic) 99 | } 100 | func (m *SearchResponse) XXX_Merge(src proto.Message) { 101 | xxx_messageInfo_SearchResponse.Merge(m, src) 102 | } 103 | func (m *SearchResponse) XXX_Size() int { 104 | return xxx_messageInfo_SearchResponse.Size(m) 105 | } 106 | func (m *SearchResponse) XXX_DiscardUnknown() { 107 | xxx_messageInfo_SearchResponse.DiscardUnknown(m) 108 | } 109 | 110 | var xxx_messageInfo_SearchResponse proto.InternalMessageInfo 111 | 112 | func (m *SearchResponse) GetResults() []*SearchResult { 113 | if m != nil { 114 | return m.Results 115 | } 116 | return nil 117 | } 118 | 119 | func (m *SearchResponse) GetModified() *timestamp.Timestamp { 120 | if m != nil { 121 | return m.Modified 122 | } 123 | return nil 124 | } 125 | 126 | type SearchResult struct { 127 | Id int64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"` 128 | Url string `protobuf:"bytes,2,opt,name=url,proto3" json:"url,omitempty"` 129 | Description string `protobuf:"bytes,3,opt,name=description,proto3" json:"description,omitempty"` 130 | XXX_NoUnkeyedLiteral struct{} `json:"-"` 131 | XXX_unrecognized []byte `json:"-"` 132 | XXX_sizecache int32 `json:"-"` 133 | } 134 | 135 | func (m *SearchResult) Reset() { *m = SearchResult{} } 136 | func (m *SearchResult) String() string { return proto.CompactTextString(m) } 137 | func (*SearchResult) ProtoMessage() {} 138 | func (*SearchResult) Descriptor() ([]byte, []int) { 139 | return fileDescriptor_4dc296cbfe5ffcd5, []int{2} 140 | } 141 | 142 | func (m *SearchResult) XXX_Unmarshal(b []byte) error { 143 | return xxx_messageInfo_SearchResult.Unmarshal(m, b) 144 | } 145 | func (m *SearchResult) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { 146 | return xxx_messageInfo_SearchResult.Marshal(b, m, deterministic) 147 | } 148 | func (m *SearchResult) XXX_Merge(src proto.Message) { 149 | xxx_messageInfo_SearchResult.Merge(m, src) 150 | } 151 | func (m *SearchResult) XXX_Size() int { 152 | return xxx_messageInfo_SearchResult.Size(m) 153 | } 154 | func (m *SearchResult) XXX_DiscardUnknown() { 155 | xxx_messageInfo_SearchResult.DiscardUnknown(m) 156 | } 157 | 158 | var xxx_messageInfo_SearchResult proto.InternalMessageInfo 159 | 160 | func (m *SearchResult) GetId() int64 { 161 | if m != nil { 162 | return m.Id 163 | } 164 | return 0 165 | } 166 | 167 | func (m *SearchResult) GetUrl() string { 168 | if m != nil { 169 | return m.Url 170 | } 171 | return "" 172 | } 173 | 174 | func (m *SearchResult) GetDescription() string { 175 | if m != nil { 176 | return m.Description 177 | } 178 | return "" 179 | } 180 | 181 | func init() { 182 | proto.RegisterType((*SearchRequest)(nil), "SearchRequest") 183 | proto.RegisterType((*SearchResponse)(nil), "SearchResponse") 184 | proto.RegisterType((*SearchResult)(nil), "SearchResult") 185 | } 186 | 187 | func init() { proto.RegisterFile("messages.proto", fileDescriptor_4dc296cbfe5ffcd5) } 188 | 189 | var fileDescriptor_4dc296cbfe5ffcd5 = []byte{ 190 | // 261 bytes of a gzipped FileDescriptorProto 191 | 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x4c, 0x8f, 0x41, 0x6b, 0xb4, 0x30, 192 | 0x10, 0x86, 0x51, 0xd9, 0xef, 0xab, 0x63, 0xb5, 0x25, 0xf4, 0x20, 0x7b, 0x59, 0xf1, 0xd0, 0x7a, 193 | 0x72, 0x61, 0x0b, 0xfd, 0x19, 0x65, 0x49, 0x7b, 0x17, 0x5d, 0x67, 0x6d, 0x40, 0x4d, 0xcc, 0x24, 194 | 0x87, 0xfe, 0xfb, 0x62, 0x82, 0xcb, 0xde, 0x32, 0xef, 0x3c, 0xcc, 0x9b, 0x07, 0xb2, 0x09, 0x89, 195 | 0xda, 0x01, 0xa9, 0x56, 0x5a, 0x1a, 0xb9, 0x3f, 0x0c, 0x52, 0x0e, 0x23, 0x1e, 0xdd, 0xd4, 0xd9, 196 | 0xeb, 0xd1, 0x88, 0x09, 0xc9, 0xb4, 0x93, 0xf2, 0x40, 0x39, 0x43, 0xfa, 0x85, 0xad, 0xbe, 0xfc, 197 | 0x70, 0x5c, 0x2c, 0x92, 0x61, 0x2f, 0xb0, 0x5b, 0x2c, 0xea, 0xdf, 0x3c, 0x28, 0x82, 0x2a, 0xe6, 198 | 0x7e, 0x60, 0x07, 0x48, 0x54, 0x3b, 0x60, 0x33, 0xdb, 0xa9, 0x43, 0x9d, 0x87, 0x45, 0x50, 0xed, 199 | 0x38, 0xac, 0xd1, 0xa7, 0x4b, 0xd8, 0x2b, 0x3c, 0x69, 0x24, 0x3b, 0x9a, 0x46, 0xa1, 0x6e, 0xd6, 200 | 0x45, 0x1e, 0x39, 0x28, 0xf5, 0xf1, 0x19, 0xf5, 0xb9, 0x1d, 0xb0, 0x5c, 0x20, 0xdb, 0xfa, 0x48, 201 | 0xc9, 0x99, 0x90, 0xbd, 0xc1, 0x7f, 0x8f, 0x50, 0x1e, 0x14, 0x51, 0x95, 0x9c, 0xd2, 0xfa, 0x46, 202 | 0xd8, 0xd1, 0xf0, 0x6d, 0xcb, 0x3e, 0xe0, 0x61, 0x92, 0xbd, 0xb8, 0x0a, 0xec, 0xdd, 0x07, 0x92, 203 | 0xd3, 0xbe, 0xf6, 0x7a, 0xf5, 0xa6, 0x57, 0x7f, 0x6f, 0x7a, 0xfc, 0xc6, 0x96, 0x1c, 0x1e, 0xef, 204 | 0x0f, 0xb2, 0x0c, 0x42, 0xd1, 0x3b, 0xbd, 0x88, 0x87, 0xa2, 0x67, 0xcf, 0x10, 0x59, 0x3d, 0xba, 205 | 0x93, 0x31, 0x5f, 0x9f, 0xac, 0x80, 0xa4, 0x47, 0xba, 0x68, 0xa1, 0x8c, 0x90, 0xb3, 0x13, 0x89, 206 | 0xf9, 0x7d, 0xd4, 0xfd, 0x73, 0x8d, 0xef, 0x7f, 0x01, 0x00, 0x00, 0xff, 0xff, 0x6a, 0x94, 0xf0, 207 | 0x59, 0x70, 0x01, 0x00, 0x00, 208 | } 209 | -------------------------------------------------------------------------------- /chapter_2_io_in_go/protocolbuffers/messages/messages.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | import "google/protobuf/timestamp.proto"; 4 | 5 | message SearchRequest { 6 | string query = 1; 7 | int32 page_number = 2; 8 | int32 result_per_page = 3; 9 | } 10 | 11 | message SearchResponse { 12 | repeated SearchResult results = 1; 13 | google.protobuf.Timestamp modified = 2; 14 | } 15 | 16 | message SearchResult { 17 | int64 id = 1; 18 | string url = 2; 19 | string description = 3; 20 | } -------------------------------------------------------------------------------- /chapter_2_io_in_go/scanexample/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "fmt" 7 | ) 8 | 9 | const text = `A|B|C|D|E|F|G` 10 | 11 | func main() { 12 | pipeSplit := func(data []byte, atEOF bool) (advance int, token []byte, err error) { 13 | if atEOF && len(data) == 0 { 14 | return 0, nil, nil 15 | } 16 | // Split at the pipe. 17 | if i := bytes.IndexByte(data, '|'); i >= 0 { 18 | return i + 1, data[0:i], nil 19 | } 20 | // If we're at EOF, return everything up to that. 21 | if atEOF { 22 | return len(data), data, nil 23 | } 24 | // Request more data. 25 | return 0, nil, nil 26 | } 27 | s := bufio.NewScanner(bytes.NewBufferString(text)) 28 | s.Split(pipeSplit) 29 | for s.Scan() { 30 | fmt.Println(s.Text()) 31 | } 32 | if s.Err() != nil { 33 | fmt.Println("error scanning data", s.Err()) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /chapter_2_io_in_go/semaphoreexample/client/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "net" 7 | "time" 8 | ) 9 | 10 | func main() { 11 | id := 0 12 | for { 13 | go connect(id) 14 | time.Sleep(time.Second) 15 | id++ 16 | } 17 | } 18 | 19 | func connect(id int) { 20 | conn, err := net.Dial("tcp", "localhost:8050") 21 | if err != nil { 22 | return 23 | } 24 | defer conn.Close() 25 | data := make([]byte, 8) 26 | n, err := conn.Read(data) 27 | if err != nil { 28 | fmt.Println("Error reading:", err) 29 | return 30 | } 31 | if n != 8 { 32 | fmt.Println("Unexpected data received") 33 | return 34 | } 35 | if isConnectionRejected(data) { 36 | fmt.Printf("Connection %d was rejected by server...\n", id) 37 | return 38 | } 39 | for { 40 | n, err := conn.Read(data) 41 | if err != nil && err != io.EOF { 42 | fmt.Println("Error reading:", err) 43 | return 44 | } 45 | if n != 8 { 46 | fmt.Println("Unexpected data received") 47 | return 48 | } 49 | fmt.Printf("%d: Received data\n", id) 50 | } 51 | } 52 | 53 | func isConnectionRejected(data []byte) bool { 54 | for _, d := range data { 55 | if d != 1 { 56 | return true 57 | } 58 | } 59 | return false 60 | } 61 | -------------------------------------------------------------------------------- /chapter_2_io_in_go/semaphoreexample/server/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/binary" 5 | "fmt" 6 | "math/rand" 7 | "net" 8 | "os" 9 | "os/signal" 10 | "syscall" 11 | "time" 12 | 13 | "golang.org/x/sync/semaphore" 14 | ) 15 | 16 | const maxConcurrentRequests int64 = 4 17 | 18 | var accepted = []byte{1, 1, 1, 1, 1, 1, 1, 1} 19 | var rejected = []byte{0, 0, 0, 0, 0, 0, 0, 0} 20 | 21 | func main() { 22 | sem := semaphore.NewWeighted(maxConcurrentRequests) 23 | 24 | go func() { 25 | l, err := net.ListenTCP("tcp", &net.TCPAddr{Port: 8050}) 26 | if err != nil { 27 | fmt.Println(err) 28 | os.Exit(1) 29 | } 30 | 31 | for { 32 | conn, err := l.AcceptTCP() 33 | if err != nil && err.Error() != "use of closed network connection" { 34 | fmt.Println("Error:", err) 35 | continue 36 | } 37 | if canAcceptMore := sem.TryAcquire(1); canAcceptMore { 38 | go func() { 39 | defer conn.Close() 40 | defer sem.Release(1) 41 | fmt.Println("Accepted connection") 42 | if _, err := conn.Write(accepted); err != nil { 43 | fmt.Println("Error sending acceptance:", err) 44 | fmt.Println("Closing") 45 | return 46 | } 47 | data := make([]byte, 8) 48 | for { 49 | binary.LittleEndian.PutUint64(data, uint64(rand.Int63())) 50 | if _, err := conn.Write(data); err != nil { 51 | fmt.Println("Error sending data:", err) 52 | fmt.Println("Closing") 53 | return 54 | } 55 | time.Sleep(time.Second) 56 | } 57 | }() 58 | } else { 59 | fmt.Println("Rejected connection, max connections achieved") 60 | go func() { 61 | defer conn.Close() 62 | conn.Write(rejected) 63 | }() 64 | } 65 | } 66 | }() 67 | 68 | sigs := make(chan os.Signal) 69 | signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) 70 | <-sigs 71 | } 72 | -------------------------------------------------------------------------------- /chapter_2_io_in_go/udpexample/receiver/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | "os" 7 | "strings" 8 | ) 9 | 10 | func main() { 11 | laddr := &net.UDPAddr{ 12 | Port: 3002, 13 | } 14 | raddr := &net.UDPAddr{ 15 | Port: 3022, 16 | } 17 | l, err := net.DialUDP("udp", laddr, raddr) 18 | if err != nil { 19 | fmt.Printf("error starting to listen: %v\n", err) 20 | os.Exit(1) 21 | } 22 | data := make([]byte, 8) 23 | for { 24 | _, err := l.Read(data) 25 | if err != nil { 26 | fmt.Println("Error:", err) 27 | continue 28 | } 29 | fmt.Println(hex(data)) 30 | _, err = l.Write([]byte(" OK")) 31 | if err != nil { 32 | fmt.Println("Error writing:", err) 33 | } 34 | } 35 | } 36 | 37 | func hex(v []byte) string { 38 | s := make([]string, len(v)) 39 | for i, v := range v { 40 | s[i] = fmt.Sprintf("0x%02x", v) 41 | } 42 | return strings.Join(s, " ") 43 | } 44 | -------------------------------------------------------------------------------- /chapter_2_io_in_go/udpexample/sender/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/binary" 5 | "fmt" 6 | "net" 7 | "os" 8 | "time" 9 | ) 10 | 11 | func main() { 12 | laddr := &net.UDPAddr{ 13 | Port: 3022, 14 | } 15 | raddr := &net.UDPAddr{ 16 | Port: 3002, 17 | } 18 | conn, err := net.DialUDP("udp", laddr, raddr) 19 | if err != nil { 20 | fmt.Printf("error starting to dial: %v\n", err) 21 | os.Exit(1) 22 | } 23 | go func() { 24 | response := make([]byte, 8) 25 | for { 26 | _, err := conn.Read(response) 27 | if err != nil { 28 | fmt.Printf("error reading data: %v\n", err) 29 | continue 30 | } 31 | fmt.Println("Received", string(response)) 32 | } 33 | }() 34 | bytes := make([]byte, 8) 35 | for i := int64(0); i < 255; i++ { 36 | binary.LittleEndian.PutUint64(bytes, uint64(i)) 37 | _, err := conn.Write(bytes) 38 | if err != nil { 39 | fmt.Printf("error sending data: %v\n", err) 40 | os.Exit(1) 41 | } 42 | fmt.Println(i) 43 | } 44 | fmt.Println("Sent data.") 45 | time.Sleep(time.Second * 5) 46 | } 47 | -------------------------------------------------------------------------------- /chapter_2_io_in_go/validation/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | validator "gopkg.in/go-playground/validator.v9" 7 | ) 8 | 9 | type input struct { 10 | Email string `validate:"email"` 11 | ChildAge int `validate:"min=0,max=17"` 12 | } 13 | 14 | func main() { 15 | validate := validator.New() 16 | 17 | v := input{ 18 | Email: "test@example.com", 19 | ChildAge: 9, 20 | } 21 | errs := validate.Struct(v) 22 | if errs != nil { 23 | if ve, ok := errs.(validator.ValidationErrors); ok { 24 | for _, fe := range ve { 25 | fmt.Printf("error: %v\n", fe) 26 | fmt.Printf(" field: %v\n", fe.Field()) 27 | fmt.Printf(" tag: %v\n", fe.Tag()) 28 | } 29 | } 30 | } 31 | 32 | iv := input{ 33 | Email: "test", 34 | ChildAge: -1, 35 | } 36 | errs = validate.Struct(iv) 37 | if errs != nil { 38 | if ve, ok := errs.(validator.ValidationErrors); ok { 39 | for _, fe := range ve { 40 | fmt.Printf("error: %v\n", fe) 41 | fmt.Printf(" field: %v\n", fe.Field()) 42 | fmt.Printf(" tag: %v\n", fe.Tag()) 43 | } 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /chapter_2_io_in_go/variable/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | ) 7 | 8 | func main() { 9 | var payload bytes.Buffer 10 | data := []byte{1, 2, 3, 4, 5} 11 | err := payload.WriteByte(byte(len(data))) 12 | if err != nil { 13 | fmt.Println("failed to write length of data") 14 | return 15 | } 16 | _, err = payload.Write(data) 17 | if err != nil { 18 | fmt.Println("failed to write data to buffer") 19 | return 20 | } 21 | // Write the data to the connection 22 | // _, err = conn.Write(payload) 23 | } 24 | -------------------------------------------------------------------------------- /chapter_3_applications/graphdata/client/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "fmt" 7 | 8 | "github.com/machinebox/graphql" 9 | ) 10 | 11 | func main() { 12 | c := graphql.NewClient("http://localhost:11111/query") 13 | // c.Log = func(s string) { fmt.Println(s) } 14 | // Create parent node. 15 | id, err := createNode(context.Background(), c, newNode{ 16 | ID: "node1", 17 | Parent: nil, 18 | }) 19 | if err != nil { 20 | fmt.Printf("failed to create first node: %v\n", err) 21 | return 22 | } 23 | fmt.Printf("created node '%s'\n", id) 24 | // Create child node 1. 25 | node1Parent := "node1" 26 | id, err = createNode(context.Background(), c, newNode{ 27 | ID: "childNode1", 28 | Parent: &node1Parent, 29 | }) 30 | if err != nil { 31 | fmt.Printf("failed to create first child node: %v\n", err) 32 | return 33 | } 34 | fmt.Printf("created node '%s'\n", id) 35 | // Create child node 2. 36 | id, err = createNode(context.Background(), c, newNode{ 37 | ID: "childNode2", 38 | Parent: &node1Parent, 39 | }) 40 | if err != nil { 41 | fmt.Printf("failed to create second child node: %v\n", err) 42 | return 43 | } 44 | fmt.Printf("created node '%s'\n", id) 45 | // Create edges. 46 | id, err = createEdge(context.Background(), c, newEdge{ 47 | Parent: "node1", 48 | Child: "childNode1", 49 | }) 50 | if err != nil { 51 | fmt.Printf("failed to create edge between parent and child 1: %v\n", err) 52 | return 53 | } 54 | id, err = createEdge(context.Background(), c, newEdge{ 55 | Parent: "node1", 56 | Child: "childNode2", 57 | }) 58 | if err != nil { 59 | fmt.Printf("failed to create edge between parent and child 2: %v\n", err) 60 | return 61 | } 62 | fmt.Printf("created node '%s'\n", id) 63 | // List nodes. 64 | nr, err := listParents(context.Background(), c) 65 | if err != nil { 66 | fmt.Printf("failed to list nodes: %v\n", err) 67 | return 68 | } 69 | fmt.Println("retrieved nodes:") 70 | d, err := json.MarshalIndent(nr, "", " ") 71 | fmt.Println(string(d)) 72 | if err != nil { 73 | fmt.Printf("failed to marshal return value: %v\n", err) 74 | return 75 | } 76 | } 77 | 78 | const createNodeQuery = `mutation($node: NewNode!) { 79 | createNode(node: $node) 80 | }` 81 | 82 | type newNode struct { 83 | ID string `json:"id"` 84 | Parent *string `json:"parent"` 85 | } 86 | 87 | func createNode(ctx context.Context, client *graphql.Client, node newNode) (id string, err error) { 88 | req := graphql.NewRequest(createNodeQuery) 89 | req.Var("node", node) 90 | req.Header.Set("Cache-Control", "no-cache") 91 | var result struct { 92 | CreateNode string `json:"createNode"` 93 | } 94 | err = client.Run(ctx, req, &result) 95 | id = result.CreateNode 96 | return 97 | } 98 | 99 | const createEdgeQuery = `mutation($edge: NewEdge!) { 100 | createEdge(edge: $edge) 101 | }` 102 | 103 | type newEdge struct { 104 | Parent string `json:"parent"` 105 | Child string `json:"child"` 106 | } 107 | 108 | func createEdge(ctx context.Context, client *graphql.Client, edge newEdge) (id string, err error) { 109 | req := graphql.NewRequest(createEdgeQuery) 110 | req.Var("edge", edge) 111 | req.Header.Set("Cache-Control", "no-cache") 112 | var result struct { 113 | CreateEdge string `json:"createEdge"` 114 | } 115 | err = client.Run(ctx, req, &result) 116 | id = result.CreateEdge 117 | return 118 | } 119 | 120 | const listParentsQuery = `query { 121 | list(take:10, skip: 0) { 122 | id 123 | parent { 124 | id 125 | } 126 | children { 127 | id 128 | children { 129 | id 130 | } 131 | } 132 | } 133 | }` 134 | 135 | type nodeResult struct { 136 | ID string `json:"id"` 137 | Parent *nodeResult `json:"parent"` 138 | Children []*nodeResult `json:"children"` 139 | } 140 | 141 | func listParents(ctx context.Context, client *graphql.Client) (result []nodeResult, err error) { 142 | req := graphql.NewRequest(listParentsQuery) 143 | req.Header.Set("Cache-Control", "no-cache") 144 | var nr struct { 145 | List []nodeResult `json:"list"` 146 | } 147 | err = client.Run(ctx, req, &nr) 148 | result = nr.List 149 | return 150 | } 151 | -------------------------------------------------------------------------------- /chapter_3_applications/graphdata/gqlgen.yml: -------------------------------------------------------------------------------- 1 | # .gqlgen.yml example 2 | # 3 | # Refer to https://gqlgen.com/config/ 4 | # for detailed .gqlgen.yml documentation. 5 | 6 | schema: 7 | - schema.graphql 8 | exec: 9 | filename: generated.go 10 | model: 11 | filename: models_gen.go 12 | resolver: 13 | filename: resolver.go 14 | type: Resolver 15 | 16 | models: 17 | Node: 18 | model: github.com/PacktPublishing/Hands-On-Networking-with-Go-Programming/chapter_3_applications/graphdata/graphstore.Node -------------------------------------------------------------------------------- /chapter_3_applications/graphdata/graphstore/node.go: -------------------------------------------------------------------------------- 1 | package graphstore 2 | 3 | // Node within a graph. 4 | type Node struct { 5 | // ID of the node. 6 | ID string `json:"id"` 7 | // Parent node. 8 | Parent *string `json:"parent"` 9 | // Child nodes. 10 | Children []string `json:"children"` 11 | } 12 | 13 | // NewNode creates a new Node. 14 | func NewNode(id string, parent *string) Node { 15 | return Node{ 16 | ID: id, 17 | Parent: parent, 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /chapter_3_applications/graphdata/graphstore/store.go: -------------------------------------------------------------------------------- 1 | package graphstore 2 | 3 | import "errors" 4 | 5 | // NewStore creates a new store. 6 | func NewStore() *Store { 7 | return &Store{ 8 | Nodes: make(map[string]*Node), 9 | } 10 | } 11 | 12 | // Store of a graph. 13 | type Store struct { 14 | Nodes map[string]*Node 15 | } 16 | 17 | // Add a node. 18 | func (s *Store) Add(n Node) { 19 | s.Nodes[n.ID] = &n 20 | } 21 | 22 | var errChildNodeNotFound = errors.New("child node not found") 23 | var errParentNodeNotFound = errors.New("parent node not found") 24 | 25 | // AddEdge between parent and child. 26 | func (s *Store) AddEdge(parent, child string) (err error) { 27 | c, cok := s.Nodes[child] 28 | if !cok { 29 | err = errChildNodeNotFound 30 | return 31 | } 32 | p, pok := s.Nodes[parent] 33 | if !pok { 34 | err = errParentNodeNotFound 35 | return 36 | } 37 | p.Children = append(p.Children, child) 38 | c.Parent = &parent 39 | return 40 | } 41 | 42 | // Get a node. 43 | func (s *Store) Get(id string) (n *Node) { 44 | if nn, ok := s.Nodes[id]; ok { 45 | n = nn 46 | } 47 | return 48 | } 49 | 50 | // Roots of the graph. 51 | func (s *Store) Roots() []string { 52 | op := []string{} 53 | for k, n := range s.Nodes { 54 | if n.Parent == nil { 55 | op = append(op, k) 56 | } 57 | } 58 | return op 59 | } 60 | -------------------------------------------------------------------------------- /chapter_3_applications/graphdata/models_gen.go: -------------------------------------------------------------------------------- 1 | // Code generated by github.com/99designs/gqlgen, DO NOT EDIT. 2 | 3 | package graphdata 4 | 5 | type NewEdge struct { 6 | Parent string `json:"parent"` 7 | Child string `json:"child"` 8 | } 9 | 10 | type NewNode struct { 11 | ID string `json:"id"` 12 | Parent *string `json:"parent"` 13 | } 14 | -------------------------------------------------------------------------------- /chapter_3_applications/graphdata/resolver.go: -------------------------------------------------------------------------------- 1 | package graphdata 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/PacktPublishing/Hands-On-Networking-with-Go-Programming/chapter_3_applications/graphdata/graphstore" 7 | ) 8 | 9 | type Resolver struct { 10 | Store *graphstore.Store 11 | } 12 | 13 | func (r *Resolver) Mutation() MutationResolver { 14 | return &mutationResolver{r} 15 | } 16 | func (r *Resolver) Node() NodeResolver { 17 | return &nodeResolver{r} 18 | } 19 | func (r *Resolver) Query() QueryResolver { 20 | return &queryResolver{r} 21 | } 22 | 23 | type mutationResolver struct{ *Resolver } 24 | 25 | func (r *mutationResolver) CreateNode(ctx context.Context, node NewNode) (string, error) { 26 | r.Store.Add(graphstore.NewNode(node.ID, node.Parent)) 27 | return node.ID, nil 28 | } 29 | 30 | func (r *mutationResolver) CreateEdge(ctx context.Context, edge NewEdge) (string, error) { 31 | err := r.Store.AddEdge(edge.Parent, edge.Child) 32 | return edge.Parent, err 33 | } 34 | 35 | type nodeResolver struct{ *Resolver } 36 | 37 | func (r *nodeResolver) Parent(ctx context.Context, obj *graphstore.Node) (parent *graphstore.Node, err error) { 38 | if obj.Parent != nil { 39 | parent = r.Store.Get(*obj.Parent) 40 | } 41 | return 42 | } 43 | func (r *nodeResolver) Children(ctx context.Context, obj *graphstore.Node) (children []graphstore.Node, err error) { 44 | children = make([]graphstore.Node, len(obj.Children)) 45 | for i, c := range obj.Children { 46 | if n := r.Store.Get(c); n != nil { 47 | children[i] = *n 48 | } 49 | } 50 | return 51 | } 52 | 53 | type queryResolver struct{ *Resolver } 54 | 55 | func (r *queryResolver) List(ctx context.Context, skip *int, take *int) (op []graphstore.Node, err error) { 56 | for _, id := range r.Store.Roots() { 57 | if n := r.Store.Get(id); n != nil { 58 | op = append(op, *n) 59 | } 60 | } 61 | return 62 | } 63 | func (r *queryResolver) Get(ctx context.Context, id string) (n *graphstore.Node, err error) { 64 | n = r.Store.Get(id) 65 | return 66 | } 67 | -------------------------------------------------------------------------------- /chapter_3_applications/graphdata/schema.graphql: -------------------------------------------------------------------------------- 1 | type Node { 2 | id: ID! 3 | parent: Node 4 | children: [Node!] 5 | } 6 | 7 | type Query { 8 | list(skip: Int, take: Int): [Node!]! 9 | get(id: ID!): Node 10 | } 11 | 12 | input NewNode { 13 | id: ID! 14 | parent: ID 15 | } 16 | 17 | input NewEdge { 18 | parent: ID! 19 | child: ID! 20 | } 21 | 22 | type Mutation { 23 | createNode(node: NewNode!): ID! 24 | createEdge(edge: NewEdge!): ID! 25 | } -------------------------------------------------------------------------------- /chapter_3_applications/graphdata/server/server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "net/http" 6 | "os" 7 | 8 | "github.com/PacktPublishing/Hands-On-Networking-with-Go-Programming/chapter_3_applications/graphdata/graphstore" 9 | 10 | "github.com/99designs/gqlgen/handler" 11 | "github.com/PacktPublishing/Hands-On-Networking-with-Go-Programming/chapter_3_applications/graphdata" 12 | ) 13 | 14 | const defaultPort = "11111" 15 | 16 | func main() { 17 | port := os.Getenv("PORT") 18 | if port == "" { 19 | port = defaultPort 20 | } 21 | 22 | r := &graphdata.Resolver{ 23 | Store: graphstore.NewStore(), 24 | } 25 | 26 | http.Handle("/", handler.Playground("GraphQL playground", "/query")) 27 | http.Handle("/query", handler.GraphQL(graphdata.NewExecutableSchema(graphdata.Config{Resolvers: r}))) 28 | 29 | log.Printf("connect to http://localhost:%s/ for GraphQL playground", port) 30 | log.Fatal(http.ListenAndServe(":"+port, nil)) 31 | } 32 | -------------------------------------------------------------------------------- /chapter_3_applications/graphql/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '2' 2 | services: 3 | graphql: 4 | image: golang:latest 5 | volumes: 6 | - ../:/go/src/github.com/PacktPublishing/Hands-On-Networking-with-Go-Programming/chapter_3_applications 7 | command: 'bash -c "cd /go/src/github.com/PacktPublishing/Hands-On-Networking-with-Go-Programming/chapter_3_applications/graphql && go get ./... && go run schema/server/main.go"' 8 | ports: 9 | - 8080:8080 10 | links: 11 | - companyrestserver 12 | - orderservergrpc 13 | companyrestserver: 14 | image: golang:latest 15 | volumes: 16 | - ../:/go/src/github.com/PacktPublishing/Hands-On-Networking-with-Go-Programming/chapter_3_applications 17 | command: 'bash -c "cd /go/src/github.com/PacktPublishing/Hands-On-Networking-with-Go-Programming/chapter_3_applications/restexample && go get ./... && go run server/cmd/main.go"' 18 | expose: 19 | - 9021 20 | orderservergrpc: 21 | image: golang:latest 22 | volumes: 23 | - ../:/go/src/github.com/PacktPublishing/Hands-On-Networking-with-Go-Programming/chapter_3_applications 24 | command: 'bash -c "cd /go/src/github.com/PacktPublishing/Hands-On-Networking-with-Go-Programming/chapter_3_applications/grpc && go get ./... && go run server/main.go"' 25 | expose: 26 | - 8888 27 | -------------------------------------------------------------------------------- /chapter_3_applications/graphql/gqlid/id.go: -------------------------------------------------------------------------------- 1 | package gqlid 2 | 3 | import ( 4 | "bytes" 5 | "encoding/base64" 6 | "encoding/gob" 7 | "errors" 8 | "fmt" 9 | ) 10 | 11 | // Type of the ID. 12 | type Type int 13 | 14 | const ( 15 | _ = iota 16 | // TypeInt64 means that the underlying ID is an int64 value. 17 | TypeInt64 Type = 1 18 | // TypeInt32 means that the underlying ID is an int32 value. 19 | TypeInt32 20 | // TypeString means that the underlying ID is a string value. 21 | TypeString 22 | ) 23 | 24 | // Int64 ID. 25 | func Int64(service, resource string, id int64) *ID { 26 | return &ID{ 27 | Service: service, 28 | Resource: resource, 29 | Type: TypeInt64, 30 | ValueInt64: id, 31 | } 32 | } 33 | 34 | // Int32 ID. 35 | func Int32(service, resource string, id int32) *ID { 36 | return &ID{ 37 | Service: service, 38 | Resource: resource, 39 | Type: TypeInt32, 40 | ValueInt32: id, 41 | } 42 | } 43 | 44 | // String ID. 45 | func String(service, resource string, id string) *ID { 46 | return &ID{ 47 | Service: service, 48 | Resource: resource, 49 | Type: TypeString, 50 | ValueString: id, 51 | } 52 | } 53 | 54 | // ID of an item within a GraphQL service. 55 | type ID struct { 56 | Service string 57 | Resource string 58 | Type Type 59 | ValueInt64 int64 60 | ValueInt32 int32 61 | ValueString string 62 | } 63 | 64 | // Int64 gets the value of the ID. 65 | func (id *ID) Int64() (value int64, ok bool) { 66 | if id.Type == TypeInt64 { 67 | return id.ValueInt64, true 68 | } 69 | return 70 | } 71 | 72 | // Int32 gets the value of the ID. 73 | func (id *ID) Int32() (value int32, ok bool) { 74 | if id.Type == TypeInt32 { 75 | return id.ValueInt32, true 76 | } 77 | return 78 | } 79 | 80 | // String gets the value of the ID. 81 | func (id *ID) String() (value string, ok bool) { 82 | if id.Type == TypeString { 83 | return id.ValueString, true 84 | } 85 | return 86 | } 87 | 88 | // ErrNotValidID is returned when the ID if not in the required format (base64 encoded gob). 89 | var ErrNotValidID = errors.New("ID: invalid ID format") 90 | 91 | // Parse an ID from a base64 encoded GraphQL ID. 92 | func Parse(s string) (id *ID, err error) { 93 | ids, err := base64.StdEncoding.DecodeString(s) 94 | if err != nil { 95 | err = ErrNotValidID 96 | return 97 | } 98 | d := gob.NewDecoder(bytes.NewReader(ids)) 99 | err = d.Decode(&id) 100 | return 101 | } 102 | 103 | // ParseInt64For parses an ID and returns an error if it's not an int64. 104 | func ParseInt64For(service, resource string, s string) (idValue int64, err error) { 105 | id, err := Parse(s) 106 | if err != nil { 107 | return 108 | } 109 | if id.Service != service || id.Resource != resource { 110 | err = fmt.Errorf("expected service/resource of '%s/%s', got '%s/%s'", service, resource, id.Service, id.Resource) 111 | return 112 | } 113 | idValue, ok := id.Int64() 114 | if !ok { 115 | err = fmt.Errorf("expected Int64 ID, got %v", id.Type) 116 | return 117 | } 118 | return 119 | } 120 | 121 | // Encoded returns the base64 encoded ID or panics if an error occurred. 122 | func (id *ID) Encoded() (s string) { 123 | w := new(bytes.Buffer) 124 | e := gob.NewEncoder(w) 125 | if err := e.Encode(id); err != nil { 126 | panic(err.Error()) 127 | } 128 | s = base64.StdEncoding.EncodeToString(w.Bytes()) 129 | return 130 | } 131 | -------------------------------------------------------------------------------- /chapter_3_applications/graphql/gqlid/id_test.go: -------------------------------------------------------------------------------- 1 | package gqlid 2 | 3 | import "testing" 4 | 5 | func TestInt64(t *testing.T) { 6 | tests := []struct { 7 | name string 8 | id *ID 9 | expectedService string 10 | expectedResource string 11 | expectedInt64 int64 12 | expectedOK bool 13 | }{ 14 | { 15 | name: "can roundtrip int64", 16 | id: Int64("service_name", "resource_name", 123), 17 | expectedService: "service_name", 18 | expectedResource: "resource_name", 19 | expectedInt64: 123, 20 | expectedOK: true, 21 | }, 22 | } 23 | for _, test := range tests { 24 | test := test 25 | t.Run(test.name, func(t *testing.T) { 26 | t.Parallel() 27 | actual, ok := test.id.Int64() 28 | if test.expectedOK != ok { 29 | t.Fatalf("expected OK %v, got %v", test.expectedOK, ok) 30 | } 31 | if actual != test.expectedInt64 { 32 | t.Fatalf("expected %v, got %v", test.expectedInt64, actual) 33 | } 34 | encoded := test.id.Encoded() 35 | decoded, err := Parse(encoded) 36 | if err != nil { 37 | t.Fatalf("failed to encode and decode the ID: %v", err) 38 | } 39 | if decoded.ValueInt64 != actual { 40 | t.Errorf("failed to decode int64 value, expected %v, got %v", actual, decoded.ValueInt64) 41 | } 42 | }) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /chapter_3_applications/graphql/schema/companyloader_gen.go: -------------------------------------------------------------------------------- 1 | // Code generated by github.com/vektah/dataloaden, DO NOT EDIT. 2 | 3 | package schema 4 | 5 | import ( 6 | "sync" 7 | "time" 8 | ) 9 | 10 | // CompanyLoaderConfig captures the config to create a new CompanyLoader 11 | type CompanyLoaderConfig struct { 12 | // Fetch is a method that provides the data for the loader 13 | Fetch func(keys []int64) ([]*Company, []error) 14 | 15 | // Wait is how long wait before sending a batch 16 | Wait time.Duration 17 | 18 | // MaxBatch will limit the maximum number of keys to send in one batch, 0 = not limit 19 | MaxBatch int 20 | } 21 | 22 | // NewCompanyLoader creates a new CompanyLoader given a fetch, wait, and maxBatch 23 | func NewCompanyLoader(config CompanyLoaderConfig) *CompanyLoader { 24 | return &CompanyLoader{ 25 | fetch: config.Fetch, 26 | wait: config.Wait, 27 | maxBatch: config.MaxBatch, 28 | } 29 | } 30 | 31 | // CompanyLoader batches and caches requests 32 | type CompanyLoader struct { 33 | // this method provides the data for the loader 34 | fetch func(keys []int64) ([]*Company, []error) 35 | 36 | // how long to done before sending a batch 37 | wait time.Duration 38 | 39 | // this will limit the maximum number of keys to send in one batch, 0 = no limit 40 | maxBatch int 41 | 42 | // INTERNAL 43 | 44 | // lazily created cache 45 | cache map[int64]*Company 46 | 47 | // the current batch. keys will continue to be collected until timeout is hit, 48 | // then everything will be sent to the fetch method and out to the listeners 49 | batch *companyBatch 50 | 51 | // mutex to prevent races 52 | mu sync.Mutex 53 | } 54 | 55 | type companyBatch struct { 56 | keys []int64 57 | data []*Company 58 | error []error 59 | closing bool 60 | done chan struct{} 61 | } 62 | 63 | // Load a company by key, batching and caching will be applied automatically 64 | func (l *CompanyLoader) Load(key int64) (*Company, error) { 65 | return l.LoadThunk(key)() 66 | } 67 | 68 | // LoadThunk returns a function that when called will block waiting for a company. 69 | // This method should be used if you want one goroutine to make requests to many 70 | // different data loaders without blocking until the thunk is called. 71 | func (l *CompanyLoader) LoadThunk(key int64) func() (*Company, error) { 72 | l.mu.Lock() 73 | if it, ok := l.cache[key]; ok { 74 | l.mu.Unlock() 75 | return func() (*Company, error) { 76 | return it, nil 77 | } 78 | } 79 | if l.batch == nil { 80 | l.batch = &companyBatch{done: make(chan struct{})} 81 | } 82 | batch := l.batch 83 | pos := batch.keyIndex(l, key) 84 | l.mu.Unlock() 85 | 86 | return func() (*Company, error) { 87 | <-batch.done 88 | 89 | var data *Company 90 | if pos < len(batch.data) { 91 | data = batch.data[pos] 92 | } 93 | 94 | var err error 95 | // its convenient to be able to return a single error for everything 96 | if len(batch.error) == 1 { 97 | err = batch.error[0] 98 | } else if batch.error != nil { 99 | err = batch.error[pos] 100 | } 101 | 102 | if err == nil { 103 | l.mu.Lock() 104 | l.unsafeSet(key, data) 105 | l.mu.Unlock() 106 | } 107 | 108 | return data, err 109 | } 110 | } 111 | 112 | // LoadAll fetches many keys at once. It will be broken into appropriate sized 113 | // sub batches depending on how the loader is configured 114 | func (l *CompanyLoader) LoadAll(keys []int64) ([]*Company, []error) { 115 | results := make([]func() (*Company, error), len(keys)) 116 | 117 | for i, key := range keys { 118 | results[i] = l.LoadThunk(key) 119 | } 120 | 121 | companys := make([]*Company, len(keys)) 122 | errors := make([]error, len(keys)) 123 | for i, thunk := range results { 124 | companys[i], errors[i] = thunk() 125 | } 126 | return companys, errors 127 | } 128 | 129 | // LoadAllThunk returns a function that when called will block waiting for a companys. 130 | // This method should be used if you want one goroutine to make requests to many 131 | // different data loaders without blocking until the thunk is called. 132 | func (l *CompanyLoader) LoadAllThunk(keys []int64) func() ([]*Company, []error) { 133 | results := make([]func() (*Company, error), len(keys)) 134 | for i, key := range keys { 135 | results[i] = l.LoadThunk(key) 136 | } 137 | return func() ([]*Company, []error) { 138 | companys := make([]*Company, len(keys)) 139 | errors := make([]error, len(keys)) 140 | for i, thunk := range results { 141 | companys[i], errors[i] = thunk() 142 | } 143 | return companys, errors 144 | } 145 | } 146 | 147 | // Prime the cache with the provided key and value. If the key already exists, no change is made 148 | // and false is returned. 149 | // (To forcefully prime the cache, clear the key first with loader.clear(key).prime(key, value).) 150 | func (l *CompanyLoader) Prime(key int64, value *Company) bool { 151 | l.mu.Lock() 152 | var found bool 153 | if _, found = l.cache[key]; !found { 154 | // make a copy when writing to the cache, its easy to pass a pointer in from a loop var 155 | // and end up with the whole cache pointing to the same value. 156 | cpy := *value 157 | l.unsafeSet(key, &cpy) 158 | } 159 | l.mu.Unlock() 160 | return !found 161 | } 162 | 163 | // Clear the value at key from the cache, if it exists 164 | func (l *CompanyLoader) Clear(key int64) { 165 | l.mu.Lock() 166 | delete(l.cache, key) 167 | l.mu.Unlock() 168 | } 169 | 170 | func (l *CompanyLoader) unsafeSet(key int64, value *Company) { 171 | if l.cache == nil { 172 | l.cache = map[int64]*Company{} 173 | } 174 | l.cache[key] = value 175 | } 176 | 177 | // keyIndex will return the location of the key in the batch, if its not found 178 | // it will add the key to the batch 179 | func (b *companyBatch) keyIndex(l *CompanyLoader, key int64) int { 180 | for i, existingKey := range b.keys { 181 | if key == existingKey { 182 | return i 183 | } 184 | } 185 | 186 | pos := len(b.keys) 187 | b.keys = append(b.keys, key) 188 | if pos == 0 { 189 | go b.startTimer(l) 190 | } 191 | 192 | if l.maxBatch != 0 && pos >= l.maxBatch-1 { 193 | if !b.closing { 194 | b.closing = true 195 | l.batch = nil 196 | go b.end(l) 197 | } 198 | } 199 | 200 | return pos 201 | } 202 | 203 | func (b *companyBatch) startTimer(l *CompanyLoader) { 204 | time.Sleep(l.wait) 205 | l.mu.Lock() 206 | 207 | // we must have hit a batch limit and are already finalizing this batch 208 | if b.closing { 209 | l.mu.Unlock() 210 | return 211 | } 212 | 213 | l.batch = nil 214 | l.mu.Unlock() 215 | 216 | b.end(l) 217 | } 218 | 219 | func (b *companyBatch) end(l *CompanyLoader) { 220 | b.data, b.error = l.fetch(b.keys) 221 | close(b.done) 222 | } 223 | -------------------------------------------------------------------------------- /chapter_3_applications/graphql/schema/dataloader.go: -------------------------------------------------------------------------------- 1 | package schema 2 | 3 | import ( 4 | "context" 5 | "net/http" 6 | "time" 7 | 8 | "github.com/PacktPublishing/Hands-On-Networking-with-Go-Programming/chapter_3_applications/graphql/gqlid" 9 | 10 | "github.com/PacktPublishing/Hands-On-Networking-with-Go-Programming/chapter_3_applications/restexample/client" 11 | ) 12 | 13 | type dataLoaderMiddlewareKey string 14 | 15 | const companyLoaderKey = dataLoaderMiddlewareKey("dataloaderCompany") 16 | 17 | // WithCompanyDataloaderMiddleware populates the Data Loader middleware for loading company data. 18 | func WithCompanyDataloaderMiddleware(companyClient *client.CompanyClient, next http.Handler) http.Handler { 19 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 20 | l := NewCompanyLoader(CompanyLoaderConfig{ 21 | Fetch: func(ids []int64) (companies []*Company, errs []error) { 22 | // Load the data from the client. 23 | cc, err := companyClient.GetMany(ids) 24 | if err != nil { 25 | errs = append(errs, err) 26 | return 27 | } 28 | // Map from the client returned by the REST client to the client GraphQL type. 29 | companies = make([]*Company, len(cc)) 30 | for i, c := range cc { 31 | companies[i] = &Company{ 32 | ID: gqlid.Int64(companyRestService, companyRestService, c.ID).Encoded(), 33 | Name: c.Name, 34 | VatNumber: "Unknown", 35 | } 36 | } 37 | return 38 | }, 39 | MaxBatch: 100, 40 | Wait: 10 * time.Millisecond, 41 | }) 42 | ctx := context.WithValue(r.Context(), companyLoaderKey, l) 43 | r = r.WithContext(ctx) 44 | next.ServeHTTP(w, r) 45 | }) 46 | } 47 | -------------------------------------------------------------------------------- /chapter_3_applications/graphql/schema/gqlgen.yml: -------------------------------------------------------------------------------- 1 | # .gqlgen.yml example 2 | # 3 | # Refer to https://gqlgen.com/config/ 4 | # for detailed .gqlgen.yml documentation. 5 | 6 | schema: 7 | - schema.graphql 8 | exec: 9 | filename: generated.go 10 | model: 11 | filename: models_gen.go 12 | resolver: 13 | filename: resolver.go 14 | type: Resolver 15 | 16 | # In order to let gqlgen create relationships from the UserID property, we need to 17 | # make our own type. 18 | models: 19 | Order: 20 | model: github.com/PacktPublishing/Hands-On-Networking-with-Go-Programming/chapter_3_applications/graphql/schema.Order -------------------------------------------------------------------------------- /chapter_3_applications/graphql/schema/gqlgen.yml.old: -------------------------------------------------------------------------------- 1 | schema: 2 | - schema.graphql 3 | exec: 4 | filename: generated.go 5 | model: 6 | filename: models_gen.go 7 | resolver: 8 | filename: resolver.go 9 | type: Resolver 10 | 11 | # In order to let gqlgen create relationships from the UserID property, we need to 12 | # make our own type. 13 | models: 14 | Order: 15 | model: github.com/PacktPublishing/Hands-On-Networking-with-Go-Programming/chapter_3_applications/graphql/schema.Order -------------------------------------------------------------------------------- /chapter_3_applications/graphql/schema/models_gen.go: -------------------------------------------------------------------------------- 1 | // Code generated by github.com/99designs/gqlgen, DO NOT EDIT. 2 | 3 | package schema 4 | 5 | type Address struct { 6 | Address1 string `json:"address1"` 7 | Address2 string `json:"address2"` 8 | Address3 string `json:"address3"` 9 | Address4 string `json:"address4"` 10 | Postcode string `json:"postcode"` 11 | Country string `json:"country"` 12 | } 13 | 14 | type Company struct { 15 | ID string `json:"id"` 16 | Name string `json:"name"` 17 | VatNumber string `json:"vatNumber"` 18 | Address Address `json:"address"` 19 | } 20 | 21 | type Item struct { 22 | ID string `json:"id"` 23 | Desc string `json:"desc"` 24 | Price int `json:"price"` 25 | } 26 | 27 | type NewCompany struct { 28 | Name string `json:"name"` 29 | VatNumber string `json:"vatNumber"` 30 | Address NewCompanyAddress `json:"address"` 31 | } 32 | 33 | type NewCompanyAddress struct { 34 | Address1 string `json:"address1"` 35 | Address2 string `json:"address2"` 36 | Address3 string `json:"address3"` 37 | Address4 string `json:"address4"` 38 | Postcode string `json:"postcode"` 39 | Country string `json:"country"` 40 | } 41 | 42 | type NewOrder struct { 43 | Items []string `json:"items"` 44 | } 45 | -------------------------------------------------------------------------------- /chapter_3_applications/graphql/schema/order.go: -------------------------------------------------------------------------------- 1 | package schema 2 | 3 | type Order struct { 4 | ID string `json:"id"` 5 | Items []Item `json:"items"` 6 | CompanyID int64 `json:"companyId"` 7 | } 8 | -------------------------------------------------------------------------------- /chapter_3_applications/graphql/schema/resolver.go: -------------------------------------------------------------------------------- 1 | package schema 2 | 3 | import ( 4 | "context" 5 | "encoding/base64" 6 | "fmt" 7 | 8 | "github.com/PacktPublishing/Hands-On-Networking-with-Go-Programming/chapter_3_applications/graphql/gqlid" 9 | "github.com/PacktPublishing/Hands-On-Networking-with-Go-Programming/chapter_3_applications/grpc/interface/order" 10 | "github.com/PacktPublishing/Hands-On-Networking-with-Go-Programming/chapter_3_applications/restexample/client" 11 | "github.com/PacktPublishing/Hands-On-Networking-with-Go-Programming/chapter_3_applications/restexample/resources/company" 12 | ) 13 | 14 | // These constants define the ID types for the remote services. 15 | // The GraphQL endpoint uses two remote services, one for "Orders" and one for "Companies". 16 | const companyRestService = "companyrest" 17 | const companyRestResource = "company" 18 | 19 | // NewResolver creates the new GraphQL resolver, with the relevant REST and gRPC clients which provide access 20 | // to data. These could, in fact, access data directly. 21 | func NewResolver(oc order.OrdersClient, cc *client.CompanyClient) *Resolver { 22 | return &Resolver{ 23 | OrderClient: oc, 24 | CompanyClient: cc, 25 | } 26 | } 27 | 28 | // A Resolver resolves GraphQL requests: both Mutations and Queries. 29 | type Resolver struct { 30 | //TODO: The Resolver here accepts real clients, but this makes it hard to test. 31 | //TODO: It would be better to replace them with interfaces so that they can be mocked out. 32 | OrderClient order.OrdersClient 33 | CompanyClient *client.CompanyClient 34 | } 35 | 36 | // Mutation returns the MutationResolver which handles any mutation requests. 37 | func (r *Resolver) Mutation() MutationResolver { 38 | return &mutationResolver{ 39 | resolver: r, 40 | companyClient: r.CompanyClient, 41 | orderClient: r.OrderClient, 42 | } 43 | } 44 | 45 | // Query returns resolvers for the GraphQL queries. 46 | func (r *Resolver) Query() QueryResolver { 47 | return &queryResolver{ 48 | resolver: r, 49 | companyClient: r.CompanyClient, 50 | orderClient: r.OrderClient, 51 | } 52 | } 53 | 54 | // Order returns the Order resolver. This isn't a query that clients can use, but is actually 55 | // a way for any Order types retned by a query to lookup additional data, potentially from other 56 | // services. The orderResolver here uses DataLoader middleware to batch up individual requests 57 | // together into less backend operations, reducing latency. 58 | func (r *Resolver) Order() OrderResolver { 59 | return &orderResolver{r} 60 | } 61 | 62 | // The mutationResolver handles mutation requests. 63 | type mutationResolver struct { 64 | resolver *Resolver 65 | companyClient *client.CompanyClient 66 | orderClient order.OrdersClient 67 | } 68 | 69 | // CreateCompany is the createCompany mutation on the GraphQL schema. It uses the company client to 70 | // create a new company. 71 | func (r *mutationResolver) CreateCompany(ctx context.Context, new NewCompany) (created *Company, err error) { 72 | fmt.Println("mutationResolver: CreateCompany") 73 | id, err := r.companyClient.Post(company.Company{ 74 | Name: new.Name, 75 | VATNumber: new.VatNumber, 76 | Address: company.Address{ 77 | Address1: new.Address.Address1, 78 | Address2: new.Address.Address2, 79 | Address3: new.Address.Address3, 80 | Address4: new.Address.Address4, 81 | Country: new.Address.Country, 82 | Postcode: new.Address.Postcode, 83 | }, 84 | }) 85 | if err != nil { 86 | err = fmt.Errorf("failed to post company: %v", err) 87 | return 88 | } 89 | created = &Company{ 90 | ID: gqlid.Int64(companyRestService, companyRestResource, int64(id.ID)).Encoded(), 91 | Name: new.Name, 92 | VatNumber: new.VatNumber, 93 | Address: Address{ 94 | Address1: new.Address.Address1, 95 | Address2: new.Address.Address2, 96 | Address3: new.Address.Address3, 97 | Address4: new.Address.Address4, 98 | Country: new.Address.Country, 99 | Postcode: new.Address.Postcode, 100 | }, 101 | } 102 | return 103 | } 104 | 105 | // CreateOrder is the createOrder mutation on the GraphQL schema. 106 | func (r *mutationResolver) CreateOrder(ctx context.Context, companyID string, new NewOrder) (o *Order, err error) { 107 | fmt.Println("mutationResolver: CreateOrder") 108 | id, err := gqlid.ParseInt64For(companyRestService, companyRestResource, companyID) 109 | if err != nil { 110 | err = fmt.Errorf("company ID was not in expected format: %v", err) 111 | return 112 | } 113 | fmt.Println("parsed id") 114 | items := make([]*order.Item, len(new.Items)) 115 | for i, itm := range new.Items { 116 | items[i] = &order.Item{ 117 | Id: itm, 118 | } 119 | //TODO: Look up the price and description of the items. 120 | } 121 | fmt.Println("adding order") 122 | ar, err := r.orderClient.Add(ctx, &order.AddRequest{ 123 | Fields: &order.Fields{ 124 | CompanyId: id, 125 | Items: items, 126 | UserId: "abc", 127 | }, 128 | }) 129 | if err != nil { 130 | return 131 | } 132 | fmt.Println("sorting out") 133 | o.CompanyID = id 134 | o.ID = ar.Id 135 | o.Items = make([]Item, len(new.Items)) 136 | for i, itm := range new.Items { 137 | o.Items[i].ID = itm 138 | //TODO: Fill out the price and description of the items. 139 | } 140 | return 141 | } 142 | 143 | // orderResolver resolves any requirements that the order has when being loaded. In this case, the *Order type 144 | // doesn't have a Company field on it, but within the GraphQL schema, the Order type has a Company child field. 145 | // This resolver is responsible for loading the Company records and populating those child fields. 146 | type orderResolver struct{ *Resolver } 147 | 148 | // Company populates the Company field for the Order GraphQL type. In this case, it uses the GraphQL dataloader 149 | // to group together multiple requests. 150 | func (r *orderResolver) Company(ctx context.Context, obj *Order) (*Company, error) { 151 | fmt.Println("Order:Company") 152 | // This version doesn't use the data loader, but the CompanyID on the returned value has been converted to a string 153 | // as is usual for GraphQL, so it won't work any more. 154 | // return r.Query().Company(ctx, obj.CompanyID) 155 | // Use the data loader instead. 156 | return ctx.Value(companyLoaderKey).(*CompanyLoader).Load(obj.CompanyID) 157 | } 158 | 159 | // queryResolver implements the queries that can be made against the GraphQL endpoint. 160 | type queryResolver struct { 161 | resolver *Resolver 162 | companyClient *client.CompanyClient 163 | orderClient order.OrdersClient 164 | } 165 | 166 | // Orders implements the Orders GraphQL query. 167 | func (r *queryResolver) Orders(ctx context.Context, companyID string, ids []string) (orders []Order, err error) { 168 | fmt.Println("Query:Orders") 169 | //TODO: Implement a multi-get at the backend, or iterate through the ids. 170 | gr, err := r.orderClient.Get(ctx, &order.GetRequest{ 171 | Id: companyID, 172 | }) 173 | if err != nil { 174 | return 175 | } 176 | orders = append(orders, Order{ 177 | ID: base64.StdEncoding.EncodeToString([]byte(gr.Order.Id)), 178 | CompanyID: gr.Order.Fields.CompanyId, 179 | Items: mapItemsToGraphQL(gr.Order.Fields.Items), 180 | }) 181 | return 182 | } 183 | 184 | func mapItemsToGraphQL(input []*order.Item) (output []Item) { 185 | output = make([]Item, len(input)) 186 | for i, in := range input { 187 | output[i].Desc = in.Desc 188 | output[i].ID = base64.StdEncoding.EncodeToString([]byte(in.Id)) 189 | output[i].Price = int(in.Price) 190 | } 191 | return 192 | } 193 | 194 | // Company implements the Company GraphQL query. 195 | func (r *queryResolver) Company(ctx context.Context, id string) (result *Company, err error) { 196 | fmt.Println("queryResolver: Company", id) 197 | int64id, err := gqlid.ParseInt64For(companyRestService, companyRestResource, id) 198 | if err != nil { 199 | return 200 | } 201 | c, err := r.companyClient.Get(int64id) 202 | if err != nil { 203 | return 204 | } 205 | result = &Company{ 206 | ID: id, 207 | Name: c.Name, 208 | VatNumber: c.VATNumber, 209 | Address: Address{ 210 | Address1: c.Address.Address1, 211 | Address2: c.Address.Address2, 212 | Address3: c.Address.Address3, 213 | Address4: c.Address.Address4, 214 | Country: c.Address.Country, 215 | Postcode: c.Address.Postcode, 216 | }, 217 | } 218 | return 219 | } 220 | 221 | // Companies implments the Companies GraphQL query. 222 | func (r *queryResolver) Companies(ctx context.Context, ids []string) (op []Company, err error) { 223 | fmt.Println("queryResolver: Company", ids) 224 | // This is inefficient, because it simply calls the other resolver multiple times in a loop. 225 | // It would be more realistic to use an endpoint which supports retrieving multiple items in a 226 | // single operation. 227 | op = make([]Company, len(ids)) 228 | for i, id := range ids { 229 | c, cerr := r.Company(ctx, id) 230 | if cerr != nil { 231 | err = cerr 232 | return 233 | } 234 | op[i] = *c 235 | } 236 | return 237 | } 238 | -------------------------------------------------------------------------------- /chapter_3_applications/graphql/schema/schema.graphql: -------------------------------------------------------------------------------- 1 | # Basic types 2 | type Order { 3 | id: ID! 4 | items: [Item!]! 5 | company: Company! 6 | } 7 | 8 | type Item { 9 | id: ID! 10 | desc: String! 11 | price: Int! 12 | } 13 | 14 | type Company { 15 | id: ID! 16 | name: String! 17 | vatNumber: String! 18 | address: Address! 19 | } 20 | 21 | type Address { 22 | address1: String! 23 | address2: String! 24 | address3: String! 25 | address4: String! 26 | postcode: String! 27 | country: String! 28 | } 29 | 30 | # Define the queries. 31 | type Query { 32 | orders(companyId: ID!, ids: [ID!]): [Order!]! 33 | company(id: ID!): Company! 34 | companies(ids: [ID!]!): [Company!]! 35 | } 36 | 37 | # Define input types for mutations. 38 | input NewCompanyAddress { 39 | address1: String! 40 | address2: String! 41 | address3: String! 42 | address4: String! 43 | postcode: String! 44 | country: String! 45 | } 46 | 47 | input NewCompany { 48 | name: String! 49 | vatNumber: String! 50 | address: NewCompanyAddress! 51 | } 52 | 53 | input NewOrder { 54 | items: [ID!]! 55 | } 56 | 57 | # Define mutations. 58 | type Mutation { 59 | createCompany(company: NewCompany!): Company! 60 | createOrder(companyId: ID!, order: NewOrder!): Order! 61 | } -------------------------------------------------------------------------------- /chapter_3_applications/graphql/schema/server/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "net/http" 7 | "os" 8 | 9 | "github.com/PacktPublishing/Hands-On-Networking-with-Go-Programming/chapter_3_applications/grpc/interface/order" 10 | "google.golang.org/grpc" 11 | 12 | "github.com/99designs/gqlgen/handler" 13 | "github.com/PacktPublishing/Hands-On-Networking-with-Go-Programming/chapter_3_applications/graphql/schema" 14 | "github.com/PacktPublishing/Hands-On-Networking-with-Go-Programming/chapter_3_applications/restexample/client" 15 | ) 16 | 17 | const defaultPort = "8080" 18 | 19 | func main() { 20 | port := os.Getenv("PORT") 21 | if port == "" { 22 | port = defaultPort 23 | } 24 | 25 | // Create the gRPC client that provides order details. 26 | grpcConn, err := grpc.Dial("orderservergrpc:8888", grpc.WithInsecure()) 27 | if err != nil { 28 | fmt.Println(err) 29 | return 30 | } 31 | orderClient := order.NewOrdersClient(grpcConn) 32 | 33 | // Create the REST client for the company service. 34 | companyClient := client.NewCompanyClient("http://companyrestserver:9021") 35 | 36 | // Create a resolver. 37 | r := schema.NewResolver(orderClient, companyClient) 38 | 39 | // Allow mutations and resolvers to collect data from the services. 40 | server := handler.GraphQL(schema.NewExecutableSchema(schema.Config{ 41 | Resolvers: r, 42 | })) 43 | http.Handle("/", handler.Playground("GraphQL playground", "/query")) 44 | // Without data loader. 45 | // http.Handle("/query", server) 46 | // With data loader. 47 | // Configure the data loader to carry out additional lookups. 48 | http.Handle("/query", schema.WithCompanyDataloaderMiddleware(companyClient, server)) 49 | 50 | log.Printf("connect to http://localhost:%s/ for GraphQL playground", port) 51 | log.Fatal(http.ListenAndServe(":"+port, nil)) 52 | } 53 | -------------------------------------------------------------------------------- /chapter_3_applications/grpc/client/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/PacktPublishing/Hands-On-Networking-with-Go-Programming/chapter_3_applications/grpc/interface/order" 8 | "google.golang.org/grpc" 9 | ) 10 | 11 | func main() { 12 | cc, err := grpc.Dial("localhost:8888", grpc.WithInsecure()) 13 | if err != nil { 14 | fmt.Println(err) 15 | return 16 | } 17 | client := order.NewOrdersClient(cc) 18 | addResp, err := client.Add(context.Background(), &order.AddRequest{ 19 | Fields: &order.Fields{ 20 | UserId: "userid123", 21 | Items: []*order.Item{ 22 | &order.Item{ 23 | Id: "item1", 24 | Desc: "description of the order", 25 | Price: 3200, 26 | }, 27 | }, 28 | }, 29 | }) 30 | if err != nil { 31 | fmt.Println(err) 32 | return 33 | } 34 | fmt.Println(addResp) 35 | 36 | getResp, err := client.Get(context.Background(), &order.GetRequest{ 37 | Id: addResp.Id, 38 | }) 39 | if err != nil { 40 | fmt.Println(err) 41 | return 42 | } 43 | fmt.Println(getResp) 44 | } 45 | -------------------------------------------------------------------------------- /chapter_3_applications/grpc/interface/order.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package order; 4 | 5 | service Orders { 6 | rpc Get (GetRequest) returns (GetResponse) {} 7 | rpc Add (AddRequest) returns (AddResponse) {} 8 | } 9 | 10 | // Define an order. 11 | message Order { 12 | string id = 1; 13 | Fields fields = 2; 14 | } 15 | 16 | message Fields { 17 | string user_id = 1; 18 | repeated Item items = 2; 19 | int64 companyId = 3; 20 | } 21 | 22 | message Item { 23 | string id = 1; 24 | string desc = 2; 25 | int64 price = 3; 26 | } 27 | 28 | // Define the request / response messages. 29 | message GetRequest { 30 | string id = 1; 31 | } 32 | 33 | message GetResponse { 34 | Order order = 1; 35 | } 36 | 37 | message AddRequest { 38 | Fields fields = 1; 39 | } 40 | 41 | message AddResponse { 42 | string id = 1; 43 | } -------------------------------------------------------------------------------- /chapter_3_applications/grpc/server/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "net" 7 | "time" 8 | 9 | "github.com/PacktPublishing/Hands-On-Networking-with-Go-Programming/chapter_3_applications/grpc/interface/order" 10 | "google.golang.org/grpc/codes" 11 | 12 | "google.golang.org/grpc" 13 | ) 14 | 15 | type OrderServer struct { 16 | orders map[string]*order.Order 17 | } 18 | 19 | func (os *OrderServer) Get(ctx context.Context, r *order.GetRequest) (resp *order.GetResponse, err error) { 20 | v, ok := os.orders[r.Id] 21 | if !ok { 22 | err = grpc.Errorf(codes.NotFound, "order %s not found", r.Id) 23 | return 24 | } 25 | resp = &order.GetResponse{ 26 | Order: v, 27 | } 28 | return 29 | } 30 | 31 | func (os *OrderServer) Add(ctx context.Context, r *order.AddRequest) (resp *order.AddResponse, err error) { 32 | key := fmt.Sprintf("id%d", time.Now().UnixNano()) 33 | os.orders[key] = &order.Order{ 34 | Id: key, 35 | Fields: r.Fields, 36 | } 37 | resp = &order.AddResponse{ 38 | Id: key, 39 | } 40 | return 41 | } 42 | 43 | func main() { 44 | os := &OrderServer{ 45 | orders: make(map[string]*order.Order), 46 | } 47 | s := grpc.NewServer() 48 | order.RegisterOrdersServer(s, os) 49 | l, err := net.Listen("tcp", ":8888") 50 | if err != nil { 51 | fmt.Println(err) 52 | return 53 | } 54 | fmt.Println("gRPC Order server started (port 8888)") 55 | err = s.Serve(l) 56 | if err != nil { 57 | fmt.Println(err) 58 | return 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /chapter_3_applications/restexample/client/client.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "io/ioutil" 8 | "net/http" 9 | "net/url" 10 | "strconv" 11 | 12 | "github.com/PacktPublishing/Hands-On-Networking-with-Go-Programming/chapter_3_applications/restexample/resources/company" 13 | ) 14 | 15 | func NewCompanyClient(endpoint string) *CompanyClient { 16 | return &CompanyClient{ 17 | endpoint: endpoint, 18 | client: http.Client{}, 19 | } 20 | } 21 | 22 | type CompanyClient struct { 23 | endpoint string 24 | client http.Client 25 | } 26 | 27 | func (cc *CompanyClient) List() (companies []company.Company, err error) { 28 | resp, err := cc.client.Get(cc.endpoint + "/companies") 29 | if err != nil { 30 | return 31 | } 32 | err = errorFromResponse(resp) 33 | if err != nil { 34 | return 35 | } 36 | dec := json.NewDecoder(resp.Body) 37 | err = dec.Decode(&companies) 38 | return 39 | } 40 | 41 | func (cc *CompanyClient) Get(id int64) (company company.Company, err error) { 42 | resp, err := cc.client.Get(cc.endpoint + fmt.Sprintf("/company/%d", id)) 43 | if err != nil { 44 | return 45 | } 46 | err = errorFromResponse(resp) 47 | if err != nil { 48 | return 49 | } 50 | dec := json.NewDecoder(resp.Body) 51 | err = dec.Decode(&company) 52 | return 53 | } 54 | 55 | func (cc *CompanyClient) GetMany(ids []int64) (companies []company.Company, err error) { 56 | var q url.Values 57 | for _, id := range ids { 58 | q.Add("ids", strconv.FormatInt(id, 10)) 59 | } 60 | resp, err := cc.client.Get(cc.endpoint + fmt.Sprintf("/company?ids=%s", q.Encode())) 61 | if err != nil { 62 | return 63 | } 64 | err = errorFromResponse(resp) 65 | if err != nil { 66 | return 67 | } 68 | dec := json.NewDecoder(resp.Body) 69 | err = dec.Decode(&companies) 70 | return 71 | } 72 | 73 | func (cc *CompanyClient) Post(c company.Company) (id company.ID, err error) { 74 | url := cc.endpoint + "/companies" 75 | if c.ID > 0 { 76 | url = cc.endpoint + fmt.Sprintf("/company/%d", c.ID) 77 | } 78 | b, err := json.Marshal(c) 79 | if err != nil { 80 | return 81 | } 82 | resp, err := cc.client.Post(url, "application/json", bytes.NewReader(b)) 83 | if err != nil { 84 | return 85 | } 86 | err = errorFromResponse(resp) 87 | if err != nil { 88 | return 89 | } 90 | bdy, err := ioutil.ReadAll(resp.Body) 91 | err = json.Unmarshal(bdy, &id) 92 | if err != nil { 93 | err = fmt.Errorf("failed to decode response body '%v': %v", string(bdy), err) 94 | } 95 | return 96 | } 97 | 98 | func errorFromResponse(r *http.Response) error { 99 | if r.StatusCode == http.StatusNotFound { 100 | return fmt.Errorf("URL %s returned a 404", r.Request.URL.String()) 101 | } 102 | if r.StatusCode < 200 || r.StatusCode > 299 { 103 | return fmt.Errorf("URL %s returned unexpected status %d", r.Request.URL.String(), r.StatusCode) 104 | } 105 | return nil 106 | } 107 | -------------------------------------------------------------------------------- /chapter_3_applications/restexample/client/cmd/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/PacktPublishing/Hands-On-Networking-with-Go-Programming/chapter_3_applications/restexample/client" 7 | 8 | "github.com/PacktPublishing/Hands-On-Networking-with-Go-Programming/chapter_3_applications/restexample/resources/company" 9 | ) 10 | 11 | func main() { 12 | cc := client.NewCompanyClient("http://localhost:9021") 13 | 14 | fmt.Println("Listing companies") 15 | companies, err := cc.List() 16 | if err != nil { 17 | fmt.Println(err) 18 | return 19 | } 20 | for _, c := range companies { 21 | fmt.Println(c) 22 | } 23 | fmt.Println() 24 | 25 | fmt.Println("Creating new company") 26 | c := company.Company{ 27 | Name: "Test Company 1", 28 | Address: company.Address{ 29 | Address1: "Address 1", 30 | Address2: "Address 2", 31 | Address3: "Address 3", 32 | Address4: "Address 4", 33 | Postcode: "Postcode", 34 | }, 35 | VATNumber: "12345", 36 | } 37 | id, err := cc.Post(c) 38 | if err != nil { 39 | fmt.Println(err) 40 | return 41 | } 42 | fmt.Printf("Created ID: %d\n", id) 43 | fmt.Println() 44 | 45 | fmt.Println("Getting company") 46 | c, err = cc.Get(id.ID) 47 | if err != nil { 48 | fmt.Println(err) 49 | return 50 | } 51 | fmt.Println() 52 | 53 | fmt.Println("Listing companies") 54 | companies, err = cc.List() 55 | if err != nil { 56 | fmt.Println(err) 57 | return 58 | } 59 | for _, c := range companies { 60 | fmt.Println(c) 61 | } 62 | fmt.Println() 63 | } 64 | -------------------------------------------------------------------------------- /chapter_3_applications/restexample/resources/company/structure.go: -------------------------------------------------------------------------------- 1 | package company 2 | 3 | // ID is returned when a company is posted. 4 | type ID struct { 5 | ID int64 `json:"id"` 6 | } 7 | 8 | type Company struct { 9 | ID int64 `json:"id"` 10 | Name string `json:"name"` 11 | VATNumber string `json:"vatNumber"` 12 | Address Address `json:"address"` 13 | } 14 | 15 | type Address struct { 16 | Address1 string `json:"address1"` 17 | Address2 string `json:"address2"` 18 | Address3 string `json:"address3"` 19 | Address4 string `json:"address4"` 20 | Postcode string `json:"postcode"` 21 | Country string `json:"country"` 22 | } 23 | -------------------------------------------------------------------------------- /chapter_3_applications/restexample/server/cmd/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | 7 | "github.com/PacktPublishing/Hands-On-Networking-with-Go-Programming/chapter_3_applications/restexample/server" 8 | "github.com/gorilla/mux" 9 | ) 10 | 11 | var hf = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 12 | w.Write([]byte("OK")) 13 | }) 14 | 15 | func main() { 16 | fmt.Println("REST company server started (port 9021)") 17 | m := mux.NewRouter() 18 | store := server.NewInMemoryCompanyStore() 19 | ch := server.NewCompanyHandler(store) 20 | m.Path("/companies").Methods("GET").HandlerFunc(ch.List) 21 | m.Path("/companies").Methods("POST").HandlerFunc(ch.Post) 22 | m.Path("/company/{id}").Methods("GET").HandlerFunc(ch.Get) 23 | m.Path("/company?ids={ids}").Methods("GET").HandlerFunc(ch.GetMany) 24 | m.Path("/company/{id}").Methods("POST").HandlerFunc(ch.Post) 25 | m.Path("/company/{id}/delete").Methods("POST").HandlerFunc(ch.Delete) 26 | http.ListenAndServe(":9021", m) 27 | } 28 | -------------------------------------------------------------------------------- /chapter_3_applications/restexample/server/handler.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "encoding/json" 5 | "log" 6 | "net/http" 7 | "strconv" 8 | "strings" 9 | 10 | "github.com/PacktPublishing/Hands-On-Networking-with-Go-Programming/chapter_3_applications/restexample/resources/company" 11 | "github.com/gorilla/mux" 12 | ) 13 | 14 | type CompanyStore interface { 15 | Get(id int64) (company company.Company, ok bool, err error) 16 | List() (companies []company.Company, err error) 17 | Upsert(c company.Company) (id int64, err error) 18 | Delete(id int64) (err error) 19 | } 20 | 21 | func NewCompanyHandler(cs CompanyStore) *CompanyHandler { 22 | return &CompanyHandler{ 23 | CompanyStore: cs, 24 | } 25 | } 26 | 27 | type CompanyHandler struct { 28 | CompanyStore CompanyStore 29 | } 30 | 31 | func (ch *CompanyHandler) List(w http.ResponseWriter, r *http.Request) { 32 | log.Println(r.Method, r.URL, "CompanyHandler.List") 33 | result, err := ch.CompanyStore.List() 34 | if err != nil { 35 | http.Error(w, "failed to get company list", http.StatusInternalServerError) 36 | return 37 | } 38 | enc := json.NewEncoder(w) 39 | if err := enc.Encode(result); err != nil { 40 | http.Error(w, "failed to encode result", http.StatusInternalServerError) 41 | return 42 | } 43 | return 44 | } 45 | 46 | func (ch *CompanyHandler) Post(w http.ResponseWriter, r *http.Request) { 47 | log.Println(r.Method, r.URL, "CompanyHandler.Post") 48 | dec := json.NewDecoder(r.Body) 49 | var c company.Company 50 | if err := dec.Decode(&c); err != nil { 51 | http.Error(w, "failed to decode body", http.StatusBadRequest) 52 | return 53 | } 54 | id, err := ch.CompanyStore.Upsert(c) 55 | if err != nil { 56 | http.Error(w, "failed to upsert", http.StatusInternalServerError) 57 | return 58 | } 59 | result := company.ID{ID: id} 60 | enc := json.NewEncoder(w) 61 | if err := enc.Encode(result); err != nil { 62 | http.Error(w, "failed to encode result", http.StatusInternalServerError) 63 | return 64 | } 65 | return 66 | } 67 | 68 | func (ch *CompanyHandler) Get(w http.ResponseWriter, r *http.Request) { 69 | log.Println(r.Method, r.URL, "CompanyHandler.Get") 70 | idVar, ok := mux.Vars(r)["id"] 71 | if !ok { 72 | http.Error(w, "missing ID", http.StatusNotFound) 73 | return 74 | } 75 | id, err := strconv.ParseInt(idVar, 10, 64) 76 | if err != nil { 77 | http.Error(w, "invalid ID", http.StatusBadRequest) 78 | return 79 | } 80 | enc := json.NewEncoder(w) 81 | c, ok, err := ch.CompanyStore.Get(id) 82 | if err != nil { 83 | http.Error(w, "error getting company from store", http.StatusInternalServerError) 84 | return 85 | } 86 | if !ok { 87 | http.Error(w, "ID not found", http.StatusNotFound) 88 | return 89 | } 90 | if err := enc.Encode(c); err != nil { 91 | http.Error(w, "failed to encode result", http.StatusInternalServerError) 92 | return 93 | } 94 | return 95 | } 96 | 97 | func (ch *CompanyHandler) GetMany(w http.ResponseWriter, r *http.Request) { 98 | log.Println(r.Method, r.URL, "CompanyHandler.GetMany") 99 | idVar, ok := mux.Vars(r)["ids"] 100 | if !ok { 101 | http.Error(w, "missing IDs", http.StatusNotFound) 102 | return 103 | } 104 | idStrings := strings.Split(idVar, ",") 105 | cs := make([]company.Company, len(idStrings)) 106 | for i, idsv := range idStrings { 107 | id, err := strconv.ParseInt(idsv, 10, 64) 108 | if err != nil { 109 | http.Error(w, "invalid ID", http.StatusBadRequest) 110 | return 111 | } 112 | c, ok, err := ch.CompanyStore.Get(id) 113 | if err != nil { 114 | http.Error(w, "error getting company from store", http.StatusInternalServerError) 115 | return 116 | } 117 | if !ok { 118 | http.Error(w, "ID not found", http.StatusNotFound) 119 | return 120 | } 121 | cs[i] = c 122 | } 123 | enc := json.NewEncoder(w) 124 | if err := enc.Encode(cs); err != nil { 125 | http.Error(w, "failed to encode result", http.StatusInternalServerError) 126 | return 127 | } 128 | return 129 | } 130 | 131 | func (ch *CompanyHandler) Delete(w http.ResponseWriter, r *http.Request) { 132 | log.Println(r.Method, r.URL, "CompanyHandler.Delete") 133 | idVar, ok := mux.Vars(r)["id"] 134 | if !ok { 135 | http.Error(w, "missing ID", http.StatusNotFound) 136 | return 137 | } 138 | id, err := strconv.ParseInt(idVar, 10, 64) 139 | if err != nil { 140 | http.Error(w, "invalid ID", http.StatusBadRequest) 141 | return 142 | } 143 | err = ch.CompanyStore.Delete(id) 144 | if err != nil { 145 | http.Error(w, "error deleting company from store", http.StatusInternalServerError) 146 | return 147 | } 148 | w.Write([]byte(`{ "ok": true; }`)) 149 | } 150 | -------------------------------------------------------------------------------- /chapter_3_applications/restexample/server/handler_test.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | "net/http" 7 | "net/http/httptest" 8 | "testing" 9 | 10 | "github.com/PacktPublishing/Hands-On-Networking-with-Go-Programming/chapter_3_applications/restexample/resources/company" 11 | ) 12 | 13 | type TestStore struct { 14 | GetFn func(id int64) (company company.Company, ok bool, err error) 15 | ListFn func() (companies []company.Company, err error) 16 | UpsertFn func(c company.Company) (id int64, err error) 17 | DeleteFn func(id int64) (err error) 18 | } 19 | 20 | func (ts TestStore) Get(id int64) (company company.Company, ok bool, err error) { 21 | return ts.GetFn(id) 22 | } 23 | func (ts TestStore) List() (companies []company.Company, err error) { 24 | return ts.ListFn() 25 | } 26 | func (ts TestStore) Upsert(c company.Company) (id int64, err error) { 27 | return ts.UpsertFn(c) 28 | } 29 | func (ts TestStore) Delete(id int64) (err error) { 30 | return ts.DeleteFn(id) 31 | } 32 | 33 | func TestList(t *testing.T) { 34 | tests := []struct { 35 | name string 36 | store CompanyStore 37 | r *http.Request 38 | expectedStatusCode int 39 | expectedBody string 40 | }{ 41 | { 42 | name: "a store error results in an internal server error", 43 | store: TestStore{ 44 | ListFn: func() (companies []company.Company, err error) { 45 | return nil, fmt.Errorf("database error") 46 | }, 47 | }, 48 | r: httptest.NewRequest("GET", "/List", nil), 49 | expectedStatusCode: http.StatusInternalServerError, 50 | expectedBody: "failed to get company list\n", 51 | }, 52 | { 53 | name: "the list is returned in JSON format", 54 | store: TestStore{ 55 | ListFn: func() (companies []company.Company, err error) { 56 | companies = []company.Company{ 57 | company.Company{ 58 | ID: 1, 59 | Name: "Test Company 1", 60 | Address: company.Address{ 61 | Address1: "Address 1", 62 | Postcode: "Postcode", 63 | }, 64 | }, 65 | } 66 | return 67 | }, 68 | }, 69 | r: httptest.NewRequest("GET", "/List", nil), 70 | expectedStatusCode: http.StatusOK, 71 | expectedBody: `[{"id":1,"name":"Test Company 1","vatNumber":"","address":{"address1":"Address 1","address2":"","address3":"","address4":"","postcode":"Postcode","country":""}}]` + "\n", 72 | }, 73 | } 74 | for _, test := range tests { 75 | t.Run(test.name, func(t *testing.T) { 76 | h := NewCompanyHandler(test.store) 77 | w := httptest.NewRecorder() 78 | h.List(w, test.r) 79 | actualBody, err := ioutil.ReadAll(w.Result().Body) 80 | if err != nil { 81 | t.Errorf("failed to read body: %v", err) 82 | } 83 | if string(actualBody) != test.expectedBody { 84 | t.Errorf("expected body:\n%s\n\nactual body:\n%s\n\n", test.expectedBody, string(actualBody)) 85 | } 86 | if w.Result().StatusCode != test.expectedStatusCode { 87 | t.Errorf("expected status %d, got %d", test.expectedStatusCode, w.Result().StatusCode) 88 | } 89 | }) 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /chapter_3_applications/restexample/server/inmemorystore.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "sort" 5 | "sync" 6 | 7 | "github.com/PacktPublishing/Hands-On-Networking-with-Go-Programming/chapter_3_applications/restexample/resources/company" 8 | ) 9 | 10 | func NewInMemoryCompanyStore() *InMemoryCompanyStore { 11 | return &InMemoryCompanyStore{ 12 | companies: make(map[int64]company.Company), 13 | m: &sync.Mutex{}, 14 | } 15 | } 16 | 17 | type InMemoryCompanyStore struct { 18 | companies map[int64]company.Company 19 | m *sync.Mutex 20 | } 21 | 22 | func (imcs *InMemoryCompanyStore) List() (companies []company.Company, err error) { 23 | imcs.m.Lock() 24 | defer imcs.m.Unlock() 25 | // Get all keys. 26 | keys := make([]int64, len(imcs.companies)) 27 | var i int 28 | for k := range imcs.companies { 29 | keys[i] = k 30 | i++ 31 | } 32 | // Sort the keys. 33 | sort.Slice(keys, func(i, j int) bool { 34 | return keys[i] < keys[j] 35 | }) 36 | // Return the sorted result. 37 | companies = make([]company.Company, len(keys)) 38 | for i, k := range keys { 39 | companies[i] = imcs.companies[k] 40 | } 41 | return 42 | } 43 | 44 | func (imcs *InMemoryCompanyStore) Upsert(c company.Company) (id int64, err error) { 45 | imcs.m.Lock() 46 | defer imcs.m.Unlock() 47 | if c.ID == 0 { 48 | c.ID = int64(len(imcs.companies)) 49 | } 50 | imcs.companies[c.ID] = c 51 | id = c.ID 52 | return 53 | } 54 | 55 | func (imcs *InMemoryCompanyStore) Get(id int64) (c company.Company, ok bool, err error) { 56 | imcs.m.Lock() 57 | defer imcs.m.Unlock() 58 | c, ok = imcs.companies[id] 59 | return 60 | } 61 | 62 | func (imcs *InMemoryCompanyStore) Delete(id int64) (err error) { 63 | imcs.m.Lock() 64 | defer imcs.m.Unlock() 65 | delete(imcs.companies, id) 66 | return 67 | } 68 | -------------------------------------------------------------------------------- /chapter_3_applications/router/router.go: -------------------------------------------------------------------------------- 1 | package router 2 | 3 | import "net/http" 4 | 5 | // New creates a new Router. 6 | func New() *Router { 7 | return &Router{ 8 | routes: make(map[route]http.Handler), 9 | } 10 | } 11 | 12 | // Router for HTTP requests. 13 | type Router struct { 14 | routes map[route]http.Handler 15 | } 16 | 17 | // AddRoute to the Router. 18 | func (rtr *Router) AddRoute(path string, method string, handler http.Handler) *Router { 19 | rtr.routes[route{Path: path, Method: method}] = handler 20 | return rtr 21 | } 22 | 23 | func (rtr *Router) ServeHTTP(w http.ResponseWriter, r *http.Request) { 24 | incoming := route{Path: r.URL.Path, Method: r.Method} 25 | child, exists := rtr.routes[incoming] 26 | if !exists { 27 | http.NotFound(w, r) 28 | return 29 | } 30 | child.ServeHTTP(w, r) 31 | } 32 | 33 | type route struct { 34 | Path string 35 | Method string 36 | } 37 | -------------------------------------------------------------------------------- /chapter_3_applications/router/router_test.go: -------------------------------------------------------------------------------- 1 | package router 2 | 3 | import ( 4 | "net/http" 5 | "net/http/httptest" 6 | "strconv" 7 | "testing" 8 | ) 9 | 10 | func getHandler(status int) http.Handler { 11 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 12 | w.WriteHeader(status) 13 | w.Write([]byte(strconv.Itoa(status))) 14 | }) 15 | } 16 | 17 | func TestRouter(t *testing.T) { 18 | tests := []struct { 19 | name string 20 | rtr *Router 21 | r *http.Request 22 | expectedStatus int 23 | }{ 24 | { 25 | name: "expect error", 26 | rtr: New().AddRoute("/error", "GET", getHandler(500)).AddRoute("/success", "GET", getHandler(200)), 27 | r: httptest.NewRequest("GET", "/error", nil), 28 | expectedStatus: 500, 29 | }, 30 | { 31 | name: "expect success", 32 | rtr: New().AddRoute("/error", "GET", getHandler(500)).AddRoute("/success", "GET", getHandler(200)), 33 | r: httptest.NewRequest("GET", "/success", nil), 34 | expectedStatus: 200, 35 | }, 36 | { 37 | name: "expect not found", 38 | rtr: New().AddRoute("/error", "GET", getHandler(500)).AddRoute("/success", "GET", getHandler(200)), 39 | r: httptest.NewRequest("GET", "/something", nil), 40 | expectedStatus: 404, 41 | }, 42 | { 43 | name: "method is important", 44 | rtr: New().AddRoute("/error", "GET", getHandler(500)).AddRoute("/success", "GET", getHandler(200)), 45 | r: httptest.NewRequest("POST", "/success", nil), 46 | expectedStatus: 404, 47 | }, 48 | } 49 | for _, test := range tests { 50 | t.Run(test.name, func(t *testing.T) { 51 | w := httptest.NewRecorder() 52 | test.rtr.ServeHTTP(w, test.r) 53 | if w.Result().StatusCode != test.expectedStatus { 54 | t.Errorf("expected status %d, got %d", test.expectedStatus, w.Result().StatusCode) 55 | } 56 | }) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /chapter_3_applications/router/server/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "net/http" 5 | "time" 6 | 7 | "github.com/PacktPublishing/Hands-On-Networking-with-Go-Programming/chapter_3_applications/router" 8 | ) 9 | 10 | func main() { 11 | time := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 12 | w.Write([]byte(time.Now().UTC().String())) 13 | }) 14 | 15 | r := router.New(). 16 | AddRoute("/time", http.MethodGet, time) 17 | 18 | users := map[string]string{ 19 | "jonty": "shsgfhjdsf", 20 | "terry": "123bmsda!", 21 | } 22 | 23 | h := withBasicAuth(users, r) 24 | 25 | http.ListenAndServe(":8773", h) 26 | } 27 | 28 | func withBasicAuth(userNameToPassword map[string]string, next http.Handler) http.Handler { 29 | f := func(w http.ResponseWriter, r *http.Request) { 30 | u, p, ok := r.BasicAuth() 31 | if !ok { 32 | http.Error(w, "basic auth not used", http.StatusUnauthorized) 33 | return 34 | } 35 | if pp, ok := userNameToPassword[u]; !ok || p != pp { 36 | http.Error(w, "unkown user or invalid password", http.StatusUnauthorized) 37 | return 38 | } 39 | next.ServeHTTP(w, r) 40 | } 41 | return http.HandlerFunc(f) 42 | } 43 | -------------------------------------------------------------------------------- /chapter_3_applications/tcpcopy/encryption/encrypt.go: -------------------------------------------------------------------------------- 1 | package encryption 2 | 3 | import ( 4 | "crypto/aes" 5 | "crypto/cipher" 6 | "io" 7 | ) 8 | 9 | func Encrypt(output io.Writer, input io.Reader, key []byte) (err error) { 10 | block, err := aes.NewCipher(key) 11 | if err != nil { 12 | return err 13 | } 14 | var iv [aes.BlockSize]byte 15 | stream := cipher.NewOFB(block, iv[:]) 16 | reader := &cipher.StreamReader{S: stream, R: input} 17 | _, err = io.Copy(output, reader) 18 | return 19 | } 20 | 21 | func Decrypt(output io.Writer, input io.Reader, key []byte) (err error) { 22 | block, err := aes.NewCipher(key) 23 | if err != nil { 24 | return err 25 | } 26 | var iv [aes.BlockSize]byte 27 | stream := cipher.NewOFB(block, iv[:]) 28 | writer := &cipher.StreamWriter{S: stream, W: output} 29 | _, err = io.Copy(writer, input) 30 | return 31 | } 32 | -------------------------------------------------------------------------------- /chapter_3_applications/tcpcopy/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "os" 7 | 8 | "github.com/PacktPublishing/Hands-On-Networking-with-Go-Programming/chapter_3_applications/tcpcopy/receive" 9 | "github.com/PacktPublishing/Hands-On-Networking-with-Go-Programming/chapter_3_applications/tcpcopy/send" 10 | ) 11 | 12 | var modeFlag = flag.String("m", "recv", "Mode (send or recv)") 13 | var targetFlag = flag.String("t", "", "Target host of the operation") 14 | var keyFlag = flag.String("key", "", "4 words used as the encryption key") 15 | var fileFlag = flag.String("f", "", "The path to the file to send") 16 | 17 | func main() { 18 | flag.Parse() 19 | if *modeFlag == "send" { 20 | // Send 21 | fmt.Printf("Sending file %s to %s\n", *fileFlag, *targetFlag) 22 | err := send.Start(*fileFlag, *targetFlag, *keyFlag) 23 | exit(err) 24 | } 25 | // Receive. 26 | exit(receive.Start()) 27 | } 28 | 29 | func exit(err error) { 30 | if err != nil { 31 | fmt.Println("error", err) 32 | os.Exit(1) 33 | } 34 | fmt.Println("Complete.") 35 | os.Exit(0) 36 | } 37 | -------------------------------------------------------------------------------- /chapter_3_applications/tcpcopy/receive/main.go: -------------------------------------------------------------------------------- 1 | package receive 2 | 3 | import ( 4 | "bytes" 5 | "crypto/sha256" 6 | "errors" 7 | "fmt" 8 | "io" 9 | "net" 10 | "os" 11 | "os/signal" 12 | "path" 13 | "strings" 14 | "syscall" 15 | 16 | "github.com/PacktPublishing/Hands-On-Networking-with-Go-Programming/chapter_3_applications/tcpcopy/encryption" 17 | "github.com/PacktPublishing/Hands-On-Networking-with-Go-Programming/chapter_3_applications/tcpcopy/wordlist" 18 | ) 19 | 20 | // Start the receive. 21 | func Start() (err error) { 22 | words, err := wordlist.Random(4) 23 | if err != nil { 24 | err = fmt.Errorf("unable to create encyrption key: %v", err) 25 | return 26 | } 27 | encryptionKey := sha256.Sum256([]byte(strings.Join(words, " "))) 28 | 29 | l, err := net.ListenTCP("tcp", &net.TCPAddr{Port: 8002}) 30 | if err != nil { 31 | fmt.Println(err) 32 | os.Exit(1) 33 | } 34 | 35 | sigs := make(chan os.Signal) 36 | signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) 37 | 38 | go func() { 39 | for { 40 | conn, err := l.AcceptTCP() 41 | if err != nil && err.Error() != "use of closed network connection" { 42 | fmt.Println("Error:", err) 43 | continue 44 | } 45 | go handle(conn, encryptionKey[:]) 46 | } 47 | }() 48 | 49 | fmt.Printf("Listening on: %s\n", l.Addr()) 50 | fmt.Println("Use command line:") 51 | fmt.Printf(" tcpcopy -m send -t %s -key '%s' -f filename.txt\n", l.Addr(), strings.Join(words, " ")) 52 | <-sigs 53 | fmt.Println() 54 | fmt.Println("Closing down") 55 | l.Close() 56 | return 57 | } 58 | 59 | // ErrUnexpectedHash is returned when an unexpected hash is encountered. 60 | var ErrUnexpectedHash = errors.New("unexpected hash, is the encryption key correct?") 61 | 62 | var separator = []byte("|") 63 | 64 | func handle(conn *net.TCPConn, encryptionKey []byte) { 65 | defer conn.Close() 66 | buf := make([]byte, 256*1024) // 256KB buffer. 67 | 68 | // Read the first 256KB. 69 | n, err := conn.Read(buf) 70 | if err != nil { 71 | //TODO: Push errors to a channel. 72 | fmt.Println(err) 73 | return 74 | } 75 | // Split until we get a pipe. 76 | // Should receive: 77 | // filename.txt|sha256_hash_of_encrypted_data|encrypted_filedata 78 | ranges := bytes.SplitN(buf[:n], separator, 3) 79 | if len(ranges) != 3 { 80 | //TODO: Push errors to a channel. 81 | fmt.Println("invalid data", len(ranges)) 82 | return 83 | } 84 | _, fn := path.Split(string(ranges[0])) 85 | //TODO: Validate the filename. 86 | expectedHash := ranges[1] 87 | data := ranges[2] 88 | 89 | // Create the output file. 90 | if _, err := os.Stat(fn); err == nil { 91 | //TODO: Push errors to a channel. 92 | fmt.Println(os.ErrExist) 93 | return 94 | } 95 | f, err := os.Create(fn) 96 | if err != nil { 97 | //TODO: Push errors to a channel. 98 | fmt.Println(err) 99 | return 100 | } 101 | 102 | // Write the output data to a SHA256 calculation and the output file. 103 | actualHash := sha256.New() 104 | w := io.MultiWriter(f, actualHash) 105 | 106 | // Combine the data we've already read, and the rest of the data from the TCP connection. 107 | r := io.MultiReader(bytes.NewReader(data), conn) 108 | // Decrypt the body. 109 | err = encryption.Decrypt(w, r, encryptionKey) 110 | if err != nil { 111 | //TODO: Push errors to a channel. 112 | fmt.Println(err) 113 | return 114 | } 115 | 116 | // Check that the expected hash equals the data we got. 117 | if !areEqual(actualHash.Sum(nil), expectedHash) { 118 | err = ErrUnexpectedHash 119 | } 120 | return 121 | } 122 | 123 | func areEqual(a, b []byte) bool { 124 | if a != nil && b == nil || a == nil && b != nil { 125 | return false 126 | } 127 | if len(a) != len(b) { 128 | return false 129 | } 130 | for i := 0; i < len(a); i++ { 131 | if a[i] != b[i] { 132 | return false 133 | } 134 | } 135 | return true 136 | } 137 | -------------------------------------------------------------------------------- /chapter_3_applications/tcpcopy/send/main.go: -------------------------------------------------------------------------------- 1 | package send 2 | 3 | import ( 4 | "crypto/sha256" 5 | "flag" 6 | "fmt" 7 | "net" 8 | "os" 9 | "path" 10 | 11 | "github.com/PacktPublishing/Hands-On-Networking-with-Go-Programming/chapter_3_applications/tcpcopy/encryption" 12 | ) 13 | 14 | func Start(fileName string, url string, key string) (err error) { 15 | flag.Parse() 16 | 17 | file, err := os.Open(fileName) 18 | if err != nil { 19 | return 20 | } 21 | defer file.Close() 22 | 23 | encryptionKey := sha256.Sum256([]byte(key)) 24 | 25 | hash := sha256.New() 26 | err = encryption.Encrypt(hash, file, encryptionKey[:]) 27 | if err != nil { 28 | return 29 | } 30 | // Reset the file, because we need to read it again. 31 | _, err = file.Seek(0, 0) 32 | if err != nil { 33 | return 34 | } 35 | 36 | conn, err := net.Dial("tcp", url) 37 | if err != nil { 38 | return 39 | } 40 | defer conn.Close() 41 | // Write the header. 42 | _, fn := path.Split(fileName) 43 | header := fn + "|" + fmt.Sprintf("%2x", hash.Sum(nil)) + "|" 44 | _, err = conn.Write([]byte(header)) 45 | if err != nil { 46 | err = fmt.Errorf("failed to write header: %v", err) 47 | return 48 | } 49 | // Write the rest of the file. 50 | err = encryption.Encrypt(conn, file, encryptionKey[:]) 51 | return 52 | } 53 | -------------------------------------------------------------------------------- /chapter_3_applications/tcpcopy/wordlist/pgp.go: -------------------------------------------------------------------------------- 1 | package wordlist 2 | 3 | import ( 4 | "crypto/rand" 5 | "errors" 6 | ) 7 | 8 | var even = []string{"aardvark", "absurd", "accrue", "acme", "adrift", "adult", "afflict", "ahead", "aimless", "algol", "allow", "alone", "ammo", "ancient", "apple", "artist", "assume", "athens", "atlas", "aztec", "baboon", "backfield", "backward", "banjo", "beaming", "bedlamp", "beehive", "beeswax", "befriend", "belfast", "berserk", "billiard", "bison", "blackjack", "blockade", "blowtorch", "bluebird", "bombast", "bookshelf", "brackish", "breadline", "breakup", "brickyard", "briefcase", "burbank", "button", "buzzard", "cement", "chairlift", "chatter", "checkup", "chisel", "choking", "chopper", "christmas", "clamshell", "classic", "classroom", "cleanup", "clockwork", "cobra", "commence", "concert", "cowbell", "crackdown", "cranky", "crowfoot", "crucial", "crumpled", "crusade", "cubic", "dashboard", "deadbolt", "deckhand", "dogsled", "dragnet", "drainage", "dreadful", "drifter", "dropper", "drumbeat", "drunken", "dupont", "dwelling", "eating", "edict", "egghead", "eightball", "endorse", "endow", "enlist", "erase", "escape", "exceed", "eyeglass", "eyetooth", "facial", "fallout", "flagpole", "flatfoot", "flytrap", "fracture", "framework", "freedom", "frighten", "gazelle", "geiger", "glitter", "glucose", "goggles", "goldfish", "gremlin", "guidance", "hamlet", "highchair", "hockey", "indoors", "indulge", "inverse", "involve", "island", "jawbone", "keyboard", "kickoff", "kiwi", "klaxon", "locale", "lockup", "merit", "minnow", "miser", "mohawk", "mural", "music", "necklace", "neptune", "newborn", "nightbird", "oakland", "obtuse", "offload", "optic", "orca", "payday", "peachy", "pheasant", "physique", "playhouse", "pluto", "preclude", "prefer", "preshrunk", "printer", "prowler", "pupil", "puppy", "python", "quadrant", "quiver", "quota", "ragtime", "ratchet", "rebirth", "reform", "regain", "reindeer", "rematch", "repay", "retouch", "revenge", "reward", "rhythm", "ribcage", "ringbolt", "robust", "rocker", "ruffled", "sailboat", "sawdust", "scallion", "scenic", "scorecard", "scotland", "seabird", "select", "sentence", "shadow", "shamrock", "showgirl", "skullcap", "skydive", "slingshot", "slowdown", "snapline", "snapshot", "snowcap", "snowslide", "solo", "southward", "soybean", "spaniel", "spearhead", "spellbind", "spheroid", "spigot", "spindle", "spyglass", "stagehand", "stagnate", "stairway", "standard", "stapler", "steamship", "sterling", "stockman", "stopwatch", "stormy", "sugar", "surmount", "suspense", "sweatband", "swelter", "tactics", "talon", "tapeworm", "tempest", "tiger", "tissue", "tonic", "topmost", "tracker", "transit", "trauma", "treadmill", "trojan", "trouble", "tumor", "tunnel", "tycoon", "uncut", "unearth", "unwind", "uproot", "upset", "upshot", "vapor", "village", "virus", "vulcan", "waffle", "wallet", "watchword", "wayside", "willow", "woodlark", "zulu"} 9 | var odd = []string{"adroitness", "adviser", "aftermath", "aggregate", "alkali", "almighty", "amulet", "amusement", "antenna", "applicant", "apollo", "armistice", "article", "asteroid", "atlantic", "atmosphere", "autopsy", "babylon", "backwater", "barbecue", "belowground", "bifocals", "bodyguard", "bookseller", "borderline", "bottomless", "bradbury", "bravado", "brazilian", "breakaway", "burlington", "businessman", "butterfat", "camelot", "candidate", "cannonball", "capricorn", "caravan", "caretaker", "celebrate", "cellulose", "certify", "chambermaid", "cherokee", "chicago", "clergyman", "coherence", "combustion", "commando", "company", "component", "concurrent", "confidence", "conformist", "congregate", "consensus", "consulting", "corporate", "corrosion", "councilman", "crossover", "crucifix", "cumbersome", "customer", "dakota", "decadence", "december", "decimal", "designing", "detector", "detergent", "determine", "dictator", "dinosaur", "direction", "disable", "disbelief", "disruptive", "distortion", "document", "embezzle", "enchanting", "enrollment", "enterprise", "equation", "equipment", "escapade", "eskimo", "everyday", "examine", "existence", "exodus", "fascinate", "filament", "finicky", "forever", "fortitude", "frequency", "gadgetry", "galveston", "getaway", "glossary", "gossamer", "graduate", "gravity", "guitarist", "hamburger", "hamilton", "handiwork", "hazardous", "headwaters", "hemisphere", "hesitate", "hideaway", "holiness", "hurricane", "hydraulic", "impartial", "impetus", "inception", "indigo", "inertia", "infancy", "inferno", "informant", "insincere", "insurgent", "integrate", "intention", "inventive", "istanbul", "jamaica", "jupiter", "leprosy", "letterhead", "liberty", "maritime", "matchmaker", "maverick", "medusa", "megaton", "microscope", "microwave", "midsummer", "millionaire", "miracle", "misnomer", "molasses", "molecule", "montana", "monument", "mosquito", "narrative", "nebula", "newsletter", "norwegian", "october", "ohio", "onlooker", "opulent", "orlando", "outfielder", "pacific", "pandemic", "pandora", "paperweight", "paragon", "paragraph", "paramount", "passenger", "pedigree", "pegasus", "penetrate", "perceptive", "performance", "pharmacy", "phonetic", "photograph", "pioneer", "pocketful", "politeness", "positive", "potato", "processor", "provincial", "proximate", "puberty", "publisher", "pyramid", "quantity", "racketeer", "rebellion", "recipe", "recover", "repellent", "replica", "reproduce", "resistor", "responsive", "retraction", "retrieval", "retrospect", "revenue", "revival", "revolver", "sandalwood", "sardonic", "saturday", "savagery", "scavenger", "sensation", "sociable", "souvenir", "specialist", "speculate", "stethoscope", "stupendous", "supportive", "surrender", "suspicious", "sympathy", "tambourine", "telephone", "therapist", "tobacco", "tolerance", "tomorrow", "torpedo", "tradition", "travesty", "trombonist", "truncated", "typewriter", "ultimate", "undaunted", "underfoot", "unicorn", "unify", "universe", "unravel", "upcoming", "vacancy", "vagabond", "vertigo", "virginia", "visitor", "vocalist", "voyager", "warranty", "waterloo", "whimsical", "wichita", "wilmington", "wyoming", "yesteryear", "yucatan"} 10 | 11 | // ErrRandomNotAvailable is returned when reading from random doesn't return enough bytes. 12 | var ErrRandomNotAvailable = errors.New("reading from random did not return required data") 13 | 14 | // Random returns random words from the list. 15 | func Random(length int) (words []string, err error) { 16 | numbers := make([]byte, length) 17 | var read int 18 | read, err = rand.Read(numbers) 19 | if err != nil { 20 | return 21 | } 22 | if read != length { 23 | err = ErrRandomNotAvailable 24 | return 25 | } 26 | words = PGP(numbers) 27 | return 28 | } 29 | 30 | // PGP returns strings from the PGP word list. 31 | func PGP(value []byte) (words []string) { 32 | words = make([]string, len(value)) 33 | for i, v := range value { 34 | words[i] = PGPAtIndex(i, v) 35 | } 36 | return 37 | } 38 | 39 | // PGPAtIndex returns a string from the PGP word list. 40 | // Index is the index of the item in the source list. 41 | // Value is the value of the byte in the source list. 42 | func PGPAtIndex(index int, value byte) (word string) { 43 | if index%2 == 0 { 44 | word = even[value] 45 | return 46 | } 47 | word = odd[value] 48 | return 49 | } 50 | -------------------------------------------------------------------------------- /chapter_3_applications/tcpcopy/wordlist/pgp_test.go: -------------------------------------------------------------------------------- 1 | package wordlist 2 | 3 | import "testing" 4 | 5 | func TestPGPWordSizes(t *testing.T) { 6 | if len(odd) != 256 { 7 | t.Errorf("expected odd word length 256, got %d", len(odd)) 8 | } 9 | if len(even) != 256 { 10 | t.Errorf("expected even word length 256, got %d", len(even)) 11 | } 12 | } 13 | 14 | func TestPGPArray(t *testing.T) { 15 | data := PGP([]byte{0, 0}) 16 | if data[0] != "aardvark" { 17 | t.Errorf("expected 'aardvark', got %s", data[0]) 18 | } 19 | if data[1] != "adroitness" { 20 | t.Errorf("expected 'adroitness', got %s", data[1]) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /chapter_3_applications/tls/cert.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIICqjCCAZICCQC73KlJ5zuLejANBgkqhkiG9w0BAQsFADAXMRUwEwYDVQQDDAxh 3 | ZHJpYW4ubG9jYWwwHhcNMTkwMzI0MTQxODI4WhcNMjAwMzIzMTQxODI4WjAXMRUw 4 | EwYDVQQDDAxhZHJpYW4ubG9jYWwwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK 5 | AoIBAQDR6cuQ74trzu+atUMeo/TojhYVvUq5n95eKAmfBSSkPYRP3q0xGzJ34gr2 6 | /aawKKxiPBq1pfki7rGD8cTGPJ7J2HJrHxA9EtGHYs52g31aAzPabF+p1W8QRpLo 7 | YSl5RVLrzqMz3ebVJDWqmN+jWPwWdO8x1PUJTkXHg1u/gzlt9Ej8S/5BpRXqDgnV 8 | edPmShqA6NuMP77U2p8fVdr3bx/5F++TsG5/9JSyJVt0hgoDsAAYBVnJU8i+hOBy 9 | 5LSphWHVJ1DEz9TrgMJ0+AnKHgUQFRD8vdtOzc/m85ALaclVWpgJZQ7ig08VC4Wg 10 | sTZP0XSHUOxTsBmA5HylxBvVuPqjAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAK/+ 11 | n1eDRHtDkArjSTMHEkQOaao9fXs3uUo1RVqPep5KqCJuf26YID4Il/itfSHoWe5N 12 | k2ZZqv51OiJRHxme9Eo+ws9jbfawfcYLlUi0DJmFdofF8Vxm5lU3rPg1uH/+C7gW 13 | qJ8cmN/cEqr923DO0uXZ0t+TqAwrspw5S/3vgGFTSWPC3LDUuNoUOyj5nf6Kt1I4 14 | 3yzf0kW0uMuRJ98b7C5VpgZZTAYAzGADs+2ePQWExpWt+WOo41jJUqRkfRbaRkmH 15 | jSU2FP2QTS50dwN8Xw9nXAt3M4LhFHeQGc4eIAr/EuLC5POJEPAPnrghegvTmRAa 16 | f46gnEiKycrgRi558ys= 17 | -----END CERTIFICATE----- 18 | -------------------------------------------------------------------------------- /chapter_3_applications/tls/cert.key: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDR6cuQ74trzu+a 3 | tUMeo/TojhYVvUq5n95eKAmfBSSkPYRP3q0xGzJ34gr2/aawKKxiPBq1pfki7rGD 4 | 8cTGPJ7J2HJrHxA9EtGHYs52g31aAzPabF+p1W8QRpLoYSl5RVLrzqMz3ebVJDWq 5 | mN+jWPwWdO8x1PUJTkXHg1u/gzlt9Ej8S/5BpRXqDgnVedPmShqA6NuMP77U2p8f 6 | Vdr3bx/5F++TsG5/9JSyJVt0hgoDsAAYBVnJU8i+hOBy5LSphWHVJ1DEz9TrgMJ0 7 | +AnKHgUQFRD8vdtOzc/m85ALaclVWpgJZQ7ig08VC4WgsTZP0XSHUOxTsBmA5Hyl 8 | xBvVuPqjAgMBAAECggEANBSRkRAdw03AXN4EHSIIrXozOImSdKOZhDriyDV6ddVd 9 | coOI7NoWl2/Z0JX2OVaeDnFjHxZizavzFw1xCM4Zh3LQUIuQd9anbbOyDe1puF97 10 | CgJBad2uhJc987HHnHL6s8685y1zDAbD2eO7+McnJFs2zWJMuVZWMb4BkW55XTH/ 11 | i7d/EYjb5quCh0EsSnVsbMrbQqB28K+jnoODMDdtaKxZHxTfF5KDyMMwuv1s5QUc 12 | S7DYd4RC1ZAR/Wx/3OlIDpm8Dz9jVcEx68jk/6zRyNusZAm5m7WJuQTlWxZY7JWH 13 | qcRgCbytvn73njQdaENWNkw/lrpD/IcSg1djScWRIQKBgQDz9BEzQsiitqSj1uDc 14 | DokU7LUTSbnO45aIjo8Pk4PmBDNfgKlcHzjiM/S7G0iWOj3X97zsPkXhI70njtw9 15 | iymF/j6HLpTNb1AInRQEw8mcObk1lhPBFYPR7zLS1fWWY5wYvWZWU6iXWfTDgZMr 16 | AwIGu99ZS6YvMBMGGQQN2B2BXQKBgQDcR2kJc780zGQnCY9nINfGIGZJ58c4KJTy 17 | AVRW0yhJwx0WaZbQLrRWz3IpWfY6QDAzEki9hI5nKqLTMQOoqLNRazDqsnPrYSo+ 18 | lSd0jCBeKvn4wLAPLifWEDWmOPw6dJIoZnHQVHa4DwaKPitGffu5uAITdPWH9xdh 19 | 2wAPe9yr/wKBgQDbbizOhAK+QTTkXlKR/c0V8jiJ6eXlD1eG61wJMbIRgrPG8o7j 20 | 2v2qkftPFaJ75lVcXm0jvnvSn/fzjg/Sl8s4yFDr/V1BUB+YL9oi0w0UpeNPLPgF 21 | 1FlmyxZXCLdHl79/jVVwzv01bme4N2Pt6122OuUSh/YqOb7S3EuGPF+FzQKBgDgu 22 | cmi5v7usoaKfOpb39nCO1OfmmJYHK63pjuQyKOVfQSz1M0TXlKFLeXd4fWdv9Zsg 23 | /7Jue49ufWbV6nP6yvlqGGjTqOMQviCGFDESiDLcTTMXmg/VGs3rBnPlttcF4j0x 24 | m7ehw0HAe0+I+aqZ+Tjt6IGj4/eQmsMXgxVv5uFrAoGBALF+f6jPKpRlXMUBZDmk 25 | biHF91y2QRzrBoiGrFtMN6obeNHIFFR+vwqgXxspSPov1evAM+yQjH05Oof/Pcgu 26 | l7INOLtlvJRj3i5zo40qdlYtEh50djqwVtDrEKv1lJ9K6eEV79ZE//NtkFRFBnAB 27 | KGUOg9vOFzIPUfSctCHpK2j4 28 | -----END PRIVATE KEY----- 29 | -------------------------------------------------------------------------------- /chapter_3_applications/tls/client/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "crypto/tls" 5 | "crypto/x509" 6 | "fmt" 7 | "io/ioutil" 8 | "net/http" 9 | "os" 10 | ) 11 | 12 | func main() { 13 | sc, err := ioutil.ReadFile("../cert.crt") 14 | if err != nil { 15 | fmt.Println(err) 16 | os.Exit(1) 17 | } 18 | roots := x509.NewCertPool() 19 | roots.AppendCertsFromPEM([]byte(sc)) 20 | tr := &http.Transport{ 21 | TLSClientConfig: &tls.Config{ 22 | RootCAs: roots, 23 | }, 24 | } 25 | c := &http.Client{Transport: tr} 26 | resp, err := c.Get("https://adrian.local:8443") 27 | if err != nil { 28 | fmt.Println(err) 29 | os.Exit(1) 30 | } 31 | body, err := ioutil.ReadAll(resp.Body) 32 | if err != nil { 33 | fmt.Println(err) 34 | os.Exit(1) 35 | } 36 | fmt.Println(string(body)) 37 | } 38 | -------------------------------------------------------------------------------- /chapter_3_applications/tls/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "net/http" 4 | 5 | func main() { 6 | okh := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 7 | w.Write([]byte("OK")) 8 | }) 9 | http.ListenAndServeTLS(":443", "cert.crt", "cert.key", okh) 10 | } 11 | -------------------------------------------------------------------------------- /chapter_3_applications/udpclock/client/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "fmt" 7 | "net" 8 | "os" 9 | "os/signal" 10 | "syscall" 11 | "time" 12 | ) 13 | 14 | func main() { 15 | conn, err := net.ListenMulticastUDP("udp", nil, &net.UDPAddr{ 16 | IP: net.IPv4(224, 0, 0, 5), 17 | Port: 3002, 18 | }) 19 | if err != nil { 20 | fmt.Printf("error starting to listen: %v\n", err) 21 | os.Exit(1) 22 | } 23 | 24 | go func() { 25 | prefix := []byte("time:") 26 | for { 27 | data := make([]byte, 1024) 28 | fmt.Println("Reading from socket") 29 | _, remoteAddr, err := conn.ReadFromUDP(data) 30 | fmt.Println("Read socket") 31 | if err != nil { 32 | fmt.Printf("error reading from UDP: %v\n", err) 33 | continue 34 | } 35 | // Check that the data is something we're interested in. 36 | if bytes.HasPrefix(data, prefix) { 37 | // Get the time from the server. 38 | // Skip the prefix ("time:") 39 | suffix := data[len(prefix):] 40 | // Read the int64 41 | ns := int64(binary.LittleEndian.Uint64(suffix)) 42 | fmt.Println(remoteAddr, time.Unix(0, ns), ns) 43 | } 44 | } 45 | }() 46 | 47 | sigs := make(chan os.Signal) 48 | signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) 49 | <-sigs 50 | fmt.Println("Stopped.") 51 | } 52 | -------------------------------------------------------------------------------- /chapter_3_applications/udpclock/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '2' 2 | services: 3 | client1: 4 | image: golang:latest 5 | volumes: 6 | - ./client:go/src/github.com/PacktPublishing/Hands-On-Networking-with-Go-Programming/udpclock/client 7 | command: 'bash -c "cd go/src/github.com/PacktPublishing/Hands-On-Networking-with-Go-Programming/udpclock/client && go run main.go"' 8 | client2: 9 | image: golang:latest 10 | volumes: 11 | - ./client:go/src/github.com/PacktPublishing/Hands-On-Networking-with-Go-Programming/udpclock/client 12 | command: 'bash -c "cd go/src/github.com/PacktPublishing/Hands-On-Networking-with-Go-Programming/udpclock/client && go run main.go"' 13 | server: 14 | image: golang:latest 15 | volumes: 16 | - ./server:go/src/github.com/PacktPublishing/Hands-On-Networking-with-Go-Programming/udpclock/server 17 | command: 'bash -c "cd go/src/github.com/PacktPublishing/Hands-On-Networking-with-Go-Programming/udpclock/server && go run main.go"' -------------------------------------------------------------------------------- /chapter_3_applications/udpclock/server/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/binary" 5 | "fmt" 6 | "net" 7 | "os" 8 | "os/signal" 9 | "syscall" 10 | "time" 11 | ) 12 | 13 | func main() { 14 | // Start the clock ticking. 15 | clock := make(chan time.Time) 16 | go func() { 17 | for { 18 | clock <- time.Now() 19 | time.Sleep(time.Second) 20 | } 21 | }() 22 | 23 | sigs := make(chan os.Signal) 24 | signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) 25 | 26 | loop: 27 | for { 28 | select { 29 | case <-sigs: 30 | fmt.Println("shutting down...") 31 | break loop 32 | case t := <-clock: 33 | conn, err := net.DialUDP("udp", nil, &net.UDPAddr{ 34 | IP: net.IPv4(224, 0, 0, 5), // OSPF routing protocol address. 35 | Port: 3002, 36 | }) 37 | if err != nil { 38 | fmt.Printf("error creating broadcast socket: %v\n", err) 39 | os.Exit(1) 40 | } 41 | fmt.Println("Writing to socket") 42 | // We need 5 bytes for "time:" and then another 8 bytes for the int64. 43 | data := []byte("time:") 44 | ns := make([]byte, 8) 45 | binary.LittleEndian.PutUint64(ns, uint64(t.UnixNano())) 46 | _, err = conn.Write(append(data, ns...)) 47 | if err != nil { 48 | fmt.Printf("error writing header to the socket: %v\n", err) 49 | } 50 | fmt.Println("Closing socket") 51 | conn.Close() 52 | } 53 | } 54 | fmt.Println("Complete.") 55 | } 56 | -------------------------------------------------------------------------------- /chapter_4_distributed_applications/flowlog/compare/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | 7 | "github.com/PacktPublishing/Hands-On-Networking-with-Go-Programming/chapter_4_distributed_applications/flowlog" 8 | ) 9 | 10 | var history = []string{ 11 | "2 123456789010 eni-abc123de 172.31.16.139 172.31.16.21 20641 22 6 20 4249 1418530010 1418530070 ACCEPT OK", 12 | "2 123456789010 eni-abc123de 172.31.9.69 172.31.9.12 49761 3389 6 20 4249 1418530010 1418530070 REJECT OK", 13 | "2 123456789010 eni-1235b8ca 203.0.113.12 172.31.16.139 0 0 1 4 336 1432917027 1432917142 ACCEPT OK", 14 | "2 123456789010 eni-1235b8ca 172.31.16.139 203.0.113.12 0 0 1 4 336 1432917094 1432917142 REJECT OK", 15 | } 16 | 17 | func ipAddressAndPort(ip net.IP, port int64) string { 18 | return fmt.Sprintf("%v:%v", ip, port) 19 | } 20 | 21 | // MB is one megabyte of data. 22 | const MB = 1024 * 1024 23 | 24 | func isMoreThan1MBEgress(subnet *net.IPNet, r flowlog.Record) bool { 25 | return subnet.Contains(r.SourceAddress) && r.Bytes > MB 26 | } 27 | 28 | func neverConnectedBefore(subnet *net.IPNet, pastOutboundConnections map[string]struct{}, r flowlog.Record) bool { 29 | if !subnet.Contains(r.SourceAddress) { 30 | return false 31 | } 32 | _, connected := pastOutboundConnections[ipAddressAndPort(r.DestinationAddress, r.DestinationPort)] 33 | return !connected 34 | } 35 | 36 | func main() { 37 | // Get the old data. 38 | var records []flowlog.Record 39 | for _, l := range history { 40 | r, err := flowlog.Parse(l) 41 | if err != nil { 42 | fmt.Printf("Error parsing log: %v\n", err) 43 | } 44 | records = append(records, r) 45 | } 46 | 47 | // Create a history of past connections. 48 | pastOutboundConnections := make(map[string]struct{}) 49 | for _, r := range records { 50 | pastOutboundConnections[ipAddressAndPort(r.DestinationAddress, r.DestinationPort)] = struct{}{} 51 | } 52 | 53 | // Define local subnet. 54 | _, subnet, err := net.ParseCIDR("172.31.0.0/16") 55 | if err != nil { 56 | fmt.Printf("Error parsing subnet: %v\n", err) 57 | return 58 | } 59 | 60 | // Parse the new data. 61 | newData := []string{ 62 | // New IP address 63 | "2 123456789010 eni-abc123de 172.31.16.139 192.168.16.21 9999 443 6 20 4249 1418530010 1418540070 REJECT OK", 64 | // > 1MB egress 65 | "2 123456789010 eni-abc123de 172.31.16.139 192.168.16.21 9999 443 6 20 1048577 1418530010 1418540070 REJECT OK", 66 | // Nothing unusual 67 | "2 123456789010 eni-abc123de 172.31.16.139 192.168.16.21 9999 443 6 20 1024 1418530010 1418540070 REJECT OK", 68 | } 69 | 70 | for _, d := range newData { 71 | new, err := flowlog.Parse(d) 72 | if err != nil { 73 | fmt.Printf("Error parsing new log: %v\n", err) 74 | continue 75 | } 76 | if isMoreThan1MBEgress(subnet, new) { 77 | fmt.Printf("> 1MB egress from %v to %v\n", new.SourceAddress, ipAddressAndPort(new.DestinationAddress, new.DestinationPort)) 78 | } 79 | if neverConnectedBefore(subnet, pastOutboundConnections, new) { 80 | fmt.Printf("New connection from %v to %v\n", new.SourceAddress, ipAddressAndPort(new.DestinationAddress, new.DestinationPort)) 81 | } 82 | pastOutboundConnections[ipAddressAndPort(new.DestinationAddress, new.DestinationPort)] = struct{}{} 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /chapter_4_distributed_applications/flowlog/parse.go: -------------------------------------------------------------------------------- 1 | package flowlog 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | "strconv" 7 | "strings" 8 | "time" 9 | ) 10 | 11 | const versionFieldIndex = 0 12 | const accountIDFieldIndex = 1 13 | const interfaceIDFieldIndex = 2 14 | const srcAddrFieldIndex = 3 15 | const dstAddrFieldIndex = 4 16 | const srcPortFieldIndex = 5 17 | const dstPortFieldIndex = 6 18 | const protocolFieldIndex = 7 19 | const packetsFieldIndex = 8 20 | const bytesFieldIndex = 9 21 | const startFieldIndex = 10 22 | const endFieldIndex = 11 23 | const actionFieldIndex = 12 24 | const logStatusFieldIndex = 13 25 | 26 | const fieldCount = 14 27 | 28 | // Parse the AWS flow log Record. 29 | func Parse(s string) (Record, error) { 30 | split := strings.SplitN(s, " ", fieldCount) 31 | if len(split) != fieldCount { 32 | return Record{}, fmt.Errorf("flowlog: invalid field count: %v", len(split)) 33 | } 34 | startSeconds, err := strconv.ParseInt(split[startFieldIndex], 10, 64) 35 | if err != nil { 36 | return Record{}, fmt.Errorf("flowlog: invalid start time: '%v'", split[startFieldIndex]) 37 | } 38 | endSeconds, err := strconv.ParseInt(split[endFieldIndex], 10, 64) 39 | if err != nil { 40 | return Record{}, fmt.Errorf("flowlog: invalid end time: '%v'", split[endFieldIndex]) 41 | } 42 | 43 | return Record{ 44 | Version: split[versionFieldIndex], 45 | AccountID: split[accountIDFieldIndex], 46 | InterfaceID: split[interfaceIDFieldIndex], 47 | SourceAddress: net.ParseIP(split[srcAddrFieldIndex]), 48 | DestinationAddress: net.ParseIP(split[dstAddrFieldIndex]), 49 | SourcePort: parseIntOrZero(split[srcPortFieldIndex]), 50 | DestinationPort: parseIntOrZero(split[dstPortFieldIndex]), 51 | Protocol: split[protocolFieldIndex], 52 | Packets: parseIntOrZero(split[packetsFieldIndex]), 53 | Bytes: parseIntOrZero(split[bytesFieldIndex]), 54 | Start: time.Unix(startSeconds, 0), 55 | End: time.Unix(endSeconds, 0), 56 | Action: Action(split[actionFieldIndex]), 57 | Status: Status(split[logStatusFieldIndex]), 58 | }, nil 59 | } 60 | 61 | func parseIntOrZero(s string) (i int64) { 62 | i, _ = strconv.ParseInt(s, 10, 64) 63 | return i 64 | } 65 | 66 | // Action of the Record. 67 | type Action string 68 | 69 | const ( 70 | // ActionNone is the action when no data is present. 71 | ActionNone Action = "-" 72 | // ActionAccept is the action when it the connection was accepted. 73 | ActionAccept Action = "ACCEPT" 74 | // ActionReject is the action when it the connection was rejected. 75 | ActionReject Action = "REJECT" 76 | ) 77 | 78 | // Status of the Record. 79 | type Status string 80 | 81 | const ( 82 | // StatusOK is a successful log. 83 | StatusOK Status = "OK" 84 | // StatusNoData is when the log has no data in the collection period. 85 | StatusNoData Status = "NODATA" 86 | // StatusSkipData is the status when records were skipped in the capture window. 87 | StatusSkipData Status = "SKIPDATA" 88 | ) 89 | 90 | // Record represents a network flow in your flow log. 91 | type Record struct { 92 | // Version of the VPC Flow Logs. 93 | Version string 94 | // AccountID for the flow log. 95 | AccountID string 96 | // InterfaceID of the network interface for which the traffic is recorded. 97 | InterfaceID string 98 | // SourceAddress is an IPv4 or IPv6 address. The IPv4 address of the network interface is always its private IPv4 address. 99 | SourceAddress net.IP 100 | // DestinationAddress is an IPv4 or IPv6 address. The IPv4 address of the network interface is always its private IPv4 address. 101 | DestinationAddress net.IP 102 | // SourcePort of the traffic. 103 | SourcePort int64 104 | // DestinationPort of the traffic. 105 | DestinationPort int64 106 | // Protocol is the IANA protocol number of the traffic. For more information, see Assigned Internet Protocol Numbers. 107 | Protocol string 108 | // Packets is the number of packets transferred during the capture window. 109 | Packets int64 110 | // Bytes is the number of bytes transferred during the capture window. 111 | Bytes int64 112 | // Start time of the start of the capture window. 113 | Start time.Time 114 | // End time of the end of the capture window. 115 | End time.Time 116 | // Action associated with the traffic - ACCEPT: The recorded traffic was permitted by the security groups or network ACLs. or REJECT: The recorded traffic was not permitted by the security groups or network ACLs. 117 | Action Action 118 | // Status of the flow log: OK: Data is logging normally to the chosen destinations. NODATA: There was no network traffic to or from the network interface during the capture window. SKIPDATA: Some flow log records were skipped during the capture window. This may be because of an internal capacity constraint, or an internal error. 119 | Status Status 120 | } 121 | -------------------------------------------------------------------------------- /chapter_4_distributed_applications/flowlog/parse_test.go: -------------------------------------------------------------------------------- 1 | package flowlog 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | "reflect" 7 | "testing" 8 | "time" 9 | ) 10 | 11 | func TestParse(t *testing.T) { 12 | tests := []struct { 13 | name string 14 | input string 15 | expected Record 16 | }{ 17 | { 18 | name: "SSH traffic (destination port 22, TCP protocol) to network interface eni-abc123de in account 123456789010 was allowed", 19 | input: "2 123456789010 eni-abc123de 172.31.16.139 172.31.16.21 20641 22 6 20 4249 1418530010 1418530070 ACCEPT OK", 20 | expected: Record{ 21 | Version: "2", 22 | AccountID: "123456789010", 23 | InterfaceID: "eni-abc123de", 24 | SourceAddress: net.ParseIP("172.31.16.139"), 25 | DestinationAddress: net.ParseIP("172.31.16.21"), 26 | SourcePort: 20641, 27 | DestinationPort: 22, 28 | Protocol: "6", 29 | Packets: 20, 30 | Bytes: 4249, 31 | Start: time.Unix(1418530010, 0), 32 | End: time.Unix(1418530070, 0), 33 | Action: ActionAccept, 34 | Status: StatusOK, 35 | }, 36 | }, 37 | { 38 | name: "RDP traffic (destination port 3389, TCP protocol) to network interface eni-abc123de in account 123456789010 was rejected", 39 | input: "2 123456789010 eni-abc123de 172.31.9.69 172.31.9.12 49761 3389 6 20 4249 1418530010 1418530070 REJECT OK", 40 | expected: Record{ 41 | Version: "2", 42 | AccountID: "123456789010", 43 | InterfaceID: "eni-abc123de", 44 | SourceAddress: net.ParseIP("172.31.9.69"), 45 | DestinationAddress: net.ParseIP("172.31.9.12"), 46 | SourcePort: 49761, 47 | DestinationPort: 3389, 48 | Protocol: "6", 49 | Packets: 20, 50 | Bytes: 4249, 51 | Start: time.Unix(1418530010, 0), 52 | End: time.Unix(1418530070, 0), 53 | Action: ActionReject, 54 | Status: StatusOK, 55 | }, 56 | }, 57 | { 58 | name: "no data in window", 59 | input: "2 123456789010 eni-1a2b3c4d - - - - - - - 1431280876 1431280934 - NODATA", 60 | expected: Record{ 61 | Version: "2", 62 | AccountID: "123456789010", 63 | InterfaceID: "eni-1a2b3c4d", 64 | SourceAddress: nil, 65 | DestinationAddress: nil, 66 | SourcePort: 0, 67 | DestinationPort: 0, 68 | Protocol: "-", 69 | Packets: 0, 70 | Bytes: 0, 71 | Start: time.Unix(1431280876, 0), 72 | End: time.Unix(1431280934, 0), 73 | Action: ActionNone, 74 | Status: StatusNoData, 75 | }, 76 | }, 77 | { 78 | name: "data in window skipped", 79 | input: "2 123456789010 eni-4b118871 - - - - - - - 1431280876 1431280934 - SKIPDATA", 80 | expected: Record{ 81 | Version: "2", 82 | AccountID: "123456789010", 83 | InterfaceID: "eni-4b118871", 84 | SourceAddress: nil, 85 | DestinationAddress: nil, 86 | SourcePort: 0, 87 | DestinationPort: 0, 88 | Protocol: "-", 89 | Packets: 0, 90 | Bytes: 0, 91 | Start: time.Unix(1431280876, 0), 92 | End: time.Unix(1431280934, 0), 93 | Action: ActionNone, 94 | Status: StatusSkipData, 95 | }, 96 | }, 97 | { 98 | name: "ping request accept", 99 | input: "2 123456789010 eni-1235b8ca 203.0.113.12 172.31.16.139 0 0 1 4 336 1432917027 1432917142 ACCEPT OK", 100 | expected: Record{ 101 | Version: "2", 102 | AccountID: "123456789010", 103 | InterfaceID: "eni-1235b8ca", 104 | SourceAddress: net.ParseIP("203.0.113.12"), 105 | DestinationAddress: net.ParseIP("172.31.16.139"), 106 | SourcePort: 0, 107 | DestinationPort: 0, 108 | Protocol: "1", 109 | Packets: 4, 110 | Bytes: 336, 111 | Start: time.Unix(1432917027, 0), 112 | End: time.Unix(1432917142, 0), 113 | Action: ActionAccept, 114 | Status: StatusOK, 115 | }, 116 | }, 117 | { 118 | name: "ping response reject", 119 | input: "2 123456789010 eni-1235b8ca 172.31.16.139 203.0.113.12 0 0 1 4 336 1432917094 1432917142 REJECT OK", 120 | expected: Record{ 121 | Version: "2", 122 | AccountID: "123456789010", 123 | InterfaceID: "eni-1235b8ca", 124 | SourceAddress: net.ParseIP("172.31.16.139"), 125 | DestinationAddress: net.ParseIP("203.0.113.12"), 126 | SourcePort: 0, 127 | DestinationPort: 0, 128 | Protocol: "1", 129 | Packets: 4, 130 | Bytes: 336, 131 | Start: time.Unix(1432917094, 0), 132 | End: time.Unix(1432917142, 0), 133 | Action: ActionReject, 134 | Status: StatusOK, 135 | }, 136 | }, 137 | } 138 | for _, test := range tests { 139 | test := test 140 | t.Run(test.name, func(t *testing.T) { 141 | t.Parallel() 142 | actual, err := Parse(test.input) 143 | if err != nil { 144 | t.Errorf("unexpected error: %v", err) 145 | } 146 | if !reflect.DeepEqual(test.expected, actual) { 147 | t.Errorf("expected:\n%+v\ngot:\n%+v", test.expected, actual) 148 | } 149 | }) 150 | } 151 | } 152 | 153 | func TestParseErrors(t *testing.T) { 154 | tests := []struct { 155 | name string 156 | input string 157 | expected error 158 | }{ 159 | { 160 | name: "empty", 161 | input: "", 162 | expected: fmt.Errorf("flowlog: invalid field count: 1"), 163 | }, 164 | { 165 | name: "invalid start time", 166 | input: "2 123456789010 eni-abc123de 172.31.9.69 172.31.9.12 49761 3389 6 20 4249 not_time 1418530070 REJECT OK", 167 | expected: fmt.Errorf("flowlog: invalid start time: 'not_time'"), 168 | }, 169 | { 170 | name: "invalid end time", 171 | input: "2 123456789010 eni-abc123de 172.31.9.69 172.31.9.12 49761 3389 6 20 4249 1418530070 not_time REJECT OK", 172 | expected: fmt.Errorf("flowlog: invalid end time: 'not_time'"), 173 | }, 174 | } 175 | for _, test := range tests { 176 | test := test 177 | t.Run(test.name, func(t *testing.T) { 178 | t.Parallel() 179 | _, err := Parse(test.input) 180 | if err == nil { 181 | t.Error("unexpected success") 182 | return 183 | } 184 | if test.expected.Error() != err.Error() { 185 | t.Errorf("expected:\n%s\ngot:\n%s", test.expected, err.Error()) 186 | } 187 | }) 188 | } 189 | } 190 | -------------------------------------------------------------------------------- /chapter_4_distributed_applications/jwtexample/client/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | "net/http" 7 | "os" 8 | ) 9 | 10 | func main() { 11 | username := "user1@example.com" 12 | pwd := "BadPassword1" 13 | r, err := http.NewRequest("GET", "http://localhost:8000", nil) 14 | if err != nil { 15 | fmt.Println("error create request to issuer:", err) 16 | os.Exit(1) 17 | } 18 | r.SetBasicAuth(username, pwd) 19 | resp, err := http.DefaultClient.Do(r) 20 | if err != nil { 21 | fmt.Println("error getting token from issuer:", err) 22 | os.Exit(1) 23 | } 24 | if resp.StatusCode != http.StatusOK { 25 | fmt.Println("unexpeted status code from issuer:", resp.StatusCode) 26 | os.Exit(1) 27 | } 28 | token, err := ioutil.ReadAll(resp.Body) 29 | if err != nil { 30 | fmt.Println("error reading token from issuer:", err) 31 | os.Exit(1) 32 | } 33 | fmt.Println("Got token from issuer. Making request to server.") 34 | 35 | // Now lets make a request to the server. 36 | sr, err := http.NewRequest("GET", "http://localhost:8001/whoami", nil) 37 | if err != nil { 38 | fmt.Println("error creating request to server:", err) 39 | os.Exit(1) 40 | } 41 | sr.Header.Set("Authorization", "Bearer "+string(token)) 42 | resp, err = http.DefaultClient.Do(sr) 43 | if err != nil { 44 | fmt.Println("error getting response from server:", err) 45 | os.Exit(1) 46 | } 47 | if resp.StatusCode != http.StatusOK { 48 | fmt.Println("unexpeted status code from server:", resp.StatusCode) 49 | os.Exit(1) 50 | } 51 | whoami, err := ioutil.ReadAll(resp.Body) 52 | if err != nil { 53 | fmt.Println("error reading whoami response from server:", err) 54 | os.Exit(1) 55 | } 56 | fmt.Println("Made request to server.") 57 | fmt.Println("whoami:", string(whoami)) 58 | } 59 | -------------------------------------------------------------------------------- /chapter_4_distributed_applications/jwtexample/issuer/example_private.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEpAIBAAKCAQEA1IE11W+JDl1+JhB5g2V3Ux/284zkyAN08s6OLMCHVIp5/7KA 3 | WDTe23voRiKaPKDWNlh9gUZqkgKBb4kxhI5ctAgy6p22hPz6gsz82/0jMvJKojC4 4 | Duv0vAc8r/xS/Uwq6xkBWs8KTqeQhkqInsthzj92FzHukQc3UUkrTq8Iu3nVTBzg 5 | oBXZOvBxFW2swt/9tE4zhtyhAgDlnrpjVP6Va0MBJu+6bA1R18AKYSK9uNpMkof9 6 | ltk44NstKgO5YpV9BdyNBlnU6CfpsoeWdveCJmSEPzv7YCHnQZdijg0DKCITZQxr 7 | VVKUh561x0b5242kbWJOCUyLBOj6nkYXyF+qrQIDAQABAoIBAQCnN22XIAcnSKZl 8 | aX1UydkVjgeTKoE0apPyJFt4F5/mBHlvnZSk1CWxbFUgK0ZXAvDNHuDTgweFEXes 9 | vrY6apPEDteSCrx+9Vpi5s7qhMzX4BSef9u10jJoawF0MgdTzkXPbYPFYznnHq/5 10 | HFlZKw0xcHqKUf46HQWIbx0m81DZw9vEtofBsNNvNBGp7GtbvBhMQoFdMHUsbD2N 11 | Yf6unJb4KXixwtVXma6Wy2BQbJj1ifQ+xmZFak/130w1+64OY9+QMUa8zNKK2OHO 12 | iVK4BrPYk3Vt49XsMRTLB6h3MDXvcvQ66x7jY8RunP1jr9bD8eK3ZX7uO6uyCa4Q 13 | iNafQ10BAoGBAO9HWhYR2GAxVeIekkf1UwC6dc7MEioeIhxqP9rekEurVZRh42ko 14 | zz5yD07CUEEz/iWZ+pqk/XYNTBv9si3PLqadN1b+o7XOgm67rVyp69rjJA2eKrlu 15 | xOyMfXoeEi2CCLbHi7MTb6EdLk4MAjHMcFI6VOBt4bpvQiPQUBFpXEDhAoGBAONa 16 | 4IiwxIf6uUg3MRF248VYoFcmObLh1n75K2UGkqMzFUNA6lPaDreRhhXmJGrbHz0O 17 | wjMZ9zc799HVnZr5YpQbidMGc+S0LrmqAI1zINIvpZ9RgYwMEzy/5qGLpYFt3Gx7 18 | Mal7SdaVDdPbVLG4tzSqm3/0svQtxoeQ6utbGgdNAoGBAI4ZSZ6hqmY15lMK5MRn 19 | JIviL+RHvOHWU1ucnZ9VXUwSzBf6qhrhaXIkOoMDUrXmMqAR+YmtQfjBnNliqFYc 20 | HBBGfX7kakSmBz/LpQDKyI6NJfQQYj8NUVVJeZr0EMeF2bbyejw25qw/sCgZaZQ5 21 | XNr4WT+PAea9/AFYzLQKZgcBAoGAY5g6xgZRgZPWuIjc6N6g9qFVU/f9zJvb37F9 22 | TfssH2vQQ67bN7JNQiLwjwVLLLginheqAMK+JicR74zZRrs6cNEDdjrcZ/J6iYCs 23 | T0qAtTKEJh+JVXUwtCsId/n5nZInvinVXn4QoXyYGxd4qYXWU67tAYeLISYwUtCr 24 | 6D/3Tf0CgYAm9LOsTzyAbrNExdM5Aj64R23DWaiH3q91z3pBB9wo99gcnCcdkm7n 25 | 2XWOLf3/103MaMy+TckAWIRdr3CLLuc4LevjsdM2T66wXDU9HoqVPjepiPYfzAq0 26 | 6D5hL7RE6SCte1/u4QNKfVL8smZg06joL2PVSN0IXG4VjzoJEJjFCg== 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /chapter_4_distributed_applications/jwtexample/issuer/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "crypto/rsa" 5 | "fmt" 6 | "net/http" 7 | "os" 8 | "time" 9 | 10 | "github.com/gofrs/uuid" 11 | 12 | "github.com/dgrijalva/jwt-go" 13 | ) 14 | 15 | const privateKeyPEM = `-----BEGIN RSA PRIVATE KEY----- 16 | MIIEpAIBAAKCAQEA1IE11W+JDl1+JhB5g2V3Ux/284zkyAN08s6OLMCHVIp5/7KA 17 | WDTe23voRiKaPKDWNlh9gUZqkgKBb4kxhI5ctAgy6p22hPz6gsz82/0jMvJKojC4 18 | Duv0vAc8r/xS/Uwq6xkBWs8KTqeQhkqInsthzj92FzHukQc3UUkrTq8Iu3nVTBzg 19 | oBXZOvBxFW2swt/9tE4zhtyhAgDlnrpjVP6Va0MBJu+6bA1R18AKYSK9uNpMkof9 20 | ltk44NstKgO5YpV9BdyNBlnU6CfpsoeWdveCJmSEPzv7YCHnQZdijg0DKCITZQxr 21 | VVKUh561x0b5242kbWJOCUyLBOj6nkYXyF+qrQIDAQABAoIBAQCnN22XIAcnSKZl 22 | aX1UydkVjgeTKoE0apPyJFt4F5/mBHlvnZSk1CWxbFUgK0ZXAvDNHuDTgweFEXes 23 | vrY6apPEDteSCrx+9Vpi5s7qhMzX4BSef9u10jJoawF0MgdTzkXPbYPFYznnHq/5 24 | HFlZKw0xcHqKUf46HQWIbx0m81DZw9vEtofBsNNvNBGp7GtbvBhMQoFdMHUsbD2N 25 | Yf6unJb4KXixwtVXma6Wy2BQbJj1ifQ+xmZFak/130w1+64OY9+QMUa8zNKK2OHO 26 | iVK4BrPYk3Vt49XsMRTLB6h3MDXvcvQ66x7jY8RunP1jr9bD8eK3ZX7uO6uyCa4Q 27 | iNafQ10BAoGBAO9HWhYR2GAxVeIekkf1UwC6dc7MEioeIhxqP9rekEurVZRh42ko 28 | zz5yD07CUEEz/iWZ+pqk/XYNTBv9si3PLqadN1b+o7XOgm67rVyp69rjJA2eKrlu 29 | xOyMfXoeEi2CCLbHi7MTb6EdLk4MAjHMcFI6VOBt4bpvQiPQUBFpXEDhAoGBAONa 30 | 4IiwxIf6uUg3MRF248VYoFcmObLh1n75K2UGkqMzFUNA6lPaDreRhhXmJGrbHz0O 31 | wjMZ9zc799HVnZr5YpQbidMGc+S0LrmqAI1zINIvpZ9RgYwMEzy/5qGLpYFt3Gx7 32 | Mal7SdaVDdPbVLG4tzSqm3/0svQtxoeQ6utbGgdNAoGBAI4ZSZ6hqmY15lMK5MRn 33 | JIviL+RHvOHWU1ucnZ9VXUwSzBf6qhrhaXIkOoMDUrXmMqAR+YmtQfjBnNliqFYc 34 | HBBGfX7kakSmBz/LpQDKyI6NJfQQYj8NUVVJeZr0EMeF2bbyejw25qw/sCgZaZQ5 35 | XNr4WT+PAea9/AFYzLQKZgcBAoGAY5g6xgZRgZPWuIjc6N6g9qFVU/f9zJvb37F9 36 | TfssH2vQQ67bN7JNQiLwjwVLLLginheqAMK+JicR74zZRrs6cNEDdjrcZ/J6iYCs 37 | T0qAtTKEJh+JVXUwtCsId/n5nZInvinVXn4QoXyYGxd4qYXWU67tAYeLISYwUtCr 38 | 6D/3Tf0CgYAm9LOsTzyAbrNExdM5Aj64R23DWaiH3q91z3pBB9wo99gcnCcdkm7n 39 | 2XWOLf3/103MaMy+TckAWIRdr3CLLuc4LevjsdM2T66wXDU9HoqVPjepiPYfzAq0 40 | 6D5hL7RE6SCte1/u4QNKfVL8smZg06joL2PVSN0IXG4VjzoJEJjFCg== 41 | -----END RSA PRIVATE KEY-----` 42 | 43 | func main() { 44 | pk, err := jwt.ParseRSAPrivateKeyFromPEM([]byte(privateKeyPEM)) 45 | if err != nil { 46 | fmt.Println("failed to parse key:", err) 47 | os.Exit(1) 48 | } 49 | 50 | h := Handler{ 51 | key: pk, 52 | // Using plain text passwords is a bad idea. Don't do it. :) 53 | users: map[string]string{ 54 | "user1@example.com": "BadPassword1", 55 | "user2@example.com": "BadPassword2", 56 | }, 57 | } 58 | 59 | http.ListenAndServe(":8000", h) 60 | } 61 | 62 | type Handler struct { 63 | key *rsa.PrivateKey 64 | users map[string]string 65 | } 66 | 67 | func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { 68 | username, password, ok := r.BasicAuth() 69 | if !ok { 70 | http.Error(w, "unauthorized", http.StatusUnauthorized) 71 | return 72 | } 73 | if ap, userExists := h.users[username]; !userExists || ap != password { 74 | http.Error(w, "unauthorized", http.StatusUnauthorized) 75 | return 76 | } 77 | 78 | t := jwt.NewWithClaims(jwt.SigningMethodRS256, jwt.StandardClaims{ 79 | Id: uuid.Must(uuid.NewV4()).String(), 80 | Audience: "any", 81 | IssuedAt: time.Now().UTC().Unix(), 82 | ExpiresAt: time.Now().UTC().Add(time.Hour).Unix(), 83 | Issuer: "my_issuer", 84 | Subject: username, 85 | }) 86 | 87 | s, err := t.SignedString(h.key) 88 | if err != nil { 89 | http.Error(w, "internal error", http.StatusInternalServerError) 90 | return 91 | } 92 | w.Write([]byte(s)) 93 | } 94 | -------------------------------------------------------------------------------- /chapter_4_distributed_applications/jwtexample/server/example_public.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PUBLIC KEY----- 2 | MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1IE11W+JDl1+JhB5g2V3 3 | Ux/284zkyAN08s6OLMCHVIp5/7KAWDTe23voRiKaPKDWNlh9gUZqkgKBb4kxhI5c 4 | tAgy6p22hPz6gsz82/0jMvJKojC4Duv0vAc8r/xS/Uwq6xkBWs8KTqeQhkqInsth 5 | zj92FzHukQc3UUkrTq8Iu3nVTBzgoBXZOvBxFW2swt/9tE4zhtyhAgDlnrpjVP6V 6 | a0MBJu+6bA1R18AKYSK9uNpMkof9ltk44NstKgO5YpV9BdyNBlnU6CfpsoeWdveC 7 | JmSEPzv7YCHnQZdijg0DKCITZQxrVVKUh561x0b5242kbWJOCUyLBOj6nkYXyF+q 8 | rQIDAQAB 9 | -----END PUBLIC KEY----- 10 | -------------------------------------------------------------------------------- /chapter_4_distributed_applications/jwtexample/server/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "crypto/rsa" 6 | "fmt" 7 | "io/ioutil" 8 | "net/http" 9 | "os" 10 | "strings" 11 | 12 | "github.com/dgrijalva/jwt-go" 13 | ) 14 | 15 | func main() { 16 | b, err := ioutil.ReadFile("example_public.pem") 17 | if err != nil { 18 | fmt.Println("failed to read key:", err) 19 | os.Exit(1) 20 | } 21 | pk, err := jwt.ParseRSAPublicKeyFromPEM(b) 22 | if err != nil { 23 | fmt.Println("failed to parse key:", err) 24 | os.Exit(1) 25 | } 26 | 27 | v := JWTValidator{ 28 | key: pk, 29 | next: WhoAmI{}, 30 | } 31 | 32 | err = http.ListenAndServe(":8001", v) 33 | if err != nil { 34 | fmt.Println(err) 35 | } 36 | } 37 | 38 | type claimsKey string 39 | 40 | var claimsContext claimsKey = "claimsContext" 41 | 42 | type JWTValidator struct { 43 | key *rsa.PublicKey 44 | next http.Handler 45 | } 46 | 47 | func (v JWTValidator) ServeHTTP(w http.ResponseWriter, r *http.Request) { 48 | // Get the token from the request. 49 | auth := r.Header.Get("Authorization") 50 | if auth == "" { 51 | http.Error(w, "missing Authorization header", http.StatusUnauthorized) 52 | return 53 | } 54 | 55 | s := strings.TrimPrefix(auth, "Bearer ") 56 | 57 | token, err := jwt.Parse(s, func(token *jwt.Token) (interface{}, error) { 58 | if _, ok := token.Method.(*jwt.SigningMethodRSA); !ok { 59 | return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"]) 60 | } 61 | 62 | return v.key, nil 63 | }) 64 | if err != nil { 65 | http.Error(w, "auth error", http.StatusUnauthorized) 66 | return 67 | } 68 | 69 | claims, ok := token.Claims.(jwt.MapClaims) 70 | if !ok || !token.Valid { 71 | http.Error(w, "unauthorized", http.StatusUnauthorized) 72 | return 73 | } 74 | // Add the user's details to the context. 75 | r = r.WithContext(context.WithValue(r.Context(), claimsContext, claims)) 76 | v.next.ServeHTTP(w, r) 77 | } 78 | 79 | type WhoAmI struct { 80 | } 81 | 82 | func (wai WhoAmI) ServeHTTP(w http.ResponseWriter, r *http.Request) { 83 | cv := r.Context().Value(claimsContext) 84 | if cv == nil { 85 | http.Error(w, "can't find user", http.StatusUnauthorized) 86 | return 87 | } 88 | claims, ok := cv.(jwt.MapClaims) 89 | if !ok { 90 | http.Error(w, "claims aren't of that type", http.StatusInternalServerError) 91 | return 92 | } 93 | w.Write([]byte(fmt.Sprintf("%+v\n", claims["sub"]))) 94 | } 95 | -------------------------------------------------------------------------------- /chapter_4_distributed_applications/portscandetector/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | "os" 7 | "os/signal" 8 | "strings" 9 | "sync" 10 | "syscall" 11 | ) 12 | 13 | // NewCappedCounter creates a counter for string values which has a maximum 14 | // number of values it can contain. 15 | func NewCappedCounter() *CappedCounter { 16 | return &CappedCounter{ 17 | Capacity: 100, 18 | Keys: []string{}, 19 | Values: make(map[string]int), 20 | } 21 | } 22 | 23 | // CappedCounter is a counter of how many times a key has been seen 24 | // but caps the maximum number of keys it retains. 25 | type CappedCounter struct { 26 | Capacity int 27 | Keys []string 28 | Values map[string]int 29 | m sync.Mutex 30 | } 31 | 32 | // Increment the key's value by one and return the total. 33 | func (cc *CappedCounter) Increment(k string) (total int) { 34 | cc.m.Lock() 35 | defer cc.m.Unlock() 36 | v, ok := cc.Values[k] 37 | if !ok { 38 | cc.Keys = append(cc.Keys, k) 39 | if len(cc.Keys) > cc.Capacity { 40 | cc.Keys = cc.Keys[1:] 41 | } 42 | } 43 | total = v + 1 44 | cc.Values[k] = total 45 | return 46 | } 47 | 48 | // Scan is the source and target of the scan. 49 | type Scan struct { 50 | From string 51 | To string 52 | } 53 | 54 | // Start a Listener. 55 | func Start(port int, scanned chan Scan) (stop func(), err error) { 56 | l, err := net.ListenTCP("tcp", &net.TCPAddr{Port: port}) 57 | if err != nil { 58 | return 59 | } 60 | var wg sync.WaitGroup 61 | stop = func() { 62 | wg.Wait() 63 | l.Close() 64 | } 65 | go func(l *net.TCPListener, scanned chan Scan) { 66 | defer wg.Done() 67 | for { 68 | conn, err := l.AcceptTCP() 69 | if err != nil { 70 | if err.Error() == "use of closed network connection" { 71 | return 72 | } 73 | continue 74 | } 75 | go func() { 76 | defer conn.Close() 77 | segs := strings.Split(conn.RemoteAddr().String(), ":") 78 | fromIP := strings.Join(segs[:len(segs)-1], ":") 79 | scanned <- Scan{From: fromIP, To: conn.LocalAddr().String()} 80 | }() 81 | } 82 | }(l, scanned) 83 | return 84 | } 85 | 86 | func main() { 87 | sigs := make(chan os.Signal) 88 | signal.Notify(sigs, syscall.SIGINT) 89 | 90 | ports := []int{8080, 8086, 9100} 91 | stoppers := make([]func(), len(ports)) 92 | scanned := make(chan Scan) 93 | for i, p := range ports { 94 | var err error 95 | stoppers[i], err = Start(p, scanned) 96 | if err != nil { 97 | fmt.Println(err) 98 | } 99 | } 100 | count := NewCappedCounter() 101 | 102 | for { 103 | select { 104 | case by := <-scanned: 105 | total := count.Increment(by.From) 106 | fmt.Printf("Connection from %s to %s\n", by.From, by.To) 107 | if total >= 3 { 108 | fmt.Printf("Scanned by %s\n", by.From) 109 | } 110 | case <-sigs: 111 | fmt.Println("Shutting down...") 112 | for _, stop := range stoppers { 113 | stop() 114 | } 115 | return 116 | } 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /chapter_4_distributed_applications/portscanner/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "net" 7 | "sync" 8 | "time" 9 | ) 10 | 11 | var ports = []int{ 12 | 22, // SSH 13 | 23, // Telnet 14 | 80, // HTTP 15 | 443, // HTTPS 16 | 8080, // HTTP 17 | 53, // Domain 18 | 1433, // SQL Server 19 | 3306, // MySQL 20 | } 21 | 22 | var targetFlag = flag.String("target", "192.168.0.1", "The target to scan") 23 | 24 | func main() { 25 | flag.Parse() 26 | 27 | target := net.ParseIP(*targetFlag) 28 | if target == nil { 29 | fmt.Println("Failed to parse target address, is it formatted correctly?") 30 | return 31 | } 32 | 33 | var wg sync.WaitGroup 34 | for _, port := range ports { 35 | port := port 36 | go func() { 37 | wg.Add(1) 38 | defer wg.Done() 39 | addr := &net.TCPAddr{ 40 | IP: target, 41 | Port: port, 42 | } 43 | if portActive(addr.String()) { 44 | fmt.Println(addr.String()) 45 | } 46 | }() 47 | } 48 | wg.Wait() 49 | } 50 | 51 | func portActive(addr string) bool { 52 | var d net.Dialer 53 | // Wait for 500 ms per connection. 54 | d.Timeout = time.Millisecond * 500 55 | conn, err := d.Dial("tcp", addr) 56 | if err != nil { 57 | return false 58 | } 59 | defer conn.Close() 60 | return true 61 | } 62 | -------------------------------------------------------------------------------- /chapter_4_distributed_applications/proxy/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "log" 7 | "net" 8 | "net/http" 9 | 10 | "github.com/armon/go-socks5" 11 | ) 12 | 13 | func main() { 14 | // Start up an server to warn about disallowed domains. 15 | go func() { 16 | http.ListenAndServe(":10000", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 17 | fmt.Println("Accessing warning page.") 18 | w.Write([]byte("The domain you tried to access is not permitted.")) 19 | })) 20 | }() 21 | 22 | go func() { 23 | // Generate your own certificate against your local CA using the steps at https://deliciousbrains.com/ssl-certificate-authority-for-local-https-development/ 24 | certFile := "/Users/adrian/Documents/localca/LocalCAAll.crt" 25 | keyFile := "/Users/adrian/Documents/localca/LocalCAAll.key" 26 | http.ListenAndServeTLS(":10001", certFile, keyFile, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 27 | fmt.Println("Accessing TLS warning page.") 28 | w.Write([]byte("The TLS domain you tried to access is not permitted.")) 29 | })) 30 | }() 31 | 32 | // Redirect all disallowed IP addresses to our local server. 33 | s, err := socks5.New(&socks5.Config{ 34 | Rewriter: rewriter{}, 35 | }) 36 | if err != nil { 37 | log.Fatal(err) 38 | } 39 | s.ListenAndServe("tcp", ":9999") 40 | } 41 | 42 | type rewriter struct{} 43 | 44 | func (ur rewriter) Rewrite(ctx context.Context, r *socks5.Request) (context.Context, *socks5.AddrSpec) { 45 | fmt.Printf("Atttempting to connect to: %v\n", r.DestAddr.IP.String()) 46 | allowed, err := isAllowedIP(r.DestAddr.IP) 47 | if err != nil { 48 | log.Printf("failed to get IPs: %v", err) 49 | } 50 | if !allowed { 51 | // Rewrite to use our local server. 52 | fmt.Println("Rewriting to use local server.") 53 | if r.DestAddr.Port == 80 || r.DestAddr.Port == 8080 { 54 | // Connect to our local insecure Web server. 55 | return ctx, &socks5.AddrSpec{ 56 | FQDN: "localhost", 57 | IP: net.IPv4(127, 0, 0, 1), 58 | Port: 10000, 59 | } 60 | } 61 | // Connect to the TLS endpoint. 62 | return ctx, &socks5.AddrSpec{ 63 | FQDN: "localhost", 64 | IP: net.IPv4(127, 0, 0, 1), 65 | Port: 10001, 66 | } 67 | } 68 | fmt.Printf("Done.") 69 | return ctx, r.DestAddr 70 | } 71 | 72 | func getDisallowedIPAddresses() (disallowed []net.IP, err error) { 73 | disallowedDomains := []string{"google.com"} 74 | var ips []net.IP 75 | for _, disallowedDomain := range disallowedDomains { 76 | ips, err = net.LookupIP(disallowedDomain) 77 | if err != nil { 78 | return 79 | } 80 | disallowed = append(disallowed, ips...) 81 | } 82 | fmt.Printf("Disallowed domains: %v\n", disallowed) 83 | return 84 | } 85 | 86 | func isAllowedIP(ip net.IP) (bool, error) { 87 | disallowed, err := getDisallowedIPAddresses() 88 | if err != nil { 89 | return false, err 90 | } 91 | for _, d := range disallowed { 92 | fmt.Printf("Checking: %v is in disallowed list of %v\n", ip, disallowed) 93 | if ip.Equal(d) { 94 | return false, nil 95 | } 96 | } 97 | return true, nil 98 | } 99 | -------------------------------------------------------------------------------- /chapter_4_distributed_applications/queue/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "strconv" 6 | "sync" 7 | ) 8 | 9 | type data struct { 10 | Name string 11 | } 12 | 13 | func main() { 14 | // Make a queue that can hold up to 10 items before blocking. 15 | q := make(chan data, 10) 16 | 17 | // Post 10,000 items to the queue. 18 | go func() { 19 | for i := 0; i < 100000; i++ { 20 | q <- data{Name: strconv.Itoa(i)} 21 | } 22 | }() 23 | 24 | // Define a processing function. 25 | processor := func(processor int) { 26 | for { 27 | select { 28 | case d := <-q: 29 | fmt.Println(processor, "Received data", d) 30 | default: 31 | fmt.Println(processor, "Nothing to collect") 32 | } 33 | } 34 | } 35 | // Start up 2 processors. 36 | for i := 0; i < 2; i++ { 37 | go processor(i) 38 | } 39 | 40 | // Wait forever. 41 | var wg sync.WaitGroup 42 | wg.Add(1) 43 | wg.Wait() 44 | } 45 | -------------------------------------------------------------------------------- /chapter_4_distributed_applications/queue_full/cmd/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/PacktPublishing/Hands-On-Networking-with-Go-Programming/chapter_4_distributed_applications/queue_full" 7 | ) 8 | 9 | func main() { 10 | q := queue_full.New(10) 11 | 12 | for i := 0; i < 10; i++ { 13 | if err := q.Enqueue(i); err != nil { 14 | fmt.Println("got error:", err) 15 | return 16 | } 17 | } 18 | 19 | for { 20 | d, ok := q.Dequeue() 21 | if !ok { 22 | break 23 | } 24 | fmt.Println(d) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /chapter_4_distributed_applications/queue_full/queue.go: -------------------------------------------------------------------------------- 1 | package queue_full 2 | 3 | import ( 4 | "errors" 5 | "sync" 6 | ) 7 | 8 | // ErrMaxQueueSizeReached is returned when the max queue size has been reached. 9 | var ErrMaxQueueSizeReached = errors.New("queue: maximum queue size reached") 10 | 11 | // New creates a new queue. 12 | func New(maxSize int) *Queue { 13 | return &Queue{ 14 | MaxSize: maxSize, 15 | } 16 | } 17 | 18 | // Queue of items. 19 | type Queue struct { 20 | m sync.Mutex 21 | MaxSize int 22 | Data []interface{} 23 | } 24 | 25 | // Enqueue an item. 26 | func (q *Queue) Enqueue(d interface{}) (err error) { 27 | q.m.Lock() 28 | defer q.m.Unlock() 29 | if len(q.Data) == q.MaxSize { 30 | return ErrMaxQueueSizeReached 31 | } 32 | q.Data = append(q.Data, d) 33 | return 34 | } 35 | 36 | // Dequeue an item. 37 | func (q *Queue) Dequeue() (d interface{}, ok bool) { 38 | q.m.Lock() 39 | defer q.m.Unlock() 40 | if len(q.Data) == 0 { 41 | return 42 | } 43 | ok = true 44 | d = q.Data[0] 45 | q.Data = q.Data[1:] 46 | return 47 | } 48 | -------------------------------------------------------------------------------- /chapter_4_distributed_applications/rproxy/balancer.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "log" 7 | "math/rand" 8 | "net/http" 9 | "net/http/httputil" 10 | "net/url" 11 | "sync" 12 | "time" 13 | ) 14 | 15 | func NewOriginServer(target, healthCheck *url.URL) *OriginServer { 16 | return &OriginServer{ 17 | Target: target, 18 | HealthCheckEndpoint: healthCheck, 19 | } 20 | } 21 | 22 | type OriginServer struct { 23 | Target *url.URL 24 | IsHealthy bool 25 | HealthCheckEndpoint *url.URL 26 | } 27 | 28 | type Pool []*OriginServer 29 | 30 | func (p Pool) GetTarget() (u *url.URL, ok bool) { 31 | var healthy []int 32 | for i, t := range p { 33 | if t.IsHealthy { 34 | healthy = append(healthy, i) 35 | } 36 | } 37 | if len(healthy) == 0 { 38 | return 39 | } 40 | idx := rand.Int() % len(healthy) 41 | u = p[healthy[idx]].Target 42 | ok = true 43 | return 44 | } 45 | 46 | func (p Pool) HealthChecks() { 47 | var wg sync.WaitGroup 48 | for _, t := range p { 49 | wg.Add(1) 50 | go func(tt *OriginServer) { 51 | defer wg.Done() 52 | resp, err := http.Get(tt.HealthCheckEndpoint.String()) 53 | tt.IsHealthy = err == nil && resp.StatusCode == http.StatusOK 54 | fmt.Printf("%s is healthy = %v\n", tt.HealthCheckEndpoint.String(), tt.IsHealthy) 55 | }(t) 56 | } 57 | wg.Wait() 58 | } 59 | 60 | type Balancer struct { 61 | Pool Pool 62 | HealthCheckEvery time.Duration 63 | Proxy http.Handler 64 | } 65 | 66 | func (b *Balancer) ServeHTTP(w http.ResponseWriter, r *http.Request) { 67 | b.Proxy.ServeHTTP(w, r) 68 | } 69 | 70 | func (b *Balancer) healthChecks(ctx context.Context) { 71 | timer := time.After(b.HealthCheckEvery) 72 | for { 73 | select { 74 | case <-ctx.Done(): 75 | return 76 | case <-timer: 77 | b.Pool.HealthChecks() 78 | timer = time.After(b.HealthCheckEvery) 79 | } 80 | } 81 | } 82 | 83 | func NewBalancer(ctx context.Context, healthCheckEvery time.Duration, origins ...*OriginServer) (b *Balancer, cancel context.CancelFunc) { 84 | b = &Balancer{ 85 | HealthCheckEvery: healthCheckEvery, 86 | Pool: origins, 87 | } 88 | director := func(req *http.Request) { 89 | target, ok := b.Pool.GetTarget() 90 | if !ok { 91 | log.Println("no available servers, issuing 502 error!") 92 | return 93 | } 94 | targetQuery := target.RawQuery 95 | req.URL.Scheme = target.Scheme 96 | req.URL.Host = target.Host 97 | req.URL.Path = singleJoiningSlash(target.Path, req.URL.Path) 98 | req.Host = target.Host 99 | if targetQuery == "" || req.URL.RawQuery == "" { 100 | req.URL.RawQuery = targetQuery + req.URL.RawQuery 101 | } else { 102 | req.URL.RawQuery = targetQuery + "&" + req.URL.RawQuery 103 | } 104 | if _, ok := req.Header["User-Agent"]; !ok { 105 | // explicitly disable User-Agent so it's not set to default value 106 | req.Header.Set("User-Agent", "") 107 | } 108 | } 109 | b.Proxy = &httputil.ReverseProxy{ 110 | Director: director, 111 | } 112 | ctx, cancel = context.WithCancel(ctx) 113 | go b.healthChecks(ctx) 114 | return 115 | } 116 | -------------------------------------------------------------------------------- /chapter_4_distributed_applications/rproxy/cache.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "io/ioutil" 5 | "log" 6 | "net/http" 7 | "net/http/httptest" 8 | "time" 9 | ) 10 | 11 | func NewCache(duration time.Duration, next http.Handler) *Cache { 12 | return &Cache{ 13 | urlToResponse: make(map[string]CacheResponse), 14 | Next: next, 15 | Duration: duration, 16 | } 17 | } 18 | 19 | type Cache struct { 20 | urlToResponse map[string]CacheResponse 21 | Next http.Handler 22 | Duration time.Duration 23 | } 24 | 25 | type CacheResponse struct { 26 | Header http.Header 27 | Body []byte 28 | Expiry time.Time 29 | } 30 | 31 | func (c *Cache) ServeHTTP(w http.ResponseWriter, r *http.Request) { 32 | if r.Method == http.MethodGet || r.Method == http.MethodOptions || r.Method == http.MethodHead { 33 | cr, inCache := c.urlToResponse[r.URL.String()] 34 | if inCache && time.Now().Before(cr.Expiry) { 35 | log.Printf("%s: cache hit\n", r.URL.String()) 36 | } else { 37 | log.Printf("%s: setting up cache\n", r.URL.String()) 38 | // Make the response and cache it. 39 | ww := httptest.NewRecorder() 40 | c.Next.ServeHTTP(ww, r) 41 | 42 | body, _ := ioutil.ReadAll(ww.Body) 43 | cr = CacheResponse{ 44 | Body: body, 45 | Header: ww.Header(), 46 | Expiry: time.Now().Add(c.Duration), 47 | } 48 | c.urlToResponse[r.URL.String()] = cr 49 | } 50 | // Copy headers. 51 | for k, v := range cr.Header { 52 | for _, vv := range v { 53 | w.Header().Set(k, vv) 54 | } 55 | } 56 | // Write body. 57 | if cr.Body != nil { 58 | w.Write(cr.Body) 59 | } 60 | return 61 | } 62 | c.Next.ServeHTTP(w, r) 63 | } 64 | -------------------------------------------------------------------------------- /chapter_4_distributed_applications/rproxy/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "log" 7 | "net/http" 8 | "net/http/httputil" 9 | "net/url" 10 | "os" 11 | "strings" 12 | "time" 13 | 14 | "github.com/didip/tollbooth" 15 | 16 | "github.com/didip/tollbooth/limiter" 17 | ) 18 | 19 | func withBasicAuth(userNameToPassword map[string]string, next http.Handler) http.Handler { 20 | f := func(w http.ResponseWriter, r *http.Request) { 21 | u, p, ok := r.BasicAuth() 22 | if !ok { 23 | http.Error(w, "basic auth not used", http.StatusUnauthorized) 24 | return 25 | } 26 | if pp, ok := userNameToPassword[u]; !ok || p != pp { 27 | http.Error(w, "unkown user or invalid password", http.StatusUnauthorized) 28 | return 29 | } 30 | next.ServeHTTP(w, r) 31 | } 32 | return http.HandlerFunc(f) 33 | } 34 | 35 | func withAPIKey(apiKeyToUser map[string]string, next http.Handler) http.Handler { 36 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 37 | if _, ok := apiKeyToUser[r.Header.Get("X-API-Key")]; !ok { 38 | http.Error(w, "missing or invalid X-API-Key header", http.StatusUnauthorized) 39 | return 40 | } 41 | next.ServeHTTP(w, r) 42 | }) 43 | } 44 | 45 | type limitingMiddleware struct { 46 | limiter *limiter.Limiter 47 | handler http.Handler 48 | } 49 | 50 | func (lm limitingMiddleware) ServeHTTP(w http.ResponseWriter, r *http.Request) { 51 | header := "X-API-Key" 52 | lm.limiter.SetHeader(header, []string{r.Header.Get(header)}) 53 | lm.handler.ServeHTTP(w, r) 54 | } 55 | 56 | func withRateLimiting(next http.Handler) http.Handler { 57 | perSecond := 1.0 58 | l := tollbooth.NewLimiter(perSecond, nil) 59 | return limitingMiddleware{ 60 | limiter: l, 61 | handler: tollbooth.LimitHandler(l, next), 62 | } 63 | } 64 | 65 | func urlMust(s string) *url.URL { 66 | u, err := url.Parse(s) 67 | if err != nil { 68 | fmt.Println(err) 69 | os.Exit(1) 70 | } 71 | return u 72 | } 73 | 74 | func main() { 75 | backend1 := urlMust("https://5cf6b7a146583900149cbb82.mockapi.io") 76 | healthCheck1 := urlMust("https://5cf6b7a146583900149cbb82.mockapi.io/health") 77 | backend2 := urlMust("http://localhost:1111") 78 | healthCheck2 := urlMust("http://localhost:1111/health") 79 | // rp := newReverseProxy(backend1) 80 | rp, cancel := NewBalancer(context.Background(), time.Second*5, 81 | NewOriginServer(backend1, healthCheck1), 82 | NewOriginServer(backend2, healthCheck2)) 83 | 84 | cached := NewCache(time.Minute, rp) 85 | 86 | limited := withRateLimiting(cached) 87 | 88 | apiKeyToUsername := map[string]string{ 89 | "9a5f509f-b81b-4e91-a728-1f5d55adbd55": "user1", 90 | "829d5c8c-a782-4bbf-af00-c50044111d0e": "user2", 91 | } 92 | authenticated := withAPIKey(apiKeyToUsername, limited) 93 | 94 | allowedMethods := []string{ 95 | "GET", "POST", // OPTIONS is often required, but I'm leaving it out. 96 | } 97 | allowedQueryKeys := []string{"q"} // Just allow one querystring param. 98 | maxQueryValueLength := 10 // Only small querystring values are allowed. 99 | filtered := withFiltering(allowedMethods, allowedQueryKeys, maxQueryValueLength, authenticated) 100 | 101 | err := http.ListenAndServe(":9898", filtered) 102 | if err != nil { 103 | fmt.Println(err) 104 | os.Exit(1) 105 | } 106 | cancel() 107 | } 108 | 109 | func withFiltering(allowMethods []string, allowQueryKeys []string, maxQueryValueLength int, next http.Handler) http.Handler { 110 | // Put the items into a map for faster lookup. 111 | ms := make(map[string]struct{}) 112 | for _, am := range allowMethods { 113 | ms[am] = struct{}{} 114 | } 115 | aq := make(map[string]struct{}) 116 | for _, k := range allowQueryKeys { 117 | aq[k] = struct{}{} 118 | } 119 | f := func(w http.ResponseWriter, r *http.Request) { 120 | if _, methodAllowed := ms[r.Method]; !methodAllowed { 121 | dumped, _ := httputil.DumpRequest(r, true) 122 | log.Printf("filtered request (invalid HTTP method): %s", string(dumped)) 123 | http.Error(w, "invalid HTTP method", http.StatusMethodNotAllowed) 124 | return 125 | } 126 | for k, v := range r.URL.Query() { 127 | if _, queryParamAllowed := aq[k]; !queryParamAllowed { 128 | dumped, _ := httputil.DumpRequest(r, true) 129 | log.Printf("filtered request (invalid query param): %s", string(dumped)) 130 | http.Error(w, "invalid query param", http.StatusUnprocessableEntity) 131 | return 132 | } 133 | if len(v) > maxQueryValueLength { 134 | dumped, _ := httputil.DumpRequest(r, true) 135 | log.Printf("filtered request (query param length): %s", string(dumped)) 136 | http.Error(w, "invalid query param length", http.StatusUnprocessableEntity) 137 | return 138 | } 139 | } 140 | next.ServeHTTP(w, r) 141 | } 142 | return http.HandlerFunc(f) 143 | } 144 | 145 | func newReverseProxy(target *url.URL) *httputil.ReverseProxy { 146 | targetQuery := target.RawQuery 147 | director := func(req *http.Request) { 148 | req.URL.Scheme = target.Scheme 149 | req.URL.Host = target.Host 150 | req.URL.Path = singleJoiningSlash(target.Path, req.URL.Path) 151 | req.Host = target.Host 152 | if targetQuery == "" || req.URL.RawQuery == "" { 153 | req.URL.RawQuery = targetQuery + req.URL.RawQuery 154 | } else { 155 | req.URL.RawQuery = targetQuery + "&" + req.URL.RawQuery 156 | } 157 | if _, ok := req.Header["User-Agent"]; !ok { 158 | // explicitly disable User-Agent so it's not set to default value 159 | req.Header.Set("User-Agent", "") 160 | } 161 | } 162 | return &httputil.ReverseProxy{Director: director} 163 | } 164 | 165 | func singleJoiningSlash(a, b string) string { 166 | aslash := strings.HasSuffix(a, "/") 167 | bslash := strings.HasPrefix(b, "/") 168 | switch { 169 | case aslash && bslash: 170 | return a + b[1:] 171 | case !aslash && !bslash: 172 | return a + "/" + b 173 | } 174 | return a + b 175 | } 176 | -------------------------------------------------------------------------------- /chapter_4_distributed_applications/rproxy/testserver/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | ) 7 | 8 | func main() { 9 | fmt.Println("backend 2") 10 | http.ListenAndServe(":1111", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 11 | fmt.Println("received request:", r.URL.String()) 12 | w.Write([]byte("backend2")) 13 | })) 14 | } 15 | -------------------------------------------------------------------------------- /chapter_4_distributed_applications/sharding/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "crypto/sha256" 5 | "errors" 6 | "fmt" 7 | ) 8 | 9 | func main() { 10 | s, _ := NewSharder(32) 11 | fmt.Println(s.Shard("id1")) 12 | fmt.Println(s.Shard("id2")) 13 | fmt.Println(s.Shard("id3")) 14 | fmt.Println(s.Shard("id4")) 15 | fmt.Println(s.Shard("id5")) 16 | fmt.Println(s.Shard("id6")) 17 | fmt.Println(s.Shard("id7")) 18 | fmt.Println(s.Shard("id8")) 19 | fmt.Println(s.Shard("id9")) 20 | } 21 | 22 | // Sharder creates shards. 23 | type Sharder struct { 24 | divider byte 25 | } 26 | 27 | // ErrMustShardToMoreThanOne is returned when not enough shards are being made. 28 | var ErrMustShardToMoreThanOne = errors.New("sharder: must shard to more than one shard") 29 | 30 | // ErrMustShardToLessThan256 is returned when too many shards are being made. 31 | var ErrMustShardToLessThan256 = errors.New("sharder: must shard to less than 256 shards") 32 | 33 | // NewSharder allows sharding input data based on its SHA256 hash. 34 | func NewSharder(shards int) (s Sharder, err error) { 35 | if shards <= 1 { 36 | err = ErrMustShardToMoreThanOne 37 | return 38 | } 39 | if shards > 255 { 40 | err = ErrMustShardToLessThan256 41 | return 42 | } 43 | s = Sharder{ 44 | divider: byte(255) / byte(shards-1), 45 | } 46 | return 47 | } 48 | 49 | // Shard selects the shard an ID should be within. 50 | func (s Sharder) Shard(of string) (index int) { 51 | hash := sha256.Sum256([]byte(of)) 52 | firstByte := hash[0] 53 | return s.shardIndex(firstByte) 54 | } 55 | 56 | func (s Sharder) shardIndex(b byte) int { 57 | return int(b / s.divider) 58 | } 59 | -------------------------------------------------------------------------------- /chapter_4_distributed_applications/sharding/main_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestShardSection(t *testing.T) { 8 | tests := []struct { 9 | name string 10 | shardCount int 11 | input byte 12 | expected int 13 | }{ 14 | { 15 | name: "zero value is 0", 16 | shardCount: 32, 17 | input: 0, 18 | expected: 0, 19 | }, 20 | { 21 | name: "1 shards to 0", 22 | shardCount: 32, 23 | input: 1, 24 | expected: 0, 25 | }, 26 | { 27 | name: "8 shards to 1", 28 | shardCount: 32, 29 | input: 8, 30 | expected: 1, 31 | }, 32 | { 33 | name: "247 shards to 1", 34 | shardCount: 32, 35 | input: 255, 36 | expected: 31, 37 | }, 38 | } 39 | for _, tc := range tests { 40 | t.Run(tc.name, func(t *testing.T) { 41 | s, err := NewSharder(tc.shardCount) 42 | if err != nil { 43 | t.Fatalf("failed to create sharder: %v", err) 44 | } 45 | actual := s.shardIndex(tc.input) 46 | if actual != tc.expected { 47 | t.Errorf("expected %d, got %d", tc.expected, actual) 48 | } 49 | }) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /chapter_4_distributed_applications/spotifydl/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "fmt" 7 | "io/ioutil" 8 | "log" 9 | "net/http" 10 | "strings" 11 | "time" 12 | 13 | "github.com/gofrs/uuid" 14 | "golang.org/x/oauth2" 15 | "golang.org/x/oauth2/spotify" 16 | ) 17 | 18 | var ( 19 | baseURL = "localhost:9999" 20 | conf = &oauth2.Config{ 21 | RedirectURL: "http://" + baseURL + "/SpotifyCallback", 22 | ClientID: "xxxxxxxx", // See docs at https://developer.spotify.com/documentation/general/guides/app-settings/#register-your-app 23 | ClientSecret: "xxxxxxxx", 24 | Scopes: []string{"user-library-read"}, // access to read what I've saved within Spotify 25 | Endpoint: spotify.Endpoint, 26 | } 27 | state = uuid.Must(uuid.NewV4()).String() 28 | ) 29 | 30 | func main() { 31 | // Start a Web server to collect the code and push it to the channel. 32 | tokensChan := make(chan string, 1) 33 | s := http.Server{ 34 | Addr: baseURL, 35 | } 36 | s.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 37 | if actualState := r.URL.Query().Get("state"); actualState != state { 38 | http.Error(w, "unexpected authentication value", http.StatusUnauthorized) 39 | return 40 | } 41 | tokensChan <- r.URL.Query().Get("code") 42 | close(tokensChan) 43 | w.Write([]byte("Spotify authentication complete, you can close this window.")) 44 | }) 45 | go func() { 46 | err := s.ListenAndServe() 47 | if err != http.ErrServerClosed { 48 | log.Fatalf("error starting web server: %v", err) 49 | } 50 | }() 51 | 52 | // Redirect user to consent page to ask for permission 53 | // for the scopes specified above. 54 | ctx := context.Background() 55 | url := conf.AuthCodeURL(state, oauth2.AccessTypeOffline) 56 | fmt.Printf("Visit the URL for the auth dialog: %v", url) 57 | 58 | // Use the authorization code that is pushed to the redirect 59 | // URL. Exchange will do the handshake to retrieve the 60 | // initial access token. The HTTP Client returned by 61 | // conf.Client will refresh the token as necessary. 62 | code := <-tokensChan 63 | s.Close() 64 | tok, err := conf.Exchange(ctx, code) 65 | if err != nil { 66 | log.Fatal(err) 67 | } 68 | if !tok.Valid() { 69 | log.Fatal("The token is invalid") 70 | } 71 | 72 | client := conf.Client(ctx, tok) 73 | albums, err := getAllAlbums(client) 74 | if err != nil { 75 | log.Fatal(err) 76 | } 77 | for _, alb := range albums { 78 | fmt.Printf("%v, %v\n", getArtistName(alb), alb.Name) 79 | } 80 | } 81 | 82 | func getArtistName(a album) string { 83 | var names []string 84 | for _, ar := range a.Artists { 85 | names = append(names, ar.Name) 86 | } 87 | return strings.Join(names, ", ") 88 | } 89 | 90 | func getAllAlbums(c *http.Client) (albums []album, err error) { 91 | var url = "https://api.spotify.com/v1/me/albums" 92 | 93 | for { 94 | resp, err := c.Get(url) 95 | if err != nil { 96 | return albums, fmt.Errorf("error getting Spotify albums: %v", err) 97 | } 98 | bdy, err := ioutil.ReadAll(resp.Body) 99 | if err != nil { 100 | return albums, fmt.Errorf("error reading Spotify albums: %v", err) 101 | } 102 | var ar albumResponse 103 | err = json.Unmarshal(bdy, &ar) 104 | if err != nil { 105 | return albums, fmt.Errorf("error understanding Spotify albums: %v", err) 106 | } 107 | for _, itm := range ar.Items { 108 | albums = append(albums, itm.Album) 109 | } 110 | url = ar.Next 111 | if url == "" { 112 | break 113 | } 114 | } 115 | return 116 | } 117 | 118 | type albumResponse struct { 119 | Href string `json:"href"` 120 | Items []struct { 121 | AddedAt time.Time `json:"added_at"` 122 | Album album `json:"album"` 123 | } `json:"items"` 124 | Limit int `json:"limit"` 125 | Next string `json:"next"` 126 | Offset int `json:"offset"` 127 | Previous interface{} `json:"previous"` 128 | Total int `json:"total"` 129 | } 130 | 131 | type album struct { 132 | AlbumType string `json:"album_type"` 133 | Artists []struct { 134 | ExternalUrls struct { 135 | Spotify string `json:"spotify"` 136 | } `json:"external_urls"` 137 | Href string `json:"href"` 138 | ID string `json:"id"` 139 | Name string `json:"name"` 140 | Type string `json:"type"` 141 | URI string `json:"uri"` 142 | } `json:"artists"` 143 | AvailableMarkets []string `json:"available_markets"` 144 | Copyrights []struct { 145 | Text string `json:"text"` 146 | Type string `json:"type"` 147 | } `json:"copyrights"` 148 | ExternalIds struct { 149 | Upc string `json:"upc"` 150 | } `json:"external_ids"` 151 | ExternalUrls struct { 152 | Spotify string `json:"spotify"` 153 | } `json:"external_urls"` 154 | Genres []interface{} `json:"genres"` 155 | Href string `json:"href"` 156 | ID string `json:"id"` 157 | Images []struct { 158 | Height int `json:"height"` 159 | URL string `json:"url"` 160 | Width int `json:"width"` 161 | } `json:"images"` 162 | Label string `json:"label"` 163 | Name string `json:"name"` 164 | Popularity int `json:"popularity"` 165 | ReleaseDate string `json:"release_date"` 166 | ReleaseDatePrecision string `json:"release_date_precision"` 167 | TotalTracks int `json:"total_tracks"` 168 | Tracks struct { 169 | Href string `json:"href"` 170 | Items []struct { 171 | Artists []struct { 172 | ExternalUrls struct { 173 | Spotify string `json:"spotify"` 174 | } `json:"external_urls"` 175 | Href string `json:"href"` 176 | ID string `json:"id"` 177 | Name string `json:"name"` 178 | Type string `json:"type"` 179 | URI string `json:"uri"` 180 | } `json:"artists"` 181 | AvailableMarkets []string `json:"available_markets"` 182 | DiscNumber int `json:"disc_number"` 183 | DurationMs int `json:"duration_ms"` 184 | Explicit bool `json:"explicit"` 185 | ExternalUrls struct { 186 | Spotify string `json:"spotify"` 187 | } `json:"external_urls"` 188 | Href string `json:"href"` 189 | ID string `json:"id"` 190 | IsLocal bool `json:"is_local"` 191 | Name string `json:"name"` 192 | PreviewURL string `json:"preview_url"` 193 | TrackNumber int `json:"track_number"` 194 | Type string `json:"type"` 195 | URI string `json:"uri"` 196 | } `json:"items"` 197 | Limit int `json:"limit"` 198 | Next interface{} `json:"next"` 199 | Offset int `json:"offset"` 200 | Previous interface{} `json:"previous"` 201 | Total int `json:"total"` 202 | } `json:"tracks"` 203 | Type string `json:"type"` 204 | URI string `json:"uri"` 205 | } 206 | -------------------------------------------------------------------------------- /chapter_4_distributed_applications/stream/cmd/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | 7 | "github.com/PacktPublishing/Hands-On-Networking-with-Go-Programming/chapter_4_distributed_applications/stream" 8 | ) 9 | 10 | func main() { 11 | exit := func(head stream.Event, tail []stream.Event) { 12 | if head.Type != "itemAddedToBasket" { 13 | return 14 | } 15 | var success bool 16 | for _, t := range tail { 17 | if t.SessionID == head.SessionID && t.Type == "paymentReceived" { 18 | success = true 19 | } 20 | } 21 | fmt.Printf("%s: succeeded: %v\n", head.SessionID, success) 22 | } 23 | w := stream.NewEventWindow(time.Hour*5, exit) 24 | 25 | // Simulate events. 26 | events := []stream.Event{ 27 | stream.Event{ 28 | Date: time.Date(2020, time.January, 1, 0, 0, 0, 0, time.UTC), 29 | Type: "itemViewed", 30 | SessionID: "SessionA", 31 | }, 32 | stream.Event{ 33 | Date: time.Date(2020, time.January, 1, 0, 10, 0, 0, time.UTC), 34 | Type: "itemViewed", 35 | SessionID: "SessionB", 36 | }, 37 | stream.Event{ 38 | Date: time.Date(2020, time.January, 1, 1, 0, 0, 0, time.UTC), 39 | Type: "itemAddedToBasket", 40 | SessionID: "SessionA", 41 | }, 42 | stream.Event{ 43 | Date: time.Date(2020, time.January, 1, 1, 10, 0, 0, time.UTC), 44 | Type: "itemAddedToBasket", 45 | SessionID: "SessionB", 46 | }, 47 | stream.Event{ 48 | Date: time.Date(2020, time.January, 1, 2, 0, 0, 0, time.UTC), 49 | Type: "checkoutScreenViewed", 50 | SessionID: "SessionA", 51 | }, 52 | stream.Event{ 53 | Date: time.Date(2020, time.January, 1, 3, 0, 0, 0, time.UTC), 54 | Type: "paymentScreenViewed", 55 | SessionID: "SessionA", 56 | }, 57 | stream.Event{ 58 | Date: time.Date(2020, time.January, 1, 4, 0, 0, 0, time.UTC), 59 | Type: "paymentReceived", 60 | SessionID: "SessionA", 61 | }, 62 | stream.Event{ 63 | Date: time.Date(2020, time.January, 2, 0, 0, 0, 0, time.UTC), 64 | Type: "itemViewed", 65 | SessionID: "SessionC", 66 | }, 67 | stream.Event{ 68 | Date: time.Date(2020, time.January, 5, 0, 0, 0, 0, time.UTC), 69 | Type: "itemViewed", 70 | SessionID: "SessionC", 71 | }, 72 | } 73 | for _, e := range events { 74 | w.Push(e) 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /chapter_4_distributed_applications/stream/window.go: -------------------------------------------------------------------------------- 1 | package stream 2 | 3 | import ( 4 | "sync" 5 | "time" 6 | ) 7 | 8 | // Event to be windowed. 9 | type Event struct { 10 | Date time.Time `json:"date"` 11 | Type string `json:"type"` 12 | SessionID string `json:"sessionID"` 13 | } 14 | 15 | // EventWindow on a stream. 16 | type EventWindow struct { 17 | m sync.Mutex 18 | maxAge time.Duration 19 | events []Event 20 | exit func(head Event, tail []Event) 21 | } 22 | 23 | // NewEventWindow creates a window against events. 24 | func NewEventWindow(maxAge time.Duration, exit func(head Event, tail []Event)) *EventWindow { 25 | return &EventWindow{ 26 | maxAge: maxAge, 27 | events: []Event{}, 28 | exit: exit, 29 | } 30 | } 31 | 32 | // Push an event into the Windower. 33 | func (ew *EventWindow) Push(e Event) { 34 | defer ew.m.Unlock() 35 | ew.m.Lock() 36 | removeBefore := e.Date.Add(-ew.maxAge) 37 | var i int 38 | var previous Event 39 | for i, previous = range ew.events { 40 | if previous.Date.After(removeBefore) { 41 | break 42 | } 43 | } 44 | for j := 0; j < i; j++ { 45 | ew.exit(ew.events[j], ew.events[j:]) 46 | } 47 | ew.events = append(ew.events[i:], e) 48 | } 49 | 50 | // Window returns the window of current events. 51 | func (ew *EventWindow) Window() (events []Event) { 52 | return ew.events 53 | } 54 | -------------------------------------------------------------------------------- /chapter_4_distributed_applications/surveillance/download.txt: -------------------------------------------------------------------------------- 1 | You can download this. -------------------------------------------------------------------------------- /chapter_4_distributed_applications/surveillance/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "io" 5 | "net/http" 6 | "os" 7 | "path" 8 | ) 9 | 10 | func main() { 11 | http.ListenAndServe(":8000", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 12 | prefix := "/Users/adrian/go/src/github.com/PacktPublishing/Hands-On-Networking-with-Go-Programming/chapter_4_distributed_applications/surveillance/" 13 | suffix := r.URL.Query().Get("suffix") 14 | 15 | // Don't do this! 16 | // filename := prefix + suffix 17 | // Do this instead. 18 | _, fn := path.Split(suffix) 19 | filename := path.Join(prefix, fn) 20 | 21 | f, err := os.Open(filename) 22 | if err != nil { 23 | http.Error(w, "failed to open file", http.StatusInternalServerError) 24 | return 25 | } 26 | _, err = io.Copy(w, f) 27 | if err != nil { 28 | http.Error(w, "failed to copy file", http.StatusInternalServerError) 29 | return 30 | } 31 | })) 32 | } 33 | -------------------------------------------------------------------------------- /chapter_5_monitoring_and_failures/dropout/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "context" 6 | "fmt" 7 | "os" 8 | "sync" 9 | "time" 10 | ) 11 | 12 | func main() { 13 | var wg sync.WaitGroup 14 | ctx, cancel := context.WithCancel(context.Background()) 15 | go func() { 16 | wg.Add(1) 17 | defer wg.Done() 18 | countUntilCancelled(ctx) 19 | }() 20 | fmt.Println("Press enter to cancel....") 21 | bufio.NewReader(os.Stdin).ReadString('\n') 22 | cancel() 23 | fmt.Println("Cancel received, waiting for graceful shutdown") 24 | wg.Wait() 25 | fmt.Println("Graceful shutdown complete") 26 | } 27 | 28 | func countUntilCancelled(ctx context.Context) { 29 | ticker := time.NewTicker(time.Second) 30 | defer ticker.Stop() 31 | var i int64 32 | for { 33 | select { 34 | case <-ctx.Done(): 35 | fmt.Println("Worker received cancellation") 36 | return 37 | case <-ticker.C: 38 | i++ 39 | fmt.Println(i) 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /chapter_5_monitoring_and_failures/timeout/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "net/http" 7 | "sync" 8 | "time" 9 | ) 10 | 11 | func main() { 12 | var wg sync.WaitGroup 13 | 14 | var s http.Server 15 | s.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 16 | // Make sure we wait for all requests to complete. 17 | wg.Add(1) 18 | defer wg.Done() 19 | // This is a really slow HTTP request, it takes 1 minute to complete. 20 | // In the real world, you might have to make multiple calls in sequence. 21 | // If, after the first call, the client is no longer connected, 22 | // why bother making the second? 23 | fmt.Println("Server: simulating first API call...") 24 | <-time.After(time.Second * 10) 25 | 26 | if r.Context().Err() == context.Canceled { 27 | fmt.Println("Server: skipping second API call because the client is no longer connected...") 28 | w.WriteHeader(http.StatusTeapot) 29 | w.Write([]byte("Server: request cancelled by client.")) 30 | return 31 | } 32 | 33 | fmt.Println("Server: simulating second API call...") 34 | <-time.After(time.Millisecond * 500) 35 | w.WriteHeader(http.StatusOK) 36 | w.Write([]byte("Server: complete!")) 37 | }) 38 | s.Addr = "localhost:8090" 39 | 40 | // Start serving. 41 | go func() { 42 | wg.Add(1) 43 | defer wg.Done() 44 | s.ListenAndServe() 45 | }() 46 | 47 | fmt.Println("Client: waiting for server to start") 48 | time.Sleep(time.Second * 5) 49 | fmt.Println("Client: making request with 1 second timeout") 50 | req, err := http.NewRequest("GET", "http://localhost:8090", nil) 51 | if err != nil { 52 | fmt.Println("Client:", err) 53 | return 54 | } 55 | var c http.Client 56 | c.Timeout = time.Second * 1 57 | _, err = c.Do(req) 58 | if err != nil { 59 | // It should have timed out. 60 | fmt.Println("Client:", err) 61 | } 62 | if err := s.Shutdown(context.Background()); err != nil { 63 | fmt.Println(err) 64 | } 65 | fmt.Println("Waiting for server to gracefully shutdown") 66 | wg.Wait() 67 | fmt.Println("Complete") 68 | } 69 | --------------------------------------------------------------------------------