├── .gitignore ├── 978-1-4842-8095-9.jpg ├── Contributing.md ├── LICENSE.txt ├── README.md ├── appendix-fuzzing └── fuzzing_test.go ├── appendix-generics ├── complex_generics.go ├── generics.go ├── iterate.go ├── no_generics.go ├── simple_generics.go └── with_generics.go ├── ch10 ├── cedict_ts.u8 ├── css │ ├── cardstylesheet.css │ └── listflashcardsstylesheet.css ├── dictionary.go ├── flashcards.go ├── flashcardsets │ ├── common_words │ ├── lesson_04_surname_first_name │ ├── lesson_05_country_nationality │ └── lesson_06_city_native_place ├── html │ ├── listflashcards.html │ ├── listwords.html │ └── showflashcards.html ├── jscript │ ├── jquery.js │ ├── slideviewer.js │ └── sorttable.js ├── pinyinformatter.go └── server.go ├── ch11 ├── escapestring.go ├── readhtml.go └── sample.html ├── ch12 ├── marshal.go ├── parsexml.go ├── person.xml └── unmarshal.go ├── ch13 ├── arithclient.go ├── arithserver.go ├── jsonarithclient.go ├── jsonarithserver.go ├── tcparithclient.go └── tcparithserver.go ├── ch14 ├── client.go ├── clientjson.go ├── flashcardsets │ ├── CommonWords │ │ ├── 你好 │ │ ├── 你好吗 │ │ ├── 再见 │ │ ├── 厕所 │ │ ├── 喂 │ │ ├── 好 │ │ └── 谢谢 │ └── Lesson04 │ │ ├── 不 │ │ ├── 也 │ │ ├── 人 │ │ ├── 什么 │ │ ├── 他 │ │ ├── 他们 │ │ ├── 你 │ │ ├── 去 │ │ ├── 叫 │ │ ├── 名字 │ │ ├── 哪 │ │ ├── 囶 │ │ ├── 国家 │ │ ├── 她 │ │ ├── 她们 │ │ ├── 姓 │ │ ├── 学生 │ │ ├── 小姐 │ │ ├── 很 │ │ ├── 恁 │ │ ├── 想 │ │ ├── 我 │ │ ├── 是 │ │ ├── 是的 │ │ ├── 朋友 │ │ ├── 没 │ │ ├── 海 │ │ ├── 美国 │ │ ├── 老师 │ │ ├── 英国 │ │ ├── 认识 │ │ ├── 谁 │ │ ├── 贵 │ │ └── 都 ├── server.go └── xml │ ├── ListFlashcardSets.xml │ └── ListOneFlashcardSet.xml ├── ch15 ├── echoclient.go ├── echoclientgorilla.go ├── echoclienttls.go ├── echoserver.go ├── echoservergorilla.go ├── echoservertls.go ├── personclientjson.go ├── personclientxml.go ├── personserverjson.go ├── personserverxml.go ├── sensors.sh ├── temperatureserver.go ├── websocket.html └── xmlcodec.go ├── ch16 ├── contenttype.go ├── gmux.go ├── logging.go ├── matching.go ├── rpc.go ├── schema.go └── securecookie.go ├── ch17 └── basic_http_test.go ├── ch3 ├── daytimeserver.go ├── getheadinfo.go ├── ip.go ├── ipgetheadinfo.go ├── ipv4mask.go ├── ipv4router.go ├── lookuphost.go ├── lookupport.go ├── mask.go ├── ping.go ├── resolveip.go ├── simpleechoserver.go ├── threadedechoserver.go ├── threadedipechoserver.go ├── udpdaytimeclient.go └── udpdaytimeserver.go ├── ch4 ├── asn1.go ├── asn1basic.go ├── asn1fields.go ├── asn1pointers.go ├── asndaytimeclient.go ├── asndaytimeserver.go ├── badtype │ ├── code.go │ └── go.mod ├── base64.go ├── driver.go ├── gobechoclient.go ├── gobechoserver.go ├── jsonechoclient.go ├── jsonechoserver.go ├── loadgob.go ├── loadjson.go ├── myapp │ ├── protocolbuffer.go │ └── protos │ │ └── personv3.pb.go ├── newbase64coders.go ├── personv3.proto ├── prettyjson.go ├── savegob.go └── savejson.go ├── ch5 ├── ftpclient.go ├── ftpserver.go └── textprotoclient.go ├── ch6 ├── isotounicode.go ├── runeprint.go ├── utf16client.go ├── utf16encodedecode.go ├── utf16server.go └── utfnorm.go ├── ch7 ├── aes.go ├── genrsakeys.go ├── genx509cert.go ├── hmacmd5.go ├── largekey.go ├── loadrsakeys.go ├── loadx509cert.go ├── md5hash.go ├── sha256.go ├── tlsechoclient.go ├── tlsechoserver.go └── tlsgethead.go ├── ch8 ├── clientget.go ├── fileserver.go ├── get.go ├── head.go ├── httpsfileserver.go ├── printenv.go ├── proxyauthget.go ├── proxyget.go ├── punycode.go ├── serverhandler.go └── tlsunsafeclientget.go ├── ch9 ├── printemails.go ├── printjsonemails.go ├── printnameemails.go ├── printperson.go └── sequence.go └── errata.md /.gitignore: -------------------------------------------------------------------------------- 1 | **/ignore-* 2 | -------------------------------------------------------------------------------- /978-1-4842-8095-9.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apress/network-prog-with-go-2e/d8b0fd1f54fd2710e8e8a65ad804cbc2e7f63831/978-1-4842-8095-9.jpg -------------------------------------------------------------------------------- /Contributing.md: -------------------------------------------------------------------------------- 1 | # Contributing to Apress Source Code 2 | 3 | Copyright for Apress source code belongs to the author(s). However, under fair use you are encouraged to fork and contribute minor corrections and updates for the benefit of the author(s) and other readers. 4 | 5 | ## How to Contribute 6 | 7 | 1. Make sure you have a GitHub account. 8 | 2. Fork the repository for the relevant book. 9 | 3. Create a new branch on which to make your change, e.g. 10 | `git checkout -b my_code_contribution` 11 | 4. Commit your change. Include a commit message describing the correction. Please note that if your commit message is not clear, the correction will not be accepted. 12 | 5. Submit a pull request. 13 | 14 | Thank you for your contribution! -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Freeware License, some rights reserved 2 | 3 | Copyright (c) 2022 Jan Newmarch and Ronald Petty 4 | 5 | Permission is hereby granted, free of charge, to anyone obtaining a copy 6 | of this software and associated documentation files (the "Software"), 7 | to work with the Software within the limits of freeware distribution and fair use. 8 | This includes the rights to use, copy, and modify the Software for personal use. 9 | Users are also allowed and encouraged to submit corrections and modifications 10 | to the Software for the benefit of other users. 11 | 12 | It is not allowed to reuse, modify, or redistribute the Software for 13 | commercial use in any way, or for a user’s educational materials such as books 14 | or blog articles without prior permission from the copyright holder. 15 | 16 | The above copyright notice and this permission notice need to be included 17 | in all copies or substantial portions of the software. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | AUTHORS OR COPYRIGHT HOLDERS OR APRESS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 25 | SOFTWARE. 26 | 27 | 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Apress Source Code 2 | 3 | This repository accompanies [*Network Programming with Go Language*](https://link.springer.com/book/10.1007/978-1-4842-8095-9) by Jan Newmarch and Ronald Petty (Apress, 2022). 4 | 5 | [comment]: #cover 6 | ![Cover image](978-1-4842-8095-9.jpg) 7 | 8 | Download the files as a zip using the green button, or clone the repository to your machine using Git. 9 | 10 | ## Releases 11 | 12 | Release v1.0 corresponds to the code in the published book, without corrections or updates. 13 | 14 | ## Contributions 15 | 16 | See the file Contributing.md for more information on how you can contribute to this repository. -------------------------------------------------------------------------------- /appendix-fuzzing/fuzzing_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "encoding/base64" 6 | "encoding/hex" 7 | "net/http" 8 | "net/http/httptest" 9 | "testing" 10 | ) 11 | 12 | func FuzzMe(f *testing.F) { 13 | for _, seed := range [][]byte{{}, {0}, {9}, {0xa}, {0xf}, {1, 2, 3, 4}} { 14 | f.Add(seed) 15 | } 16 | 17 | // the fuzz runner f leverages the test runner t, 18 | // this is so the fuzzer can manage the tests, it generates (or uses seed) inputs 19 | // calling the passed in test 20 | f.Fuzz(func(t *testing.T, in []byte) { 21 | enc := hex.EncodeToString(in) 22 | out, err := hex.DecodeString(enc) 23 | if err != nil { 24 | t.Fatalf("%v: decode: %v", in, err) 25 | } 26 | if !bytes.Equal(in, out) { 27 | t.Fatalf("%v: not equal after round trip: %v", in, out) 28 | } 29 | }) 30 | } 31 | 32 | func FuzzBad(f *testing.F) { 33 | f.Fuzz(func(t *testing.T, i int) { 34 | if i != i { 35 | t.Fatalf("want: %v, got %v", i, i) 36 | } 37 | }) 38 | } 39 | 40 | func FuzzHandler(f *testing.F) { 41 | f.Fuzz(func(t *testing.T, data string) { 42 | v := base64.StdEncoding.EncodeToString([]byte(data)) 43 | req := httptest.NewRequest("GET", "/?q="+v, nil) 44 | res := httptest.NewRecorder() 45 | 46 | f := func(w http.ResponseWriter, req *http.Request) { 47 | keys, ok := req.URL.Query()["q"] 48 | 49 | if !ok || len(keys) != 1 { 50 | t.Log(keys) 51 | t.Fatal("q param missing or more than one instance") 52 | } 53 | 54 | val := keys[0] 55 | 56 | if len(val) > 16384 { 57 | w.WriteHeader(http.StatusNotAcceptable) 58 | } else { 59 | w.WriteHeader(http.StatusOK) 60 | } 61 | } 62 | 63 | f(res, req) 64 | 65 | if res == nil || res.Result().StatusCode != http.StatusOK { 66 | t.Fatal(res) 67 | } 68 | }) 69 | } 70 | -------------------------------------------------------------------------------- /appendix-generics/complex_generics.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "crypto/tls" 7 | "fmt" 8 | "io" 9 | "net/http" 10 | "strings" 11 | ) 12 | 13 | type myStruct struct { 14 | s *strings.Reader 15 | } 16 | 17 | func (m myStruct) Len() int { 18 | return m.s.Len() 19 | } 20 | 21 | func (m myStruct) Read(b []byte) (int, error) { 22 | return m.s.Read(b) 23 | } 24 | 25 | type MyType interface { 26 | *bytes.Buffer | *bytes.Reader | *strings.Reader | myStruct 27 | 28 | Len() int 29 | io.Reader 30 | comparable 31 | } 32 | 33 | type Lener interface { 34 | Len() int 35 | } 36 | 37 | // ./http/httptest/httptest.go 38 | func NewRequest[M MyType](method, target string, body M) *http.Request { 39 | if method == "" { 40 | method = "GET" 41 | } 42 | req, err := http.ReadRequest(bufio.NewReader(strings.NewReader(method + " " + target + " HTTP/1.0\r\n\r\n"))) 43 | if err != nil { 44 | panic("invalid NewRequest arguments; " + err.Error()) 45 | } 46 | 47 | // HTTP/1.0 was used above to avoid needing a Host field. Change it to 1.1 here. 48 | req.Proto = "HTTP/1.1" 49 | req.ProtoMinor = 1 50 | req.Close = false 51 | 52 | var zero M 53 | if body != zero { 54 | switch i := any(body).(type) { 55 | case Lener, io.ReadCloser: 56 | if b, ok := i.(Lener); ok { 57 | req.ContentLength = int64(b.Len()) 58 | } 59 | if rc, ok := i.(io.ReadCloser); ok { 60 | req.Body = rc 61 | } 62 | default: 63 | req.Body = io.NopCloser(body) 64 | } 65 | } else { 66 | req.ContentLength = -1 67 | } 68 | 69 | // 192.0.2.0/24 is "TEST-NET" in RFC 5737 for use solely in 70 | // documentation and example source code and should not be 71 | // used publicly. 72 | req.RemoteAddr = "192.0.2.1:1234" 73 | 74 | if req.Host == "" { 75 | req.Host = "example.com" 76 | } 77 | 78 | if strings.HasPrefix(target, "https://") { 79 | req.TLS = &tls.ConnectionState{ 80 | Version: tls.VersionTLS12, 81 | HandshakeComplete: true, 82 | ServerName: req.Host, 83 | } 84 | } 85 | 86 | return req 87 | } 88 | 89 | func main() { 90 | fmt.Println(NewRequest("GET", "/", myStruct{strings.NewReader("")}).ContentLength) 91 | fmt.Println(NewRequest("GET", "/", myStruct{}).ContentLength) 92 | fmt.Println(NewRequest("GET", "/", strings.NewReader("")).ContentLength) 93 | fmt.Println(NewRequest("GET", "/", &bytes.Buffer{}).ContentLength) 94 | fmt.Println(NewRequest("GET", "/", bytes.NewReader([]byte("read me"))).ContentLength) 95 | } 96 | -------------------------------------------------------------------------------- /appendix-generics/generics.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "crypto/tls" 7 | "fmt" 8 | "io" 9 | "net/http" 10 | "strings" 11 | ) 12 | 13 | type Animal interface { 14 | Sound() 15 | } 16 | 17 | type Cat struct{} 18 | 19 | func (c Cat) Sound() { fmt.Println("Meow") } 20 | 21 | type Dog struct{} 22 | 23 | func (d Dog) Sound() { fmt.Println("Woof") } 24 | 25 | type Hybrid interface { 26 | Animal 27 | } 28 | 29 | func test[H Hybrid](which H) H { 30 | which.Sound() 31 | return which 32 | /* 33 | switch any(which).(type) { 34 | case Dog: 35 | which.Sound() 36 | return which 37 | case Cat: 38 | which.Sound() 39 | return which 40 | default: 41 | which.Sound() 42 | return which 43 | } 44 | */ 45 | } 46 | 47 | type myStruct struct { 48 | s *strings.Reader 49 | } 50 | 51 | func (m myStruct) Len() int { 52 | return m.s.Len() 53 | } 54 | 55 | func (m myStruct) Read(b []byte) (int, error) { 56 | return m.s.Read(b) 57 | } 58 | 59 | func main() { 60 | c := test(Cat{}) 61 | d := test(Dog{}) 62 | 63 | c = c 64 | d = d 65 | 66 | r1 := NewRequest("GET", "/", myStruct{strings.NewReader("")}) 67 | r2 := NewRequest("GET", "/", myStruct{}) 68 | 69 | r1 = r1 70 | r2 = r2 71 | } 72 | 73 | type MyType interface { 74 | bytes.Buffer | bytes.Reader | strings.Reader | myStruct 75 | 76 | Len() int 77 | io.Reader 78 | comparable 79 | } 80 | 81 | // ./http/httptest/httptest.go 82 | func NewRequest[M MyType](method, target string, body M) *http.Request { 83 | if method == "" { 84 | method = "GET" 85 | } 86 | req, err := http.ReadRequest(bufio.NewReader(strings.NewReader(method + " " + target + " HTTP/1.0\r\n\r\n"))) 87 | if err != nil { 88 | panic("invalid NewRequest arguments; " + err.Error()) 89 | } 90 | 91 | // HTTP/1.0 was used above to avoid needing a Host field. Change it to 1.1 here. 92 | req.Proto = "HTTP/1.1" 93 | req.ProtoMinor = 1 94 | req.Close = false 95 | 96 | var zero M 97 | if body != zero { 98 | req.ContentLength = int64(body.Len()) 99 | switch v := any(body).(type) { 100 | case io.ReadCloser: 101 | req.Body = v 102 | default: 103 | req.Body = io.NopCloser(body) 104 | } 105 | } else { 106 | req.ContentLength = -1 107 | } 108 | 109 | // 192.0.2.0/24 is "TEST-NET" in RFC 5737 for use solely in 110 | // documentation and example source code and should not be 111 | // used publicly. 112 | req.RemoteAddr = "192.0.2.1:1234" 113 | 114 | if req.Host == "" { 115 | req.Host = "example.com" 116 | } 117 | 118 | if strings.HasPrefix(target, "https://") { 119 | req.TLS = &tls.ConnectionState{ 120 | Version: tls.VersionTLS12, 121 | HandshakeComplete: true, 122 | ServerName: req.Host, 123 | } 124 | } 125 | 126 | return req 127 | } 128 | -------------------------------------------------------------------------------- /appendix-generics/iterate.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | "time" 7 | ) 8 | 9 | // Our LinkedList code 10 | type LL struct { 11 | N *LL 12 | data string 13 | } 14 | 15 | // Retreive the next node in the linkedlist 16 | func (l LL) Next() *LL { return l.N } 17 | 18 | // Here we use a union of types “|” 19 | // Meaning our arguments must be of these types 20 | // Either channel or the above linkedlist type 21 | type MustBe interface { 22 | chan string | LL 23 | } 24 | 25 | // We use the union technique once more on the return types 26 | // Notice these can differ than the above "MustBe" types 27 | type Result interface { 28 | string | LL 29 | } 30 | 31 | // This is the function we wish to make generic 32 | // We are iterating over a instance of MustBe (either channel string or LL) 33 | // Notice the return type must be a Result type (either string or LL) 34 | func Iterate[M MustBe, R Result](o M, iter func(M) R) (r R) { 35 | return iter(o) 36 | } 37 | 38 | func main() { 39 | // Create channel for strings 40 | c := make(chan string, 5) 41 | c <- "ok" 42 | c <- "ok2" 43 | 44 | //This function is what we will pass into the above Iterate 45 | //Notice Iterate's first parameter is the same as the following 46 | //lambdas parameter 47 | citer := func(c chan string) string { 48 | select { 49 | case msg1 := <-c: 50 | return msg1 51 | case <-time.After(1 * time.Second): 52 | return "nothing" 53 | } 54 | } 55 | 56 | // Here we "Iterate" through the channel 57 | var wg sync.WaitGroup 58 | wg.Add(2) 59 | go func(f func(chan string) string) { 60 | for { 61 | fmt.Println(Iterate(c, f)) 62 | wg.Done() 63 | } 64 | }(citer) 65 | wg.Wait() // wait for iteration to finish 66 | 67 | // The remaining example shows passing a custom Linked List 68 | // iteration function 69 | 70 | // First we build a simple list 71 | n1 := LL{data: "n1"} 72 | n2 := LL{data: "n2"} 73 | n3 := LL{data: "n3"} 74 | n1.N = &n2 75 | n2.N = &n3 76 | 77 | // Like the above citer, the parameter type will match 78 | // the first parameter of Iterate above 79 | liter := func(l LL) LL { 80 | var zero LL 81 | 82 | if l.N != zero.N { 83 | return *l.N 84 | } else { 85 | return zero 86 | } 87 | } 88 | 89 | // We walk through the linked list 90 | n := n1 91 | for n.N != nil { 92 | fmt.Printf("node:%s\n",n.data) 93 | n = Iterate(n, liter) 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /appendix-generics/no_generics.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | func FilterInt(s []int, f func(int) bool) []int { 8 | var r []int 9 | for _, v := range s { 10 | if f(v) { 11 | r = append(r, v) 12 | } 13 | } 14 | 15 | return r 16 | } 17 | 18 | func FilterString(s []string, f func(string) bool) []string { 19 | var r []string 20 | for _, v := range s { 21 | if f(v) { 22 | r = append(r, v) 23 | } 24 | } 25 | 26 | return r 27 | } 28 | 29 | func main() { 30 | evens := FilterInt([]int{1, 2, 3, 4, 5}, func(i int) bool { return i%2 == 0 }) 31 | fmt.Printf("%v\n", evens) 32 | 33 | shortStrings := FilterString([]string{"ok", "notok", "maybe", "maybe not"}, func(s string) bool { return len(s) < 3 }) 34 | fmt.Printf("%v\n", shortStrings) 35 | } 36 | -------------------------------------------------------------------------------- /appendix-generics/simple_generics.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | type Animal interface { 8 | Sound() 9 | } 10 | 11 | type Cat struct{} 12 | 13 | func (c Cat) Sound() { fmt.Println("Meow") } 14 | 15 | func (c Cat) SpecialToCat() { fmt.Println("Cat special") } 16 | 17 | type Dog struct{} 18 | 19 | func (d Dog) Sound() { fmt.Println("Woof") } 20 | 21 | func (c Dog) UniqueToDog() { fmt.Println("Dog unique") } 22 | 23 | type Domesticated interface { 24 | Cat | Dog // Not Owls 25 | Animal 26 | } 27 | 28 | // Here we limit ourselves to domesticated animals 29 | // If you passed in a 'wild' animal, it would not work 30 | func SoundOff[H Domesticated](animal H) H { 31 | animal.Sound() 32 | 33 | switch a := any(animal).(type) { 34 | case Dog: 35 | a.UniqueToDog() 36 | case Cat: 37 | a.SpecialToCat() 38 | default: 39 | fmt.Println("Then hoo?") 40 | } 41 | return animal 42 | } 43 | 44 | // An Owl is an animal, 45 | // They are wild to us! 46 | type Owl struct{} 47 | 48 | func (Owl) Sound() {} 49 | 50 | func main() { 51 | var c Cat = SoundOff(Cat{}) 52 | d := SoundOff(Dog{}) 53 | 54 | c.Sound() 55 | c.SpecialToCat() 56 | d.Sound() 57 | d.UniqueToDog() 58 | 59 | SoundOff(Owl{}) 60 | } 61 | -------------------------------------------------------------------------------- /appendix-generics/with_generics.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | func Filter[T any](s []T, f func(T) bool) []T { 8 | var r []T 9 | for _, v := range s { 10 | if f(v) { 11 | r = append(r, v) 12 | } 13 | } 14 | 15 | return r 16 | } 17 | 18 | func main() { 19 | evens := Filter([]int{1, 2, 3, 4, 5}, func(i int) bool { return i%2 == 0 }) 20 | fmt.Printf("%v\n", evens) 21 | 22 | shortStrings := Filter([]string{"ok", "notok", "maybe", "maybe not"}, func(s string) bool { return len(s) < 3 }) 23 | fmt.Printf("%v\n", shortStrings) 24 | } 25 | -------------------------------------------------------------------------------- /ch10/css/cardstylesheet.css: -------------------------------------------------------------------------------- 1 | .english { 2 | background-color: rgb(126, 126, 126); 3 | padding: 5px; 4 | } 5 | 6 | .translations { 7 | background-color: rgb(216, 206, 206); 8 | padding: 5px; 9 | } 10 | 11 | .pinyin { 12 | background-color: rgb(199, 201, 201); 13 | padding: 5px; 14 | } 15 | 16 | .simplified { 17 | background-color: rgb(143, 143, 143); 18 | padding: 5px; 19 | } 20 | 21 | .traditional { 22 | background-color: rgb(182, 179, 179); 23 | padding: 5px; 24 | } 25 | 26 | body { 27 | background-color: lightgrey; 28 | } 29 | -------------------------------------------------------------------------------- /ch10/css/listflashcardsstylesheet.css: -------------------------------------------------------------------------------- 1 | body { 2 | background-color: lightgrey; 3 | } 4 | 5 | table { 6 | background-color: lightslategray; 7 | border: none; 8 | } 9 | 10 | td { 11 | padding: 10px; 12 | } -------------------------------------------------------------------------------- /ch10/dictionary.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "os" 7 | "strings" 8 | ) 9 | 10 | /* 11 | // go run dictionary.go - will load cedict_ts.u8 and render 12 | func main() { 13 | // called only when debugging this package 14 | dictionaryPath := "cedict_ts.u8" 15 | d := new(Dictionary) 16 | d.loadDictionary(dictionaryPath) 17 | 18 | // check it loaded okay 19 | goodD := d.LookupEnglish("good") 20 | fmt.Println(goodD.String()) 21 | } 22 | */ 23 | 24 | type DictionaryEntry struct { 25 | Traditional string 26 | Simplified string 27 | Pinyin string 28 | Translations []string 29 | } 30 | 31 | func (de DictionaryEntry) String() string { 32 | str := de.Traditional + de.Simplified + de.Pinyin 33 | for _, t := range de.Translations { 34 | str = str + "\n " + t 35 | } 36 | return str 37 | } 38 | 39 | type Dictionary struct { 40 | Entries []*DictionaryEntry 41 | } 42 | 43 | func Init(inital_len int) *Dictionary { 44 | d := new(Dictionary) 45 | d.Entries = make([]*DictionaryEntry, inital_len) 46 | return d 47 | } 48 | 49 | func (d *Dictionary) Len() int { 50 | return len(d.Entries) 51 | } 52 | 53 | func (d *Dictionary) At(i int) *DictionaryEntry { 54 | return d.Entries[i] 55 | } 56 | 57 | func (d *Dictionary) Set(i int, x *DictionaryEntry) { 58 | d.Entries[i] = x 59 | } 60 | 61 | func (d *Dictionary) Push(x *DictionaryEntry) { 62 | d.Entries = append(d.Entries, x) 63 | } 64 | 65 | func (d *Dictionary) String() string { 66 | str := "" 67 | for n := 0; n < d.Len(); n++ { 68 | de := d.At(n) 69 | str += de.String() + "\n" 70 | } 71 | return str 72 | } 73 | 74 | func (d *Dictionary) LookupPinyin(py string) *Dictionary { 75 | newD := new(Dictionary) 76 | for n := 0; n < d.Len(); n++ { 77 | de := d.At(n) 78 | if de.Pinyin == py { 79 | newD.Push(de) 80 | } 81 | } 82 | return newD 83 | } 84 | 85 | func (d *Dictionary) LookupEnglish(eng string) *Dictionary { 86 | newD := new(Dictionary) 87 | for n := 0; n < d.Len(); n++ { 88 | de := d.At(n) 89 | for _, e := range de.Translations { 90 | if e == eng { 91 | newD.Push(de) 92 | } 93 | } 94 | } 95 | return newD 96 | } 97 | 98 | func (d *Dictionary) loadDictionary(path string) { 99 | 100 | f, err := os.Open(path) 101 | r := bufio.NewReader(f) 102 | if err != nil { 103 | fmt.Println(err) 104 | os.Exit(1) 105 | } 106 | for { 107 | line, err := r.ReadString('\n') 108 | if line[0] == '#' { 109 | continue 110 | } 111 | if err != nil { 112 | return 113 | } 114 | 115 | trad, simp, pinyin, translations := parseDictEntry(line) 116 | 117 | de := DictionaryEntry{ 118 | Traditional: trad, 119 | Simplified: simp, 120 | Pinyin: pinyin, 121 | Translations: translations} 122 | 123 | d.Push(&de) 124 | } 125 | } 126 | 127 | func parseDictEntry(line string) (string, string, string, []string) { 128 | // format is 129 | // trad simp [pinyin] /trans/trans/.../ 130 | tradEnd := strings.Index(line, " ") 131 | trad := line[0:tradEnd] 132 | line = strings.TrimSpace(line[tradEnd:]) 133 | 134 | simpEnd := strings.Index(line, " ") 135 | simp := line[0:simpEnd] 136 | line = strings.TrimSpace(line[simpEnd:]) 137 | 138 | pinyinEnd := strings.Index(line, "]") 139 | pinyin := line[1:pinyinEnd] 140 | line = strings.TrimSpace(line[pinyinEnd+1:]) 141 | 142 | translations := strings.Split(line, "/") 143 | // includes empty at start and end, so 144 | translations = translations[1 : len(translations)-1] 145 | 146 | return trad, simp, pinyin, translations 147 | } 148 | -------------------------------------------------------------------------------- /ch10/flashcards.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | // "fmt" 5 | "encoding/json" 6 | "io" 7 | "os" 8 | "sort" 9 | ) 10 | 11 | type FlashCard struct { 12 | Simplified string 13 | English string 14 | Dictionary *Dictionary 15 | } 16 | 17 | type FlashCards struct { 18 | Name string 19 | CardOrder string 20 | ShowHalf string 21 | Cards []*FlashCard 22 | } 23 | 24 | func LoadJSON(r io.Reader, key any) { 25 | decoder := json.NewDecoder(r) 26 | err := decoder.Decode(key) 27 | checkError(err) 28 | } 29 | 30 | func ListFlashCardsNames() []string { 31 | flashCardsDir, err := os.Open("flashcardsets") 32 | if err != nil { 33 | return nil 34 | } 35 | files, err := flashCardsDir.Readdir(-1) 36 | 37 | fileNames := make([]string, len(files)) 38 | for n, f := range files { 39 | fileNames[n] = f.Name() 40 | } 41 | sort.Strings(fileNames) 42 | return fileNames 43 | } 44 | -------------------------------------------------------------------------------- /ch10/flashcardsets/common_words: -------------------------------------------------------------------------------- 1 | {"Name":"Common Words","CardOrder":"","ShowHalf":"","Cards":[{"Simplified":"你好","English":"hello","Dictionary":{"Entries":[{"Traditional":"你好","Simplified":"你好","Pinyin":"ni3 hao3","Translations":["hello","hi","how are you?"]}]}},{"Simplified":"喂","English":"hello (interj., esp. on telephone)","Dictionary":{"Entries":[{"Traditional":"喂","Simplified":"喂","Pinyin":"wei4","Translations":["hello (interj., esp. on telephone)","hey","to feed (sb or some animal)"]}]}},{"Simplified":"好","English":"good","Dictionary":{"Entries":[{"Traditional":"好","Simplified":"好","Pinyin":"hao3","Translations":["good","well","proper","good to","easy to","very","so","(suffix indicating completion or readiness)"]}]}},{"Simplified":"再见","English":"goodbye","Dictionary":{"Entries":[{"Traditional":"再見","Simplified":"再见","Pinyin":"zai4 jian4","Translations":["goodbye","see you again later"]}]}},{"Simplified":"你好吗","English":"How are you?","Dictionary":{"Entries":[{"Traditional":"你好嗎","Simplified":"你好吗","Pinyin":"ni3 hao3 ma5","Translations":["How are you?","Are you well?"]}]}},{"Simplified":"厕所","English":"toilet","Dictionary":{"Entries":[{"Traditional":"廁所","Simplified":"厕所","Pinyin":"ce4 suo3","Translations":["toilet","lavatory","CL:間|间[jian1],處|处[chu4]"]}]}},{"Simplified":"谢谢","English":"thanks","Dictionary":{"Entries":[{"Traditional":"謝謝","Simplified":"谢谢","Pinyin":"xie4 xie5","Translations":["to thank","thanks"]}]}}]} 2 | -------------------------------------------------------------------------------- /ch10/flashcardsets/lesson_04_surname_first_name: -------------------------------------------------------------------------------- 1 | {"Name":"Lesson 04: Surname, First Name","CardOrder":"","ShowHalf":"","Cards":[{"Simplified":"恁","English":"you (polite)","Dictionary":{"Entries":[{"Traditional":"恁","Simplified":"恁","Pinyin":"nin2","Translations":["you (polite)"]}]}},{"Simplified":"贵","English":"your (name)","Dictionary":{"Entries":[{"Traditional":"貴","Simplified":"贵","Pinyin":"gui4","Translations":["expensive","noble","your (name)","precious"]}]}},{"Simplified":"姓","English":"surname","Dictionary":{"Entries":[{"Traditional":"姓","Simplified":"姓","Pinyin":"xing4","Translations":["family name","surname","name","CL:個|个[ge4]"]}]}},{"Simplified":"我","English":"I","Dictionary":{"Entries":[{"Traditional":"我","Simplified":"我","Pinyin":"wo3","Translations":["I","me","my"]}]}},{"Simplified":"你","English":"you (informal, as opposed to courteous 您[nin2])","Dictionary":{"Entries":[{"Traditional":"你","Simplified":"你","Pinyin":"ni3","Translations":["you (informal, as opposed to courteous 您[nin2])"]}]}},{"Simplified":"叫","English":"to be called","Dictionary":{"Entries":[{"Traditional":"叫","Simplified":"叫","Pinyin":"jiao4","Translations":["to shout","to call","to order","to ask","to be called","by (indicates agent in the passive mood)"]}]}},{"Simplified":"什么","English":"what?","Dictionary":{"Entries":[{"Traditional":"什麼","Simplified":"什么","Pinyin":"shen2 me5","Translations":["what?","who?","something","anything"]}]}},{"Simplified":"名字","English":"name (of a person or thing)","Dictionary":{"Entries":[{"Traditional":"名字","Simplified":"名字","Pinyin":"ming2 zi5","Translations":["name (of a person or thing)","CL:個|个[ge4]"]}]}},{"Simplified":"他","English":"he or him","Dictionary":{"Entries":[{"Traditional":"他","Simplified":"他","Pinyin":"ta1","Translations":["he or him","(used for either sex when the sex is unknown or unimportant)","(used before sb's name for emphasis)","(used as a meaningless mock object)","other","another"]}]}},{"Simplified":"她","English":"she","Dictionary":{"Entries":[{"Traditional":"她","Simplified":"她","Pinyin":"ta1","Translations":["she"]}]}},{"Simplified":"是","English":"is","Dictionary":{"Entries":[{"Traditional":"是","Simplified":"是","Pinyin":"shi4","Translations":["is","are","am","yes","to be"]}]}},{"Simplified":"谁","English":"who","Dictionary":{"Entries":[{"Traditional":"誰","Simplified":"谁","Pinyin":"shei2","Translations":["who","also pronounced shui2"]}]}},{"Simplified":"老师","English":"teacher","Dictionary":{"Entries":[{"Traditional":"老師","Simplified":"老师","Pinyin":"lao3 shi1","Translations":["teacher","CL:個|个[ge4],位[wei4]"]}]}},{"Simplified":"认识","English":"to know","Dictionary":{"Entries":[{"Traditional":"認識","Simplified":"认识","Pinyin":"ren4 shi5","Translations":["to know","to recognize","to be familiar with","acquainted with sth","knowledge","understanding","awareness","cognition"]}]}},{"Simplified":"学生","English":"student","Dictionary":{"Entries":[{"Traditional":"學生","Simplified":"学生","Pinyin":"xue2 sheng5","Translations":["student","school child"]}]}},{"Simplified":"朋友","English":"friend","Dictionary":{"Entries":[{"Traditional":"朋友","Simplified":"朋友","Pinyin":"peng2 you5","Translations":["friend","CL:個|个[ge4],位[wei4]"]}]}},{"Simplified":"海","English":"sea","Dictionary":{"Entries":[{"Traditional":"海","Simplified":"海","Pinyin":"hai3","Translations":["ocean","sea","CL:個|个[ge4],片[pian4]"]}]}},{"Simplified":"去","English":"to go","Dictionary":{"Entries":[{"Traditional":"去","Simplified":"去","Pinyin":"qu4","Translations":["to go","to go to (a place)","to cause to go or send (sb)","to remove","to get rid of","(when used either before or after a verb) to go in order to do sth","to be apart from in space or time","(after a verb of motion indicates movement away from the speaker)","(used after certain verbs to indicate detachment or separation)","(of a time or an event etc) just passed or elapsed"]}]}},{"Simplified":"哪","English":"which","Dictionary":{"Entries":[{"Traditional":"哪","Simplified":"哪","Pinyin":"na3","Translations":["how","which"]}]}},{"Simplified":"国家","English":"country","Dictionary":{"Entries":[{"Traditional":"國家","Simplified":"国家","Pinyin":"guo2 jia1","Translations":["country","nation","state","CL:個|个[ge4]"]}]}},{"Simplified":"人","English":"person","Dictionary":{"Entries":[{"Traditional":"人","Simplified":"人","Pinyin":"ren2","Translations":["man","person","people","CL:個|个[ge4],位[wei4]"]}]}},{"Simplified":"是的","English":"yes","Dictionary":{"Entries":[{"Traditional":"是的","Simplified":"是的","Pinyin":"shi4 de5","Translations":["yes"]}]}},{"Simplified":"小姐","English":"miss","Dictionary":{"Entries":[{"Traditional":"小姐","Simplified":"小姐","Pinyin":"xiao3 jie5","Translations":["young lady","miss","CL:個|个[ge4],位[wei4]"]}]}},{"Simplified":"囶","English":"country","Dictionary":{"Entries":[{"Traditional":"囶","Simplified":"囶","Pinyin":"guo2","Translations":["variant of 國|国","country"]}]}},{"Simplified":"美国","English":"USA","Dictionary":{"Entries":[{"Traditional":"美國","Simplified":"美国","Pinyin":"Mei3 guo2","Translations":["United States","USA","US"]}]}},{"Simplified":"他们","English":"they","Dictionary":{"Entries":[{"Traditional":"他們","Simplified":"他们","Pinyin":"ta1 men5","Translations":["they"]}]}},{"Simplified":"她们","English":"they","Dictionary":{"Entries":[{"Traditional":"她們","Simplified":"她们","Pinyin":"ta1 men5","Translations":["they","them (for females)"]}]}},{"Simplified":"也","English":"also","Dictionary":{"Entries":[{"Traditional":"也","Simplified":"也","Pinyin":"ye3","Translations":["also","too","(in Classical Chinese) final particle implying affirmation"]}]}},{"Simplified":"不","English":"no","Dictionary":{"Entries":[{"Traditional":"不","Simplified":"不","Pinyin":"bu4","Translations":["(negative prefix)","not","no"]}]}},{"Simplified":"都","English":"all, both","Dictionary":{"Entries":[{"Traditional":"都","Simplified":"都","Pinyin":"dou1","Translations":["all, both","entirely (due to) each","even","already"]}]}},{"Simplified":"英国","English":"United Kingdom of Great Britain and Northern Ireland","Dictionary":{"Entries":[{"Traditional":"英國","Simplified":"英国","Pinyin":"Ying1 guo2","Translations":["United Kingdom 聯合王國|联合王国[Lian2 he2 wang2 guo2]","United Kingdom of Great Britain and Northern Ireland","abbr. for England 英格蘭|英格兰[Ying1 ge2 lan2]"]}]}},{"Simplified":"没","English":"(negative prefix for verbs)","Dictionary":{"Entries":[{"Traditional":"沒","Simplified":"没","Pinyin":"mei2","Translations":["(negative prefix for verbs)","have not","not"]}]}},{"Simplified":"想","English":"to think","Dictionary":{"Entries":[{"Traditional":"想","Simplified":"想","Pinyin":"xiang3","Translations":["to think","to believe","to suppose","to wish","to want","to miss"]}]}},{"Simplified":"很","English":"very","Dictionary":{"Entries":[{"Traditional":"很","Simplified":"很","Pinyin":"hen3","Translations":["(adverb of degree)","quite","very","awfully"]}]}}]} 2 | -------------------------------------------------------------------------------- /ch10/flashcardsets/lesson_05_country_nationality: -------------------------------------------------------------------------------- 1 | {"Name":"Lesson 05: Country, Nationality","CardOrder":"","ShowHalf":"","Cards":[{"Simplified":"去","English":"to go","Dictionary":{"Entries":[{"Traditional":"去","Simplified":"去","Pinyin":"qu4","Translations":["to go","to go to (a place)","to cause to go or send (sb)","to remove","to get rid of","(when used either before or after a verb) to go in order to do sth","to be apart from in space or time","(after a verb of motion indicates movement away from the speaker)","(used after certain verbs to indicate detachment or separation)","(of a time or an event etc) just passed or elapsed"]}]}},{"Simplified":"哪","English":"which","Dictionary":{"Entries":[{"Traditional":"哪","Simplified":"哪","Pinyin":"na3","Translations":["how","which"]}]}},{"Simplified":"个","English":"classifier for people or objects in general","Dictionary":{"Entries":[{"Traditional":"個","Simplified":"个","Pinyin":"ge4","Translations":["individual","this","that","size","classifier for people or objects in general"]}]}},{"Simplified":"国家","English":"country","Dictionary":{"Entries":[{"Traditional":"國家","Simplified":"国家","Pinyin":"guo2 jia1","Translations":["country","nation","state","CL:個|个[ge4]"]}]}},{"Simplified":"中国","English":"China","Dictionary":{"Entries":[{"Traditional":"中國","Simplified":"中国","Pinyin":"Zhong1 guo2","Translations":["China","Middle Kingdom"]}]}},{"Simplified":"人","English":"person","Dictionary":{"Entries":[{"Traditional":"人","Simplified":"人","Pinyin":"ren2","Translations":["man","person","people","CL:個|个[ge4],位[wei4]"]}]}},{"Simplified":"吗","English":"(question tag)","Dictionary":{"Entries":[{"Traditional":"嗎","Simplified":"吗","Pinyin":"ma5","Translations":["(question tag)"]}]}},{"Simplified":"是的","English":"yes","Dictionary":{"Entries":[{"Traditional":"是的","Simplified":"是的","Pinyin":"shi4 de5","Translations":["yes"]}]}},{"Simplified":"国","English":"country","Dictionary":{"Entries":[{"Traditional":"國","Simplified":"国","Pinyin":"guo2","Translations":["country","nation","state","national","CL:個|个[ge4]"]}]}},{"Simplified":"美国","English":"USA","Dictionary":{"Entries":[{"Traditional":"美國","Simplified":"美国","Pinyin":"Mei3 guo2","Translations":["United States","USA","US"]}]}},{"Simplified":"们","English":"plural marker for pronouns, and nouns referring to individuals","Dictionary":{"Entries":[{"Traditional":"們","Simplified":"们","Pinyin":"men5","Translations":["plural marker for pronouns, and nouns referring to individuals"]}]}},{"Simplified":"他们","English":"they","Dictionary":{"Entries":[{"Traditional":"他們","Simplified":"他们","Pinyin":"ta1 men5","Translations":["they"]}]}},{"Simplified":"也","English":"also","Dictionary":{"Entries":[{"Traditional":"也","Simplified":"也","Pinyin":"ye3","Translations":["also","too","(in Classical Chinese) final particle implying affirmation"]}]}},{"Simplified":"不","English":"no","Dictionary":{"Entries":[{"Traditional":"不","Simplified":"不","Pinyin":"bu4","Translations":["(negative prefix)","not","no"]}]}},{"Simplified":"都","English":"all, both","Dictionary":{"Entries":[{"Traditional":"都","Simplified":"都","Pinyin":"dou1","Translations":["all, both","entirely (due to) each","even","already"]}]}},{"Simplified":"英国","English":"United Kingdom 聯合王國|联合王国[Lian2 he2 wang2 guo2]","Dictionary":{"Entries":[{"Traditional":"英國","Simplified":"英国","Pinyin":"Ying1 guo2","Translations":["United Kingdom 聯合王國|联合王国[Lian2 he2 wang2 guo2]","United Kingdom of Great Britain and Northern Ireland","abbr. for England 英格蘭|英格兰[Ying1 ge2 lan2]"]}]}},{"Simplified":"想","English":"to want","Dictionary":{"Entries":[{"Traditional":"想","Simplified":"想","Pinyin":"xiang3","Translations":["to think","to believe","to suppose","to wish","to want","to miss"]}]}},{"Simplified":"很","English":"very","Dictionary":{"Entries":[{"Traditional":"很","Simplified":"很","Pinyin":"hen3","Translations":["(adverb of degree)","quite","very","awfully"]}]}}]} 2 | -------------------------------------------------------------------------------- /ch10/flashcardsets/lesson_06_city_native_place: -------------------------------------------------------------------------------- 1 | {"Name":"Lesson 06: City, Native Place","CardOrder":"","ShowHalf":"","Cards":[{"Simplified":"这","English":"this","Dictionary":{"Entries":[{"Traditional":"這","Simplified":"这","Pinyin":"zhe4","Translations":["this","these","(commonly pr. zhei4 before a classifier, esp. in Beijing)"]}]}},{"Simplified":"那","English":"that","Dictionary":{"Entries":[{"Traditional":"那","Simplified":"那","Pinyin":"na4","Translations":["that","those","then (in that case)","commonly pr. nei4 before a classifier, esp. in Beijing"]}]}},{"Simplified":"国家","English":"country","Dictionary":{"Entries":[{"Traditional":"國家","Simplified":"国家","Pinyin":"guo2 jia1","Translations":["country","nation","state","CL:個|个[ge4]"]}]}},{"Simplified":"地方","English":"place","Dictionary":{"Entries":[{"Traditional":"地方","Simplified":"地方","Pinyin":"di4 fang5","Translations":["area","place","space","room","territory","CL:處|处[chu4],個|个[ge4],塊|块[kuai4]"]}]}},{"Simplified":"去","English":"to go","Dictionary":{"Entries":[{"Traditional":"去","Simplified":"去","Pinyin":"qu4","Translations":["to go","to go to (a place)","to cause to go or send (sb)","to remove","to get rid of","(when used either before or after a verb) to go in order to do sth","to be apart from in space or time","(after a verb of motion indicates movement away from the speaker)","(used after certain verbs to indicate detachment or separation)","(of a time or an event etc) just passed or elapsed"]}]}},{"Simplified":"哪","English":"which","Dictionary":{"Entries":[{"Traditional":"哪","Simplified":"哪","Pinyin":"na3","Translations":["how","which"]}]}},{"Simplified":"大","English":"big","Dictionary":{"Entries":[{"Traditional":"大","Simplified":"大","Pinyin":"da4","Translations":["big","huge","large","major","great","wide","deep","oldest","eldest"]}]}},{"Simplified":"小","English":"small","Dictionary":{"Entries":[{"Traditional":"小","Simplified":"小","Pinyin":"xiao3","Translations":["small","tiny","few","young"]}]}},{"Simplified":"可是","English":"but","Dictionary":{"Entries":[{"Traditional":"可是","Simplified":"可是","Pinyin":"ke3 shi4","Translations":["but","however"]}]}},{"Simplified":"多","English":"many","Dictionary":{"Entries":[{"Traditional":"多","Simplified":"多","Pinyin":"duo1","Translations":["many","much","a lot of","numerous","multi-"]}]}}]} 2 | -------------------------------------------------------------------------------- /ch10/html/listflashcards.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Flashcards 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |

15 | Flashcards 16 |

17 |

18 | 19 |

20 |
21 | 22 | 23 | 24 | 27 | 28 | {{range $i, $e := .}} 29 | 30 | 33 | 36 | 37 | {{end}} 38 |
25 | Flashcard Sets 26 |
31 | {{$e}} 32 | 34 | 35 |
39 |
40 |
41 | 42 | 43 | 46 | 47 | 48 | 51 | 54 | 55 |
44 | Card order 45 |
49 | Random 50 | 52 | Sequential 53 |
56 |
57 | 58 | 59 | 62 | 63 | 64 | 67 | 70 | 73 | 74 |
60 | Half card display 61 |
65 | Random 66 | 68 | English 69 | 71 | Chinese 72 |
75 |
76 | 77 |
78 |
79 | 82 | 85 |
86 |
87 |
88 |

89 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /ch10/html/listwords.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Words for {{.Name}} 6 | 7 | 8 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |

18 | Words for {{.Name}} 19 |

20 |

21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | {{range .Cards}} 29 |
30 |
31 |
32 |
33 |
36 | 37 | 38 | 39 | {{with .Dictionary}} 40 | {{range .Entries}} 41 |
42 |
43 |
46 | 47 | 48 | 49 |
50 |
51 |
54 | 55 | 56 | 57 |
58 |
59 |
62 | 63 | 64 | 65 | {{end}} 66 | {{end}} 67 | 68 | 69 | {{end}} 70 |
English Pinyin Traditional Simplified
34 | {{.English}} 35 | 44 | {{.Pinyin|pinyin}} 45 | 52 | {{.Traditional}} 53 | 60 | {{.Simplified}} 61 |
71 |

72 |

73 | Return to Flash Cards list 74 |

75 | 76 | 77 | -------------------------------------------------------------------------------- /ch10/html/showflashcards.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Flashcards for {{.Name}} 6 | 7 | 8 | 9 | 10 | 11 | 14 | 15 | 18 | 19 | 23 | 24 | 25 | 26 | 27 |

28 | Flashcards for {{.Name}} 29 |

30 |

31 | 32 | {{range .Cards}} 33 |

34 |
35 |
36 | English: {{.English}} 37 |
38 |
39 | 40 | {{with .Dictionary}} 41 | {{range .Entries}} 42 |
43 |
44 | Pinyin: {{.Pinyin|pinyin}} 45 |
46 |
47 | 48 |
49 |
50 | Traditional: {{.Traditional}} 51 |
52 |
53 | 54 |
55 |
56 | Simplified: {{.Simplified}} 57 |
58 |
59 | 60 |
61 |
62 | Translations:
63 | {{range .Translations}} 64 | {{.}}
65 | {{end}} 66 |
67 |
68 | {{end}} 69 | {{end}} 70 |
71 | {{end}} 72 |

73 |
74 |

75 | Press <Space> or Tap to continue 76 |
77 | Return to Flash Cards list 78 |

79 |
80 | 81 | 82 | 83 | 84 | -------------------------------------------------------------------------------- /ch10/jscript/slideviewer.js: -------------------------------------------------------------------------------- 1 | // var touchy=('ontouchstart' in document.documentElement)?true:false;` 2 | var RANDOM = 1; 3 | var SEQUENTIAL = 2; 4 | var ENGLISH_HALF = 3; 5 | var CHINESE_HALF = 4; 6 | var RANDOM_HALF = 5; 7 | 8 | var SHOW_HALF = 1; 9 | var SHOW_FULL = 2; 10 | 11 | // overridden by form fields 12 | var cardOrder = SEQUENTIAL; 13 | var showHalfCard = RANDOM_HALF; 14 | 15 | var cardState = SHOW_HALF; 16 | 17 | var currentCard; 18 | var cards; 19 | 20 | // window.onkeypress = keypress; 21 | //document.onkeypress = keypress; 22 | 23 | function initSlides() { 24 | cards = new Cards(cardOrder, showHalfCard); 25 | cardStr = new String("card") 26 | 27 | var allDivElmts = document.getElementsByTagName("div"); 28 | var j = 0; 29 | for (var i = 0; i < allDivElmts.length; i++) { 30 | 31 | if (allDivElmts[i].className == "card") { 32 | cards.push(allDivElmts[i]); 33 | } 34 | } 35 | } 36 | 37 | function showSlides() { 38 | 39 | initSlides(); 40 | 41 | allVisible = false; 42 | 43 | currentCard = cards.getNextCard(); 44 | currentCard.showHalf(); 45 | } 46 | 47 | /* 48 | function keypress(evt) { 49 | // FF var keychar = (evt.keycode) ? evt.keycode : evt.which; 50 | //var keychar = evt.charCode || evt.keyCode; 51 | keychar = evt.which; 52 | alert(keychar); 53 | if (keychar != 32) { 54 | return true; 55 | } 56 | 57 | if (cardState == SHOW_HALF) { 58 | currentCard.show(); 59 | cardState = SHOW_FULL; 60 | } else { 61 | currentCard.hide(); 62 | currentCard = cards.getNextCard(); 63 | currentCard.showHalf(); 64 | cardState = SHOW_HALF; 65 | } 66 | return false; 67 | } 68 | */ 69 | 70 | function advanceCard() { 71 | if (cardState == SHOW_HALF) { 72 | currentCard.show(); 73 | cardState = SHOW_FULL; 74 | } else { 75 | currentCard.hide(); 76 | currentCard = cards.getNextCard(); 77 | currentCard.showHalf(); 78 | cardState = SHOW_HALF; 79 | } 80 | } 81 | 82 | $(document).keypress(function (evt) { 83 | keychar = evt.which; 84 | // alert(keychar); 85 | if (keychar != 32) { 86 | return true; 87 | } 88 | 89 | advanceCard(); 90 | return false; 91 | } 92 | ); 93 | 94 | /* 95 | $(document).click(function(evt) { 96 | // needed for iPad, Android with no keyboard! 97 | advanceCard(); 98 | return false; 99 | } 100 | ); 101 | */ 102 | 103 | document.ontouchend = function (evt) { 104 | // needed for iPad, Android with no keyboard! 105 | advanceCard(); 106 | return false; 107 | } 108 | 109 | function convertHalf(showHalfCard) { 110 | if (showHalfCard == "CHINESE_HALF") { 111 | return CHINESE_HALF; 112 | } else if (showHalfCard == "ENGLISH_HALF") { 113 | return ENGLISH_HALF; 114 | } else { 115 | return RANDOM_HALF 116 | } 117 | } 118 | 119 | function convertOrder(cardOrder) { 120 | if (cardOrder == "RANDOM") { 121 | return RANDOM; 122 | } else { 123 | return SEQUENTIAL; 124 | } 125 | } 126 | 127 | function Cards(cardOrder, showHalfCard) { 128 | this.cardOrder = convertOrder(cardOrder); 129 | this.showHalfCard = convertHalf(showHalfCard); 130 | this.length = 0; 131 | this.index = -1; 132 | 133 | this.getNextCard = function () { 134 | if (this.cardOrder == RANDOM) { 135 | r = Math.random(); 136 | rr = r * this.length; 137 | console.log("Random #", r, rr); 138 | index = Math.floor(rr); 139 | return this[index]; 140 | } 141 | if (++this.index >= this.length) { 142 | this.index = 0; 143 | } 144 | return this[this.index]; 145 | } 146 | 147 | this.push = function (node) { 148 | card = new Card(node); 149 | this[this.length++] = card; 150 | } 151 | 152 | } 153 | 154 | function Card(node) { 155 | this.node = node; 156 | this.node.style.visibility = 'hidden'; 157 | this.node.style.position = 'absolute'; 158 | this.node.style.left = 10; 159 | this.node.style.right = 0; 160 | this.node.style.width = '50%'; 161 | 162 | this.getChild = function (child) { 163 | children = this.node.getElementsByTagName("div"); 164 | for (var i = 0; i < children.length; i++) { 165 | if (children[i].className == child) { 166 | return children[i]; 167 | } 168 | } 169 | return null; 170 | } 171 | 172 | this.english = this.getChild("english"); 173 | this.translations = this.getChild("translations"); 174 | this.pinyin = this.getChild("pinyin"); 175 | this.simplified = this.getChild("simplified"); 176 | this.traditional = this.getChild("traditional"); 177 | 178 | this.show = function () { 179 | //alert("Showing full") 180 | this.node.style.visibility = 'visible'; 181 | this.setVisibility([this.pinyin, 182 | this.simplified, 183 | this.traditional], 184 | 'visible'); 185 | this.setVisibility([this.english, 186 | this.translations], 187 | 'visible'); 188 | 189 | } 190 | 191 | this.showChild = function (child) { 192 | child.style.visibility = 'visible'; 193 | } 194 | 195 | this.hideChild = function (child) { 196 | child.style.visibility = 'hidden'; 197 | } 198 | 199 | this.showHalf = function () { 200 | if (cards.showHalfCard == RANDOM_HALF) { 201 | if (Math.random() < 0.5) { 202 | this.setVisibility([this.pinyin, 203 | this.simplified, 204 | this.traditional], 205 | 'hidden'); 206 | this.setVisibility([this.english, 207 | this.translations], 208 | 'visible'); 209 | } else { 210 | this.setVisibility([this.pinyin, 211 | this.simplified, 212 | this.traditional], 213 | 'visible'); 214 | this.setVisibility([this.english, 215 | this.translations], 216 | 'hidden'); 217 | } 218 | } else if (cards.showHalfCard == CHINESE_HALF) { 219 | this.setVisibility([this.pinyin, 220 | this.simplified, 221 | this.traditional], 222 | 'visible'); 223 | this.setVisibility([this.english, 224 | this.translations], 225 | 'hidden'); 226 | } else { 227 | this.setVisibility([this.pinyin, 228 | this.simplified, 229 | this.traditional], 230 | 'hidden'); 231 | this.setVisibility([this.english, 232 | this.translations], 233 | 'visible'); 234 | } 235 | this.node.style.visibility = 'visible'; 236 | } 237 | 238 | this.hide = function () { 239 | this.node.style.visibility = 'hidden'; 240 | this.setVisibility([this.pinyin, 241 | this.simplified, 242 | this.traditional], 243 | 'hidden'); 244 | this.setVisibility([this.english, 245 | this.translations], 246 | 'hidden'); 247 | } 248 | 249 | this.setVisibility = function (nodes, state) { 250 | for (n = 0; n < nodes.length; n++) { 251 | nodes[n].style.visibility = state; 252 | } 253 | } 254 | } 255 | -------------------------------------------------------------------------------- /ch10/jscript/sorttable.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apress/network-prog-with-go-2e/d8b0fd1f54fd2710e8e8a65ad804cbc2e7f63831/ch10/jscript/sorttable.js -------------------------------------------------------------------------------- /ch10/pinyinformatter.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | ) 7 | 8 | func PinyinFormatter(args ...any) string { 9 | ok := false 10 | var s string 11 | if len(args) == 1 { 12 | s, ok = args[0].(string) 13 | } 14 | if !ok { 15 | s = fmt.Sprint(args...) 16 | } 17 | fmt.Println("Formatting func " + s) 18 | // the string may consist of several pinyin words 19 | // each one needs to be changed separately and then 20 | // added back together 21 | words := strings.Fields(s) 22 | 23 | for n, word := range words { 24 | // convert "u:" to "ü" if present 25 | uColon := strings.Index(word, "u:") 26 | if uColon != -1 { 27 | parts := strings.SplitN(word, "u:", 2) 28 | word = parts[0] + "ü" + parts[1] 29 | } 30 | println(word) 31 | // get last character, will be the tone if present 32 | chars := []rune(word) 33 | tone := chars[len(chars)-1] 34 | if tone == '5' { 35 | // there is no accent for tone 5 36 | words[n] = string(chars[0 : len(chars)-1]) 37 | println("lost accent on", words[n]) 38 | continue 39 | } 40 | if tone < '1' || tone > '4' { 41 | // not a tone value 42 | continue 43 | } 44 | words[n] = addAccent(word, int(tone)) 45 | } 46 | s = strings.Join(words, ` `) 47 | return s 48 | } 49 | 50 | var ( 51 | // maps 'a1' to '\u0101' etc 52 | aAccent = map[int]rune{ 53 | '1': '\u0101', 54 | '2': '\u00e1', 55 | '3': '\u01ce', 56 | '4': '\u00e0'} 57 | eAccent = map[int]rune{ 58 | '1': '\u0113', 59 | '2': '\u00e9', 60 | '3': '\u011b', 61 | '4': '\u00e8'} 62 | iAccent = map[int]rune{ 63 | '1': '\u012b', 64 | '2': '\u00ed', 65 | '3': '\u01d0', 66 | '4': '\u00ec'} 67 | oAccent = map[int]rune{ 68 | '1': '\u014d', 69 | '2': '\u00f3', 70 | '3': '\u01d2', 71 | '4': '\u00f2'} 72 | uAccent = map[int]rune{ 73 | '1': '\u016b', 74 | '2': '\u00fa', 75 | '3': '\u01d4', 76 | '4': '\u00f9'} 77 | üAccent = map[int]rune{ 78 | '1': 'ǖ', 79 | '2': 'ǘ', 80 | '3': 'ǚ', 81 | '4': 'ǜ'} 82 | ) 83 | 84 | func addAccent(word string, tone int) string { 85 | /* 86 | * Based on "Where do the tone marks go?" 87 | * at http://www.pinyin.info/rules/where.html 88 | */ 89 | 90 | n := strings.Index(word, "a") 91 | if n != -1 { 92 | aAcc := aAccent[tone] 93 | // replace 'a' with its tone version 94 | word = word[0:n] + string(aAcc) + word[(n+1):len(word)-1] 95 | } else { 96 | n := strings.Index(word, "e") 97 | if n != -1 { 98 | eAcc := eAccent[tone] 99 | word = word[0:n] + string(eAcc) + 100 | word[(n+1):len(word)-1] 101 | } else { 102 | n = strings.Index(word, "ou") 103 | if n != -1 { 104 | oAcc := oAccent[tone] 105 | word = word[0:n] + string(oAcc) + "u" + 106 | word[(n+2):len(word)-1] 107 | } else { 108 | chars := []rune(word) 109 | length := len(chars) 110 | // put tone onthe last vowel 111 | L: 112 | for n := range chars { 113 | m := length - n - 1 114 | switch chars[m] { 115 | case 'i': 116 | chars[m] = iAccent[tone] 117 | break L 118 | case 'o': 119 | chars[m] = oAccent[tone] 120 | break L 121 | case 'u': 122 | chars[m] = uAccent[tone] 123 | break L 124 | case 'ü': 125 | chars[m] = üAccent[tone] 126 | break L 127 | default: 128 | } 129 | } 130 | word = string(chars[0 : len(chars)-1]) 131 | } 132 | } 133 | } 134 | return word 135 | } 136 | -------------------------------------------------------------------------------- /ch10/server.go: -------------------------------------------------------------------------------- 1 | /* Server 2 | */ 3 | 4 | package main 5 | 6 | import ( 7 | "fmt" 8 | "html/template" 9 | "log" 10 | "net/http" 11 | "os" 12 | ) 13 | 14 | const ( 15 | DefaultSet = "common_words" 16 | DefaultAmount = "Random" 17 | ActionShow = "Show cards in set" 18 | ActionList = "List words in set" 19 | ActionUnknown = "Unknown action" 20 | URLFlashCardSetsPath = "flashcardSets" 21 | FlashCardPage = "flashcards.html" 22 | ListFlashCardPage = "list" + FlashCardPage 23 | ShowFlashCardPage = "show" + FlashCardPage 24 | ListWordsPage = "listwords.html" 25 | CardOrderSequential = "Sequential" 26 | CardOrderRandom = "Random" 27 | ) 28 | 29 | var showHalf = map[string]string{ 30 | "Random": "RANDOM_HALF", 31 | "English": "ENGLISH_HALF", 32 | "Chinese": "CHINESE_HALF", 33 | } 34 | 35 | func main() { 36 | if len(os.Args) != 2 { 37 | log.Fatalln("Usage: ", os.Args[0], ":port") 38 | } 39 | port := os.Args[1] 40 | 41 | http.HandleFunc("/", listFlashCards) 42 | fileServer := http.StripPrefix("/jscript/", http.FileServer(http.Dir("jscript"))) 43 | http.Handle("/jscript/", fileServer) 44 | fileServer = http.StripPrefix("/html/", http.FileServer(http.Dir("html"))) 45 | http.Handle("/html/", fileServer) 46 | fileServer = http.StripPrefix("/css/", http.FileServer(http.Dir("css"))) 47 | http.Handle("/css/", fileServer) 48 | 49 | http.HandleFunc("/"+FlashCardPage, listFlashCards) 50 | http.HandleFunc("/"+URLFlashCardSetsPath, manageFlashCards) 51 | 52 | // deliver requests to the handlers 53 | err := http.ListenAndServe(port, nil) 54 | checkError(err) 55 | } 56 | 57 | func listFlashCards(rw http.ResponseWriter, req *http.Request) { 58 | flashCardsNames := ListFlashCardsNames() 59 | t, err := template.ParseFiles("html/" + ListFlashCardPage) 60 | 61 | if err != nil { 62 | httpErrorHandler(rw, err) 63 | return 64 | } 65 | 66 | t.Execute(rw, flashCardsNames) 67 | } 68 | 69 | /* 70 | * Called from listflashcards.html on form submission 71 | */ 72 | func manageFlashCards(rw http.ResponseWriter, req *http.Request) { 73 | set := req.FormValue("flashcardSets") 74 | order := req.FormValue("order") 75 | action := req.FormValue("submit") 76 | half := req.FormValue("half") 77 | 78 | //if unset 79 | //http://localhost:8000/flashcardSets?flashcardSets=common_words&order=Random&half=Random&submit=Show+cards+in+set 80 | if len(set) == 0 { 81 | set = DefaultSet 82 | order = DefaultAmount 83 | action = ActionShow 84 | half = DefaultAmount 85 | } 86 | 87 | cardname := URLFlashCardSetsPath + "/" + set 88 | 89 | fmt.Printf("Set %s, order %s, action %s, half %s, cardname %s\n", set, order, action, half, cardname) 90 | 91 | switch action { 92 | case ActionShow: 93 | showFlashCards(rw, cardname, order, half) 94 | case ActionList: 95 | listWords(rw, cardname) 96 | default: 97 | fmt.Println(ActionUnknown) 98 | } 99 | } 100 | 101 | func showFlashCards(rw http.ResponseWriter, cardname, order, half string) { 102 | cards := new(FlashCards) 103 | content, err := os.Open(cardname) 104 | checkError(err) 105 | LoadJSON(content, &cards) 106 | 107 | switch order { 108 | case CardOrderSequential: 109 | cards.CardOrder = "SEQUENTIAL" 110 | default: 111 | cards.CardOrder = "RANDOM" 112 | } 113 | 114 | if v, ok := showHalf[half]; ok { 115 | cards.ShowHalf = v 116 | } else { 117 | cards.ShowHalf = showHalf["Chinese"] 118 | } 119 | 120 | fmt.Printf("Loading card %s, half %s, loaded # of %d, card name %s\n", cardname, half, len(cards.Cards), cards.Name) 121 | 122 | t, err := template.New(ShowFlashCardPage).Funcs(template.FuncMap{"pinyin": PinyinFormatter}).ParseFiles("html/" + ShowFlashCardPage) 123 | 124 | if err != nil { 125 | httpErrorHandler(rw, err) 126 | return 127 | } 128 | 129 | err = t.Execute(rw, cards) 130 | 131 | if err != nil { 132 | httpErrorHandler(rw, err) 133 | return 134 | } 135 | } 136 | 137 | func listWords(rw http.ResponseWriter, cardname string) { 138 | cards := new(FlashCards) 139 | content, err := os.Open(cardname) 140 | checkError(err) 141 | LoadJSON(content, &cards) 142 | 143 | fmt.Printf("Loading card name %s, loaded cards %d, card name %s\n", cardname, len(cards.Cards), cards.Name) 144 | 145 | t, err := template.New(ListWordsPage).Funcs(template.FuncMap{"pinyin": PinyinFormatter}).ParseFiles("html/" + ListWordsPage) 146 | 147 | if err != nil { 148 | httpErrorHandler(rw, err) 149 | return 150 | } 151 | 152 | err = t.Execute(rw, cards) 153 | 154 | if err != nil { 155 | httpErrorHandler(rw, err) 156 | return 157 | } 158 | } 159 | 160 | func httpErrorHandler(rw http.ResponseWriter, err error) { 161 | http.Error(rw, err.Error(), http.StatusInternalServerError) 162 | } 163 | 164 | func checkError(err error) { 165 | if err != nil { 166 | log.Fatalln("Fatal error ", err.Error()) 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /ch11/escapestring.go: -------------------------------------------------------------------------------- 1 | /* 2 | * This program serves a file in preformatted, code layout 3 | * Useful for showing program text, properly escaping special 4 | * characters like '<', '>' and '&' 5 | */ 6 | 7 | package main 8 | 9 | import ( 10 | "fmt" 11 | "html" 12 | "log" 13 | "net/http" 14 | "os" 15 | ) 16 | 17 | func main() { 18 | http.HandleFunc("/", escapeString) 19 | err := http.ListenAndServe(":8080", nil) 20 | checkError(err) 21 | } 22 | func escapeString(rw http.ResponseWriter, req *http.Request) { 23 | fmt.Println(req.URL.Path) 24 | bytes, err := os.ReadFile("." + req.URL.Path) 25 | if err != nil { 26 | rw.WriteHeader(http.StatusNotFound) 27 | return 28 | } 29 | escapedStr := html.EscapeString(string(bytes)) 30 | htmlText := "
" +
31 | 		escapedStr +
32 | 		" 
" 33 | rw.Write([]byte(htmlText)) 34 | } 35 | func checkError(err error) { 36 | if err != nil { 37 | log.Fatalln("Error ", err.Error()) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /ch11/readhtml.go: -------------------------------------------------------------------------------- 1 | /* Read HTML 2 | */ 3 | package main 4 | 5 | import ( 6 | "fmt" 7 | "golang.org/x/net/html" 8 | "io" 9 | "log" 10 | "os" 11 | "strings" 12 | ) 13 | 14 | func main() { 15 | if len(os.Args) != 2 { 16 | log.Fatalln("Usage: ", os.Args[0], "file") 17 | } 18 | file := os.Args[1] 19 | bytes, err := os.ReadFile(file) 20 | checkError(err) 21 | r := strings.NewReader(string(bytes)) 22 | z := html.NewTokenizer(r) 23 | depth := 0 24 | for { 25 | tt := z.Next() 26 | for n := 0; n < depth; n++ { 27 | fmt.Print(" ") 28 | } 29 | switch tt { 30 | case html.ErrorToken: 31 | if z.Err() == io.EOF { 32 | fmt.Println("EOF") 33 | } else { 34 | fmt.Println("Error ", z.Err().Error()) 35 | } 36 | os.Exit(0) 37 | case html.TextToken: 38 | fmt.Println("Text: \"" + z.Token().String() + "\"") 39 | case html.StartTagToken, html.EndTagToken: 40 | fmt.Println("Tag: \"" + z.Token().String() + "\"") 41 | if tt == html.StartTagToken { 42 | depth++ 43 | } else { 44 | depth-- 45 | } 46 | } 47 | } 48 | } 49 | func checkError(err error) { 50 | if err != nil { 51 | log.Fatalln("Fatal error ", err.Error()) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /ch11/sample.html: -------------------------------------------------------------------------------- 1 | 2 |    3 |      Test HTML 4 |    5 |    6 |     

Header one

7 |     

8 |       Test para 9 |     

10 |    11 | 12 | -------------------------------------------------------------------------------- /ch12/marshal.go: -------------------------------------------------------------------------------- 1 | /* Marshal 2 | */ 3 | package main 4 | 5 | import ( 6 | "encoding/xml" 7 | "fmt" 8 | ) 9 | 10 | type Person struct { 11 | XMLName xml.Name `xml:"person"` 12 | Name Name `xml:"name"` 13 | Email []Email `xml:"email"` 14 | } 15 | type Name struct { 16 | Family string `xml:"family"` 17 | Personal string `xml:"personal"` 18 | } 19 | type Email struct { 20 | Kind string "attr" 21 | Address string "chardata" 22 | } 23 | 24 | func main() { 25 | person := Person{ 26 | Name: Name{Family: "Newmarch", Personal: "Jan"}, 27 | Email: []Email{Email{Kind: "home", Address: "jan"}, 28 | Email{Kind: "work", Address: "jan"}}} 29 | buff, _ := xml.Marshal(person) 30 | fmt.Println(string(buff)) 31 | } 32 | -------------------------------------------------------------------------------- /ch12/parsexml.go: -------------------------------------------------------------------------------- 1 | /* Parse XML 2 | */ 3 | package main 4 | 5 | import ( 6 | "encoding/xml" 7 | "fmt" 8 | "io/ioutil" 9 | "log" 10 | "os" 11 | "strings" 12 | ) 13 | 14 | func main() { 15 | if len(os.Args) != 2 { 16 | log.Fatalln("Usage: ", os.Args[0], "file") 17 | } 18 | file := os.Args[1] 19 | bytes, err := ioutil.ReadFile(file) 20 | checkError(err) 21 | r := strings.NewReader(string(bytes)) 22 | parser := xml.NewDecoder(r) 23 | depth := 0 24 | for { 25 | token, err := parser.Token() 26 | if err != nil { 27 | break 28 | } 29 | switch elmt := token.(type) { 30 | case xml.StartElement: 31 | name := elmt.Name.Local 32 | printElmt(name+":start", depth) 33 | depth++ 34 | case xml.EndElement: 35 | depth-- 36 | name := elmt.Name.Local 37 | printElmt(name+":end", depth) 38 | case xml.CharData: 39 | printElmt(string([]byte(elmt)), depth) 40 | case xml.Comment: 41 | printElmt("Comment", depth) 42 | case xml.ProcInst: 43 | printElmt("ProcInst", depth) 44 | case xml.Directive: 45 | printElmt("Directive", depth) 46 | default: 47 | fmt.Println("Unknown") 48 | } 49 | } 50 | } 51 | func printElmt(s string, depth int) { 52 | slimS := strings.TrimSpace(s) 53 | if len(slimS) == 0 { 54 | return 55 | } 56 | for n := 0; n < depth; n++ { 57 | fmt.Print(" ") 58 | } 59 | fmt.Println(slimS) 60 | } 61 | func checkError(err error) { 62 | if err != nil { 63 | log.Fatalln("Fatal error ", err.Error()) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /ch12/person.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Newmarch 4 | Jan 5 | 6 | 7 | jan@newmarch.name 8 | 9 | 10 | j.newmarch@boxhill.edu.au 11 | 12 | 13 | -------------------------------------------------------------------------------- /ch12/unmarshal.go: -------------------------------------------------------------------------------- 1 | /* Unmarshal 2 | */ 3 | package main 4 | 5 | import ( 6 | "encoding/xml" 7 | "fmt" 8 | "log" 9 | ) 10 | 11 | type Person struct { 12 | XMLName Name `xml:"person"` 13 | Name Name `xml:"name"` 14 | Email []Email `xml:"email"` 15 | } 16 | 17 | type Name struct { 18 | Family string `xml:"family"` 19 | Personal string `xml:"personal"` 20 | } 21 | 22 | type Email struct { 23 | Type string `xml:"type,attr"` 24 | Address string `xml:",chardata"` 25 | } 26 | 27 | func main() { 28 | str := ` 29 | 30 | 31 | Newmarch 32 | Jan 33 | 34 | 35 | jan@newmarch.name 36 | 37 | 38 | j.newmarch@boxhill.edu.au 39 | 40 | ` 41 | var person Person 42 | err := xml.Unmarshal([]byte(str), &person) 43 | checkError(err) 44 | // now use the person structure e.g. 45 | fmt.Println("Family name: \"" + person.Name.Family + "\"") 46 | for _, email := range person.Email { 47 | fmt.Println(email) 48 | } 49 | } 50 | func checkError(err error) { 51 | if err != nil { 52 | log.Fatalln("Fatal error ", err.Error()) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /ch13/arithclient.go: -------------------------------------------------------------------------------- 1 | /* ArithClient 2 | */ 3 | package main 4 | 5 | import ( 6 | "fmt" 7 | "log" 8 | "net/rpc" 9 | "os" 10 | ) 11 | 12 | type Args struct { 13 | A, B int 14 | } 15 | type Quotient struct { 16 | Quo, Rem int 17 | } 18 | 19 | func main() { 20 | if len(os.Args) != 2 { 21 | fmt.Println("Usage: ", os.Args[0], "server") 22 | os.Exit(1) 23 | } 24 | serverAddress := os.Args[1] 25 | client, err := rpc.DialHTTP("tcp", serverAddress+":1234") 26 | if err != nil { 27 | log.Fatal("dialing:", err) 28 | } 29 | // Synchronous call 30 | args := Args{17, 8} 31 | var reply int 32 | err = client.Call("Arith.Multiply", args, &reply) 33 | if err != nil { 34 | log.Fatal("arith error:", err) 35 | } 36 | fmt.Printf("Arith: %d*%d=%d\n", args.A, args.B, reply) 37 | var quot Quotient 38 | err = client.Call("Arith.Divide", args, ") 39 | if err != nil { 40 | log.Fatal("arith error:", err) 41 | } 42 | fmt.Printf("Arith: %d/%d=%d remainder %d\n", args.A, args.B, 43 | quot.Quo, quot.Rem) 44 | } 45 | -------------------------------------------------------------------------------- /ch13/arithserver.go: -------------------------------------------------------------------------------- 1 | /* ArithServer 2 | */ 3 | package main 4 | 5 | import ( 6 | "errors" 7 | "fmt" 8 | "net/http" 9 | "net/rpc" 10 | ) 11 | 12 | type Values struct { 13 | A, B int 14 | } 15 | type Quotient struct { 16 | Quo, Rem int 17 | } 18 | type Arith int 19 | 20 | func (t *Arith) Multiply(args *Values, reply *int) error { 21 | *reply = args.A * args.B 22 | return nil 23 | } 24 | func (t *Arith) Divide(args *Values, quo *Quotient) error { 25 | if args.B == 0 { 26 | return errors.New("divide by zero") 27 | } 28 | quo.Quo = args.A / args.B 29 | quo.Rem = args.A % args.B 30 | return nil 31 | } 32 | func main() { 33 | arith := new(Arith) 34 | rpc.Register(arith) 35 | rpc.HandleHTTP() 36 | err := http.ListenAndServe(":1234", nil) 37 | if err != nil { 38 | fmt.Println(err.Error()) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /ch13/jsonarithclient.go: -------------------------------------------------------------------------------- 1 | /* JSONArithCLient 2 | */ 3 | package main 4 | 5 | import ( 6 | "fmt" 7 | "log" 8 | "net/rpc/jsonrpc" 9 | "os" 10 | ) 11 | 12 | type Args struct { 13 | A, B int 14 | } 15 | type Quotient struct { 16 | Quo, Rem int 17 | } 18 | 19 | func main() { 20 | if len(os.Args) != 2 { 21 | log.Fatalln("Usage: ", os.Args[0], "server:port") 22 | } 23 | service := os.Args[1] 24 | client, err := jsonrpc.Dial("tcp", service) 25 | if err != nil { 26 | log.Fatalln("dialing:", err) 27 | } 28 | // Synchronous call 29 | args := Args{17, 8} 30 | var reply int 31 | err = client.Call("Arith.Multiply", args, &reply) 32 | if err != nil { 33 | log.Fatalln("arith error:", err) 34 | } 35 | fmt.Printf("Arith: %d*%d=%d\n", args.A, args.B, reply) 36 | var quot Quotient 37 | err = client.Call("Arith.Divide", args, ") 38 | if err != nil { 39 | log.Fatalln("arith error:", err) 40 | } 41 | fmt.Printf("Arith: %d/%d=%d remainder %d\n", args.A, args.B, 42 | quot.Quo, quot.Rem) 43 | } 44 | -------------------------------------------------------------------------------- /ch13/jsonarithserver.go: -------------------------------------------------------------------------------- 1 | /* JSONArithServer 2 | */ 3 | package main 4 | 5 | import ( 6 | "errors" 7 | "log" 8 | "net" 9 | "net/rpc" 10 | "net/rpc/jsonrpc" 11 | ) 12 | 13 | type Args struct { 14 | A, B int 15 | } 16 | type Quotient struct { 17 | Quo, Rem int 18 | } 19 | type Arith int 20 | 21 | func (t *Arith) Multiply(args *Args, reply *int) error { 22 | *reply = args.A * args.B 23 | return nil 24 | } 25 | func (t *Arith) Divide(args *Args, quo *Quotient) error { 26 | if args.B == 0 { 27 | return errors.New("divide by zero") 28 | } 29 | quo.Quo = args.A / args.B 30 | quo.Rem = args.A % args.B 31 | return nil 32 | } 33 | func main() { 34 | arith := new(Arith) 35 | rpc.Register(arith) 36 | tcpAddr, err := net.ResolveTCPAddr("tcp", ":1234") 37 | checkError(err) 38 | listener, err := net.ListenTCP("tcp", tcpAddr) 39 | checkError(err) 40 | /* This works: 41 | rpc.Accept(listener) 42 | */ 43 | /* and so does this: 44 | */ 45 | for { 46 | conn, err := listener.Accept() 47 | if err != nil { 48 | continue 49 | } 50 | jsonrpc.ServeConn(conn) 51 | } 52 | } 53 | func checkError(err error) { 54 | if err != nil { 55 | log.Fatalln("Fatal error ", err.Error()) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /ch13/tcparithclient.go: -------------------------------------------------------------------------------- 1 | /* TCPArithClient 2 | */ 3 | package main 4 | 5 | import ( 6 | "fmt" 7 | "log" 8 | "net/rpc" 9 | "os" 10 | ) 11 | 12 | type Args struct { 13 | A, B int 14 | } 15 | type Quotient struct { 16 | Quo, Rem int 17 | } 18 | 19 | func main() { 20 | if len(os.Args) != 2 { 21 | log.Fatalln("Usage: ", os.Args[0], "server:port") 22 | } 23 | service := os.Args[1] 24 | client, err := rpc.Dial("tcp", service) 25 | if err != nil { 26 | log.Fatalln("dialing:", err) 27 | } 28 | // Synchronous call 29 | args := Args{17, 8} 30 | var reply int 31 | err = client.Call("Arith.Multiply", args, &reply) 32 | if err != nil { 33 | log.Fatalln("arith error:", err) 34 | } 35 | fmt.Printf("Arith: %d*%d=%d\n", args.A, args.B, reply) 36 | var quot Quotient 37 | err = client.Call("Arith.Divide", args, ") 38 | if err != nil { 39 | log.Fatalln("arith error:", err) 40 | } 41 | fmt.Printf("Arith: %d/%d=%d remainder %d\n", args.A, args.B, 42 | quot.Quo, quot.Rem) 43 | } 44 | -------------------------------------------------------------------------------- /ch13/tcparithserver.go: -------------------------------------------------------------------------------- 1 | /* TCPArithServer 2 | */ 3 | package main 4 | 5 | import ( 6 | "errors" 7 | "log" 8 | "net" 9 | "net/rpc" 10 | ) 11 | 12 | type Args struct { 13 | A, B int 14 | } 15 | type Quotient struct { 16 | Quo, Rem int 17 | } 18 | type Arith int 19 | 20 | func (t *Arith) Multiply(args *Args, reply *int) error { 21 | *reply = args.A * args.B 22 | return nil 23 | } 24 | func (t *Arith) Divide(args *Args, quo *Quotient) error { 25 | if args.B == 0 { 26 | return errors.New("divide by zero") 27 | } 28 | quo.Quo = args.A / args.B 29 | quo.Rem = args.A % args.B 30 | return nil 31 | } 32 | func main() { 33 | arith := new(Arith) 34 | rpc.Register(arith) 35 | tcpAddr, err := net.ResolveTCPAddr("tcp", ":1234") 36 | checkError(err) 37 | listener, err := net.ListenTCP("tcp", tcpAddr) 38 | checkError(err) 39 | /* This works: 40 | rpc.Accept(listener) 41 | */ 42 | /* and so does this: 43 | */ 44 | for { 45 | conn, err := listener.Accept() 46 | if err != nil { 47 | continue 48 | } 49 | rpc.ServeConn(conn) 50 | } 51 | } 52 | func checkError(err error) { 53 | if err != nil { 54 | log.Fatalln("Fatal error ", err.Error()) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /ch14/client.go: -------------------------------------------------------------------------------- 1 | /* Client 2 | */ 3 | 4 | package main 5 | 6 | import ( 7 | "encoding/xml" 8 | "fmt" 9 | "io" 10 | "log" 11 | "net/http" 12 | "net/http/httputil" 13 | "net/url" 14 | "os" 15 | "strings" 16 | ) 17 | 18 | const flashcard_xml string = "application/x.flashcards+xml" 19 | const flashcard_json string = "application/x.flashcards+json" 20 | 21 | type FlashcardSets struct { 22 | XMLName string `xml:"cardsets"` 23 | CardSet []CardSet `xml:"cardset"` 24 | } 25 | 26 | type CardSet struct { 27 | XMLName string `xml:"cardset"` 28 | Name string `xml:"name"` 29 | Link string `xml:"href,attr"` 30 | Cards []Card `xml:"card"` 31 | } 32 | 33 | type Card struct { 34 | Name string `xml:"name"` 35 | Link string `xml:"href,attr"` 36 | } 37 | 38 | func getter(url *url.URL, client *http.Client, acceptType string) *http.Response { 39 | request, err := http.NewRequest("GET", url.String(), nil) 40 | checkError(err) 41 | 42 | if acceptType != "" { 43 | request.Header.Add("Accept", flashcard_xml) 44 | } 45 | response, err := client.Do(request) 46 | checkError(err) 47 | if response.StatusCode != http.StatusOK { 48 | log.Fatalln(err, response) 49 | } 50 | 51 | fmt.Println("The response header is") 52 | b, _ := httputil.DumpResponse(response, false) 53 | fmt.Print(string(b)) 54 | return response 55 | } 56 | 57 | func getOneFlashcard(url *url.URL, client *http.Client) string { 58 | // Get the card as a string, don't do anything with it 59 | response := getter(url, client, "") 60 | 61 | body, err := io.ReadAll(response.Body) 62 | checkError(err) 63 | content := string(body[:]) 64 | //fmt.Printf("Body is %s", content) 65 | 66 | return content 67 | } 68 | 69 | func getOneFlashcardSet(url *url.URL, client *http.Client) CardSet { 70 | // Get one set of cards 71 | response := getter(url, client, flashcard_xml) 72 | 73 | body, err := io.ReadAll(response.Body) 74 | content := string(body[:]) 75 | fmt.Printf("Body is %s", content) 76 | 77 | var sets CardSet 78 | contentType := getContentType(response) 79 | if contentType == "XML" { 80 | 81 | err = xml.Unmarshal(body, &sets) 82 | checkError(err) 83 | fmt.Println("XML: ", sets) 84 | return sets 85 | } 86 | /* else if contentType == "JSON" { 87 | var sets FlashcardSetsJson 88 | err = json.Unmarshal(body, &sets) 89 | checkError(err) 90 | fmt.Println("JSON: ", sets) 91 | } 92 | */ 93 | return sets 94 | } 95 | 96 | func getFlashcardSets(url *url.URL, client *http.Client) FlashcardSets { 97 | // Get the toplevel / 98 | response := getter(url, client, flashcard_xml) 99 | 100 | body, err := io.ReadAll(response.Body) 101 | content := string(body[:]) 102 | fmt.Printf("Body is %s", content) 103 | 104 | var sets FlashcardSets 105 | contentType := getContentType(response) 106 | if contentType == "XML" { 107 | err = xml.Unmarshal(body, &sets) 108 | checkError(err) 109 | fmt.Println("XML: ", sets) 110 | return sets 111 | } 112 | return sets 113 | } 114 | 115 | func createFlashcardSet(url1 *url.URL, client *http.Client, name string) string { 116 | data := make(url.Values) 117 | data[`name`] = []string{name} 118 | response, err := client.PostForm(url1.String(), data) 119 | checkError(err) 120 | if response.StatusCode != http.StatusCreated { 121 | fmt.Println(`Error: `, response.Status) 122 | return `` 123 | } 124 | body, err := io.ReadAll(response.Body) 125 | content := string(body[:]) 126 | return content 127 | } 128 | 129 | func main() { 130 | if len(os.Args) != 2 { 131 | log.Fatalln("Usage: ", os.Args[0], "http://host:port/page") 132 | } 133 | url, err := url.Parse(os.Args[1]) 134 | checkError(err) 135 | 136 | client := &http.Client{} 137 | 138 | // Step 1: get a list of flashcard sets 139 | flashcardSets := getFlashcardSets(url, client) 140 | fmt.Println("Step 1: ", flashcardSets) 141 | 142 | // Step 2: try to create a new flashcard set 143 | new_url := createFlashcardSet(url, client, `NewSet`) 144 | fmt.Println("Step 2: New flashcard set has URL: ", new_url) 145 | 146 | // Step 3: using the first flashcard set, 147 | // get the list of cards in it 148 | set_url, _ := url.Parse(os.Args[1] + flashcardSets.CardSet[0].Link) 149 | 150 | fmt.Println("Asking for flashcard set URL: ", set_url.String()) 151 | oneFlashcardSet := getOneFlashcardSet(set_url, client) 152 | fmt.Println("Step 3:", oneFlashcardSet) 153 | 154 | // Step 4: get the contents of one flashcard 155 | // be lazy, just get as text/plain and 156 | // don't do anything with it 157 | card_url, _ := url.Parse(os.Args[1] + oneFlashcardSet.Cards[0].Link) 158 | fmt.Println("Asking for URL: ", card_url.String()) 159 | oneFlashcard := getOneFlashcard(card_url, client) 160 | fmt.Println("Step 4", oneFlashcard) 161 | } 162 | 163 | func getContentType(response *http.Response) string { 164 | contentType := response.Header.Get("Content-Type") 165 | if strings.Contains(contentType, flashcard_xml) { 166 | return "XML" 167 | } 168 | if strings.Contains(contentType, flashcard_json) { 169 | return "JSON" 170 | } 171 | return "" 172 | } 173 | 174 | func checkError(err error) { 175 | if err != nil { 176 | log.Fatalln("Fatal error ", err.Error()) 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /ch14/clientjson.go: -------------------------------------------------------------------------------- 1 | /* ClientGet 2 | */ 3 | 4 | package main 5 | 6 | import ( 7 | "io" 8 | "encoding/json" 9 | "fmt" 10 | "log" 11 | "net/http" 12 | "net/http/httputil" 13 | "net/url" 14 | "os" 15 | "strings" 16 | ) 17 | 18 | const flashcard_xml string = "application/x.flashcards+xml" 19 | const flashcard_json string = "application/x.flashcards+json" 20 | 21 | type FlashcardSets struct { 22 | XMLName string `xml:"cardsets"` 23 | CardSet []CardSet `xml:"cardset"` 24 | } 25 | 26 | type CardSet struct { 27 | XMLName string `xml:"cardset"` 28 | Name string `xml:"name"` 29 | Link string `xml:"href,attr"` 30 | Cards []Card `xml:"card"` 31 | } 32 | 33 | type Card struct { 34 | Name string `xml:"name"` 35 | Link string `xml:"href,attr"` 36 | } 37 | 38 | type FlashcardSetsJson struct { 39 | CardSet []CardSetJson `json:"cardsets"` 40 | } 41 | type CardSetJson struct { 42 | Name string `json:"name"` 43 | Link string `json:"@id"` 44 | Cards []CardJson `json:"cardset,omitempty"` 45 | } 46 | type CardJson struct { 47 | Name string `json:"name"` 48 | Link string `json:"@id"` 49 | } 50 | 51 | func getOneFlashcard(url *url.URL, client *http.Client) string { 52 | // Get the card as a string, don't do anything with it 53 | request, err := http.NewRequest("GET", url.String(), nil) 54 | checkError(err) 55 | 56 | response, err := client.Do(request) 57 | checkError(err) 58 | if response.StatusCode != http.StatusOK { 59 | fmt.Println(response.Status) 60 | fmt.Println(response.Header) 61 | checkError(err) 62 | } 63 | 64 | fmt.Println("The response header is") 65 | b, _ := httputil.DumpResponse(response, false) 66 | fmt.Print(string(b)) 67 | 68 | body, err := io.ReadAll(response.Body) 69 | content := string(body[:]) 70 | 71 | return content 72 | } 73 | 74 | func getOneFlashcardSet(url *url.URL, client *http.Client) CardSetJson { 75 | // Get one set of cards 76 | request, err := http.NewRequest("GET", url.String(), nil) 77 | checkError(err) 78 | 79 | // only accept our media types 80 | request.Header.Add("Accept", flashcard_json) 81 | response, err := client.Do(request) 82 | checkError(err) 83 | if response.StatusCode != http.StatusOK { 84 | fmt.Println(response.Status) 85 | fmt.Println(response.Header) 86 | checkError(err) 87 | } 88 | 89 | fmt.Println("The response header is") 90 | b, _ := httputil.DumpResponse(response, false) 91 | fmt.Print(string(b)) 92 | 93 | body, err := io.ReadAll(response.Body) 94 | content := string(body[:]) 95 | fmt.Printf("Body is %s", content) 96 | 97 | var sets CardSetJson 98 | contentType := getContentType(response) 99 | if contentType == "JSON" { 100 | err = json.Unmarshal(body, &sets) 101 | checkError(err) 102 | fmt.Println("JSON: ", sets) 103 | } 104 | 105 | return sets 106 | } 107 | 108 | func getFlashcardSets(url *url.URL, client *http.Client) FlashcardSetsJson { 109 | // Get the toplevel / 110 | request, err := http.NewRequest("GET", url.String(), nil) 111 | checkError(err) 112 | 113 | // only accept our media types 114 | request.Header.Add("Accept", flashcard_json) 115 | response, err := client.Do(request) 116 | checkError(err) 117 | if response.StatusCode != http.StatusOK { 118 | fmt.Println(response.Status) 119 | fmt.Println(response.Header) 120 | 121 | checkError(err) 122 | } 123 | 124 | fmt.Println("The response header is") 125 | b, _ := httputil.DumpResponse(response, false) 126 | fmt.Print(string(b)) 127 | 128 | body, err := io.ReadAll(response.Body) 129 | content := string(body[:]) 130 | fmt.Printf("Body is %s", content) 131 | 132 | var sets FlashcardSetsJson 133 | contentType := getContentType(response) 134 | if contentType == "JSON" { 135 | err = json.Unmarshal(body, &sets) 136 | checkError(err) 137 | fmt.Println("JSON: ", sets) 138 | } 139 | return sets 140 | } 141 | 142 | func main() { 143 | if len(os.Args) != 2 { 144 | log.Fatalln("Usage: ", os.Args[0], "http://host:port/page") 145 | } 146 | url, err := url.Parse(os.Args[1]) 147 | checkError(err) 148 | 149 | client := &http.Client{} 150 | 151 | flashcardSets := FlashcardSetsJson{} 152 | flashcardSets.CardSet = []CardSetJson{CardSetJson{}} 153 | flashcardSets.CardSet[0].Name = `n1` 154 | flashcardSets.CardSet[0].Link = `l1` 155 | //flashcardSets.CardSet[0].Cards = []CardJson{CardJson{Name: `n`, Link: `l`}} 156 | bytes, _ := json.Marshal(flashcardSets) 157 | fmt.Println(string(bytes[:])) 158 | 159 | url = url 160 | client = client 161 | 162 | // Step 1: get a list of flashcard sets 163 | flashcardSets = getFlashcardSets(url, client) 164 | fmt.Println("Step1, Cardsets are: ", flashcardSets) 165 | 166 | // Step 2: using the first flashcard set, 167 | // get the list of cards in it 168 | set_url, _ := url.Parse(os.Args[1] + flashcardSets.CardSet[0].Link) 169 | oneFlashcardSet := getOneFlashcardSet(set_url, client) 170 | fmt.Println("Step 2: ", oneFlashcardSet) 171 | 172 | // Step 3: get the contents of one flashcard 173 | // be lazy, just get as text/plain and 174 | // don't do anything with it 175 | card_url, _ := url.Parse(os.Args[1] + oneFlashcardSet.Cards[0].Link) 176 | fmt.Println("Asking for URL: ", card_url.String()) 177 | oneFlashcard := getOneFlashcard(card_url, client) 178 | fmt.Println("Step 3", oneFlashcard) 179 | } 180 | 181 | func getContentType(response *http.Response) string { 182 | contentType := response.Header.Get("Content-Type") 183 | if strings.Contains(contentType, flashcard_xml) { 184 | return "XML" 185 | } 186 | if strings.Contains(contentType, flashcard_json) { 187 | return "JSON" 188 | } 189 | return "" 190 | } 191 | 192 | func checkError(err error) { 193 | if err != nil { 194 | log.Fatalln("Fatal error ", err.Error()) 195 | } 196 | } 197 | -------------------------------------------------------------------------------- /ch14/flashcardsets/CommonWords/你好: -------------------------------------------------------------------------------- 1 | {"Simplified": "\u4f60\u597d", "Dictionary": {"Entries": [{"Traditional": "\u4f60\u597d", "Pinyin": "ni3 hao3", "Translations": ["hello", "hi", "how are you?"], "Simplified": "\u4f60\u597d"}]}, "English": "hello"} -------------------------------------------------------------------------------- /ch14/flashcardsets/CommonWords/你好吗: -------------------------------------------------------------------------------- 1 | {"Simplified": "\u4f60\u597d\u5417", "Dictionary": {"Entries": [{"Traditional": "\u4f60\u597d\u55ce", "Pinyin": "ni3 hao3 ma5", "Translations": ["How are you?", "Are you well?"], "Simplified": "\u4f60\u597d\u5417"}]}, "English": "How are you?"} -------------------------------------------------------------------------------- /ch14/flashcardsets/CommonWords/再见: -------------------------------------------------------------------------------- 1 | {"Simplified": "\u518d\u89c1", "Dictionary": {"Entries": [{"Traditional": "\u518d\u898b", "Pinyin": "zai4 jian4", "Translations": ["goodbye", "see you again later"], "Simplified": "\u518d\u89c1"}]}, "English": "goodbye"} -------------------------------------------------------------------------------- /ch14/flashcardsets/CommonWords/厕所: -------------------------------------------------------------------------------- 1 | {"Simplified": "\u5395\u6240", "Dictionary": {"Entries": [{"Traditional": "\u5ec1\u6240", "Pinyin": "ce4 suo3", "Translations": ["toilet", "lavatory", "CL:\u9593|\u95f4[jian1],\u8655|\u5904[chu4]"], "Simplified": "\u5395\u6240"}]}, "English": "toilet"} -------------------------------------------------------------------------------- /ch14/flashcardsets/CommonWords/喂: -------------------------------------------------------------------------------- 1 | {"Simplified": "\u5582", "Dictionary": {"Entries": [{"Traditional": "\u5582", "Pinyin": "wei4", "Translations": ["hello (interj., esp. on telephone)", "hey", "to feed (sb or some animal)"], "Simplified": "\u5582"}]}, "English": "hello (interj., esp. on telephone)"} -------------------------------------------------------------------------------- /ch14/flashcardsets/CommonWords/好: -------------------------------------------------------------------------------- 1 | {"Simplified": "\u597d", "Dictionary": {"Entries": [{"Traditional": "\u597d", "Pinyin": "hao3", "Translations": ["good", "well", "proper", "good to", "easy to", "very", "so", "(suffix indicating completion or readiness)"], "Simplified": "\u597d"}]}, "English": "good"} -------------------------------------------------------------------------------- /ch14/flashcardsets/CommonWords/谢谢: -------------------------------------------------------------------------------- 1 | {"Simplified": "\u8c22\u8c22", "Dictionary": {"Entries": [{"Traditional": "\u8b1d\u8b1d", "Pinyin": "xie4 xie5", "Translations": ["to thank", "thanks"], "Simplified": "\u8c22\u8c22"}]}, "English": "thanks"} -------------------------------------------------------------------------------- /ch14/flashcardsets/Lesson04/不: -------------------------------------------------------------------------------- 1 | {"Simplified": "\u4e0d", "Dictionary": {"Entries": [{"Traditional": "\u4e0d", "Pinyin": "bu4", "Translations": ["(negative prefix)", "not", "no"], "Simplified": "\u4e0d"}]}, "English": "no"} -------------------------------------------------------------------------------- /ch14/flashcardsets/Lesson04/也: -------------------------------------------------------------------------------- 1 | {"Simplified": "\u4e5f", "Dictionary": {"Entries": [{"Traditional": "\u4e5f", "Pinyin": "ye3", "Translations": ["also", "too", "(in Classical Chinese) final particle implying affirmation"], "Simplified": "\u4e5f"}]}, "English": "also"} -------------------------------------------------------------------------------- /ch14/flashcardsets/Lesson04/人: -------------------------------------------------------------------------------- 1 | {"Simplified": "\u4eba", "Dictionary": {"Entries": [{"Traditional": "\u4eba", "Pinyin": "ren2", "Translations": ["man", "person", "people", "CL:\u500b|\u4e2a[ge4],\u4f4d[wei4]"], "Simplified": "\u4eba"}]}, "English": "person"} -------------------------------------------------------------------------------- /ch14/flashcardsets/Lesson04/什么: -------------------------------------------------------------------------------- 1 | {"Simplified": "\u4ec0\u4e48", "Dictionary": {"Entries": [{"Traditional": "\u4ec0\u9ebc", "Pinyin": "shen2 me5", "Translations": ["what?", "who?", "something", "anything"], "Simplified": "\u4ec0\u4e48"}]}, "English": "what?"} -------------------------------------------------------------------------------- /ch14/flashcardsets/Lesson04/他: -------------------------------------------------------------------------------- 1 | {"Simplified": "\u4ed6", "Dictionary": {"Entries": [{"Traditional": "\u4ed6", "Pinyin": "ta1", "Translations": ["he or him", "(used for either sex when the sex is unknown or unimportant)", "(used before sb's name for emphasis)", "(used as a meaningless mock object)", "other", "another"], "Simplified": "\u4ed6"}]}, "English": "he or him"} -------------------------------------------------------------------------------- /ch14/flashcardsets/Lesson04/他们: -------------------------------------------------------------------------------- 1 | {"Simplified": "\u4ed6\u4eec", "Dictionary": {"Entries": [{"Traditional": "\u4ed6\u5011", "Pinyin": "ta1 men5", "Translations": ["they"], "Simplified": "\u4ed6\u4eec"}]}, "English": "they"} -------------------------------------------------------------------------------- /ch14/flashcardsets/Lesson04/你: -------------------------------------------------------------------------------- 1 | {"Simplified": "\u4f60", "Dictionary": {"Entries": [{"Traditional": "\u4f60", "Pinyin": "ni3", "Translations": ["you (informal, as opposed to courteous \u60a8[nin2])"], "Simplified": "\u4f60"}]}, "English": "you (informal, as opposed to courteous \u60a8[nin2])"} -------------------------------------------------------------------------------- /ch14/flashcardsets/Lesson04/去: -------------------------------------------------------------------------------- 1 | {"Simplified": "\u53bb", "Dictionary": {"Entries": [{"Traditional": "\u53bb", "Pinyin": "qu4", "Translations": ["to go", "to go to (a place)", "to cause to go or send (sb)", "to remove", "to get rid of", "(when used either before or after a verb) to go in order to do sth", "to be apart from in space or time", "(after a verb of motion indicates movement away from the speaker)", "(used after certain verbs to indicate detachment or separation)", "(of a time or an event etc) just passed or elapsed"], "Simplified": "\u53bb"}]}, "English": "to go"} -------------------------------------------------------------------------------- /ch14/flashcardsets/Lesson04/叫: -------------------------------------------------------------------------------- 1 | {"Simplified": "\u53eb", "Dictionary": {"Entries": [{"Traditional": "\u53eb", "Pinyin": "jiao4", "Translations": ["to shout", "to call", "to order", "to ask", "to be called", "by (indicates agent in the passive mood)"], "Simplified": "\u53eb"}]}, "English": "to be called"} -------------------------------------------------------------------------------- /ch14/flashcardsets/Lesson04/名字: -------------------------------------------------------------------------------- 1 | {"Simplified": "\u540d\u5b57", "Dictionary": {"Entries": [{"Traditional": "\u540d\u5b57", "Pinyin": "ming2 zi5", "Translations": ["name (of a person or thing)", "CL:\u500b|\u4e2a[ge4]"], "Simplified": "\u540d\u5b57"}]}, "English": "name (of a person or thing)"} -------------------------------------------------------------------------------- /ch14/flashcardsets/Lesson04/哪: -------------------------------------------------------------------------------- 1 | {"Simplified": "\u54ea", "Dictionary": {"Entries": [{"Traditional": "\u54ea", "Pinyin": "na3", "Translations": ["how", "which"], "Simplified": "\u54ea"}]}, "English": "which"} -------------------------------------------------------------------------------- /ch14/flashcardsets/Lesson04/囶: -------------------------------------------------------------------------------- 1 | {"Simplified": "\u56f6", "Dictionary": {"Entries": [{"Traditional": "\u56f6", "Pinyin": "guo2", "Translations": ["variant of \u570b|\u56fd", "country"], "Simplified": "\u56f6"}]}, "English": "country"} -------------------------------------------------------------------------------- /ch14/flashcardsets/Lesson04/国家: -------------------------------------------------------------------------------- 1 | {"Simplified": "\u56fd\u5bb6", "Dictionary": {"Entries": [{"Traditional": "\u570b\u5bb6", "Pinyin": "guo2 jia1", "Translations": ["country", "nation", "state", "CL:\u500b|\u4e2a[ge4]"], "Simplified": "\u56fd\u5bb6"}]}, "English": "country"} -------------------------------------------------------------------------------- /ch14/flashcardsets/Lesson04/她: -------------------------------------------------------------------------------- 1 | {"Simplified": "\u5979", "Dictionary": {"Entries": [{"Traditional": "\u5979", "Pinyin": "ta1", "Translations": ["she"], "Simplified": "\u5979"}]}, "English": "she"} -------------------------------------------------------------------------------- /ch14/flashcardsets/Lesson04/她们: -------------------------------------------------------------------------------- 1 | {"Simplified": "\u5979\u4eec", "Dictionary": {"Entries": [{"Traditional": "\u5979\u5011", "Pinyin": "ta1 men5", "Translations": ["they", "them (for females)"], "Simplified": "\u5979\u4eec"}]}, "English": "they"} -------------------------------------------------------------------------------- /ch14/flashcardsets/Lesson04/姓: -------------------------------------------------------------------------------- 1 | {"Simplified": "\u59d3", "Dictionary": {"Entries": [{"Traditional": "\u59d3", "Pinyin": "xing4", "Translations": ["family name", "surname", "name", "CL:\u500b|\u4e2a[ge4]"], "Simplified": "\u59d3"}]}, "English": "surname"} -------------------------------------------------------------------------------- /ch14/flashcardsets/Lesson04/学生: -------------------------------------------------------------------------------- 1 | {"Simplified": "\u5b66\u751f", "Dictionary": {"Entries": [{"Traditional": "\u5b78\u751f", "Pinyin": "xue2 sheng5", "Translations": ["student", "school child"], "Simplified": "\u5b66\u751f"}]}, "English": "student"} -------------------------------------------------------------------------------- /ch14/flashcardsets/Lesson04/小姐: -------------------------------------------------------------------------------- 1 | {"Simplified": "\u5c0f\u59d0", "Dictionary": {"Entries": [{"Traditional": "\u5c0f\u59d0", "Pinyin": "xiao3 jie5", "Translations": ["young lady", "miss", "CL:\u500b|\u4e2a[ge4],\u4f4d[wei4]"], "Simplified": "\u5c0f\u59d0"}]}, "English": "miss"} -------------------------------------------------------------------------------- /ch14/flashcardsets/Lesson04/很: -------------------------------------------------------------------------------- 1 | {"Simplified": "\u5f88", "Dictionary": {"Entries": [{"Traditional": "\u5f88", "Pinyin": "hen3", "Translations": ["(adverb of degree)", "quite", "very", "awfully"], "Simplified": "\u5f88"}]}, "English": "very"} -------------------------------------------------------------------------------- /ch14/flashcardsets/Lesson04/恁: -------------------------------------------------------------------------------- 1 | {"Simplified": "\u6041", "Dictionary": {"Entries": [{"Traditional": "\u6041", "Pinyin": "nin2", "Translations": ["you (polite)"], "Simplified": "\u6041"}]}, "English": "you (polite)"} -------------------------------------------------------------------------------- /ch14/flashcardsets/Lesson04/想: -------------------------------------------------------------------------------- 1 | {"Simplified": "\u60f3", "Dictionary": {"Entries": [{"Traditional": "\u60f3", "Pinyin": "xiang3", "Translations": ["to think", "to believe", "to suppose", "to wish", "to want", "to miss"], "Simplified": "\u60f3"}]}, "English": "to think"} -------------------------------------------------------------------------------- /ch14/flashcardsets/Lesson04/我: -------------------------------------------------------------------------------- 1 | {"Simplified": "\u6211", "Dictionary": {"Entries": [{"Traditional": "\u6211", "Pinyin": "wo3", "Translations": ["I", "me", "my"], "Simplified": "\u6211"}]}, "English": "I"} -------------------------------------------------------------------------------- /ch14/flashcardsets/Lesson04/是: -------------------------------------------------------------------------------- 1 | {"Simplified": "\u662f", "Dictionary": {"Entries": [{"Traditional": "\u662f", "Pinyin": "shi4", "Translations": ["is", "are", "am", "yes", "to be"], "Simplified": "\u662f"}]}, "English": "is"} -------------------------------------------------------------------------------- /ch14/flashcardsets/Lesson04/是的: -------------------------------------------------------------------------------- 1 | {"Simplified": "\u662f\u7684", "Dictionary": {"Entries": [{"Traditional": "\u662f\u7684", "Pinyin": "shi4 de5", "Translations": ["yes"], "Simplified": "\u662f\u7684"}]}, "English": "yes"} -------------------------------------------------------------------------------- /ch14/flashcardsets/Lesson04/朋友: -------------------------------------------------------------------------------- 1 | {"Simplified": "\u670b\u53cb", "Dictionary": {"Entries": [{"Traditional": "\u670b\u53cb", "Pinyin": "peng2 you5", "Translations": ["friend", "CL:\u500b|\u4e2a[ge4],\u4f4d[wei4]"], "Simplified": "\u670b\u53cb"}]}, "English": "friend"} -------------------------------------------------------------------------------- /ch14/flashcardsets/Lesson04/没: -------------------------------------------------------------------------------- 1 | {"Simplified": "\u6ca1", "Dictionary": {"Entries": [{"Traditional": "\u6c92", "Pinyin": "mei2", "Translations": ["(negative prefix for verbs)", "have not", "not"], "Simplified": "\u6ca1"}]}, "English": "(negative prefix for verbs)"} -------------------------------------------------------------------------------- /ch14/flashcardsets/Lesson04/海: -------------------------------------------------------------------------------- 1 | {"Simplified": "\u6d77", "Dictionary": {"Entries": [{"Traditional": "\u6d77", "Pinyin": "hai3", "Translations": ["ocean", "sea", "CL:\u500b|\u4e2a[ge4],\u7247[pian4]"], "Simplified": "\u6d77"}]}, "English": "sea"} -------------------------------------------------------------------------------- /ch14/flashcardsets/Lesson04/美国: -------------------------------------------------------------------------------- 1 | {"Simplified": "\u7f8e\u56fd", "Dictionary": {"Entries": [{"Traditional": "\u7f8e\u570b", "Pinyin": "Mei3 guo2", "Translations": ["United States", "USA", "US"], "Simplified": "\u7f8e\u56fd"}]}, "English": "USA"} -------------------------------------------------------------------------------- /ch14/flashcardsets/Lesson04/老师: -------------------------------------------------------------------------------- 1 | {"Simplified": "\u8001\u5e08", "Dictionary": {"Entries": [{"Traditional": "\u8001\u5e2b", "Pinyin": "lao3 shi1", "Translations": ["teacher", "CL:\u500b|\u4e2a[ge4],\u4f4d[wei4]"], "Simplified": "\u8001\u5e08"}]}, "English": "teacher"} -------------------------------------------------------------------------------- /ch14/flashcardsets/Lesson04/英国: -------------------------------------------------------------------------------- 1 | {"Simplified": "\u82f1\u56fd", "Dictionary": {"Entries": [{"Traditional": "\u82f1\u570b", "Pinyin": "Ying1 guo2", "Translations": ["United Kingdom \u806f\u5408\u738b\u570b|\u8054\u5408\u738b\u56fd[Lian2 he2 wang2 guo2]", "United Kingdom of Great Britain and Northern Ireland", "abbr. for England \u82f1\u683c\u862d|\u82f1\u683c\u5170[Ying1 ge2 lan2]"], "Simplified": "\u82f1\u56fd"}]}, "English": "United Kingdom of Great Britain and Northern Ireland"} -------------------------------------------------------------------------------- /ch14/flashcardsets/Lesson04/认识: -------------------------------------------------------------------------------- 1 | {"Simplified": "\u8ba4\u8bc6", "Dictionary": {"Entries": [{"Traditional": "\u8a8d\u8b58", "Pinyin": "ren4 shi5", "Translations": ["to know", "to recognize", "to be familiar with", "acquainted with sth", "knowledge", "understanding", "awareness", "cognition"], "Simplified": "\u8ba4\u8bc6"}]}, "English": "to know"} -------------------------------------------------------------------------------- /ch14/flashcardsets/Lesson04/谁: -------------------------------------------------------------------------------- 1 | {"Simplified": "\u8c01", "Dictionary": {"Entries": [{"Traditional": "\u8ab0", "Pinyin": "shei2", "Translations": ["who", "also pronounced shui2"], "Simplified": "\u8c01"}]}, "English": "who"} -------------------------------------------------------------------------------- /ch14/flashcardsets/Lesson04/贵: -------------------------------------------------------------------------------- 1 | {"Simplified": "\u8d35", "Dictionary": {"Entries": [{"Traditional": "\u8cb4", "Pinyin": "gui4", "Translations": ["expensive", "noble", "your (name)", "precious"], "Simplified": "\u8d35"}]}, "English": "your (name)"} -------------------------------------------------------------------------------- /ch14/flashcardsets/Lesson04/都: -------------------------------------------------------------------------------- 1 | {"Simplified": "\u90fd", "Dictionary": {"Entries": [{"Traditional": "\u90fd", "Pinyin": "dou1", "Translations": ["all, both", "entirely (due to) each", "even", "already"], "Simplified": "\u90fd"}]}, "English": "all, both"} -------------------------------------------------------------------------------- /ch14/server.go: -------------------------------------------------------------------------------- 1 | /* Server 2 | */ 3 | 4 | package main 5 | 6 | import ( 7 | "fmt" 8 | "html/template" 9 | "log" 10 | "net/http" 11 | "net/url" 12 | "os" 13 | "regexp" 14 | "strconv" 15 | "strings" 16 | ) 17 | 18 | type FlashcardSet struct { 19 | Name string 20 | Link string 21 | } 22 | 23 | type Flashcard struct { 24 | Name string 25 | Link string 26 | } 27 | 28 | const flashcard_xml string = "application/x.flashcards+xml" 29 | const flashcard_json string = "application/x.flashcards+json" 30 | 31 | type ValueQuality struct { 32 | Value string 33 | Quality float64 34 | } 35 | 36 | /* Based on https://siongui.github.io/2015/02/22/go-parse-accept-language/ */ 37 | func parseValueQuality(s string) []ValueQuality { 38 | var vqs []ValueQuality 39 | 40 | strs := strings.Split(s, `,`) 41 | for _, str := range strs { 42 | trimmedStr := strings.Trim(str, ` `) 43 | valQ := strings.Split(trimmedStr, `;`) 44 | if len(valQ) == 1 { 45 | vq := ValueQuality{valQ[0], 1} 46 | vqs = append(vqs, vq) 47 | } else { 48 | qp := strings.Split(valQ[1], `=`) 49 | q, err := strconv.ParseFloat(qp[1], 64) 50 | if err != nil { 51 | q = 0 52 | } 53 | vq := ValueQuality{valQ[0], q} 54 | vqs = append(vqs, vq) 55 | } 56 | } 57 | return vqs 58 | } 59 | 60 | func qualityOfValue(value string, vqs []ValueQuality) float64 { 61 | for _, vq := range vqs { 62 | if value == vq.Value { 63 | return vq.Quality 64 | } 65 | } 66 | return 0 67 | } 68 | 69 | func main() { 70 | if len(os.Args) != 2 { 71 | log.Fatalln("Usage: ", os.Args[0], ":port\n") 72 | } 73 | port := os.Args[1] 74 | 75 | http.HandleFunc(`/`, handleFlashCardSets) 76 | files, err := os.ReadDir(`flashcardsets`) 77 | checkError(err) 78 | for _, file := range files { 79 | fmt.Println(file.Name()) 80 | cardset_url := `/flashcardSets/` + url.QueryEscape(file.Name()) 81 | fmt.Println("Adding handlers for ", cardset_url) 82 | http.HandleFunc(cardset_url, handleOneFlashCardSet) 83 | http.HandleFunc(cardset_url+`/`, handleOneFlashCard) 84 | } 85 | 86 | // deliver requests to the handlers 87 | err = http.ListenAndServe(port, nil) 88 | checkError(err) 89 | } 90 | 91 | func hasIllegalChars(s string) bool { 92 | // check against chars to break out of current dir 93 | b, err := regexp.Match("[/$~]", []byte(s)) 94 | if err != nil { 95 | fmt.Println(err) 96 | return true 97 | } 98 | if b { 99 | return true 100 | } 101 | return false 102 | } 103 | 104 | func handleOneFlashCard(rw http.ResponseWriter, req *http.Request) { 105 | // should be PathUnescape 106 | path, _ := url.QueryUnescape(req.URL.String()) 107 | // lose intial '/' 108 | path = path[1:] 109 | if req.Method == http.MethodGet { 110 | fmt.Println("Handling card: ", path) 111 | json_contents, err := os.ReadFile(path) 112 | if err != nil { 113 | rw.WriteHeader(http.StatusNotFound) 114 | rw.Write([]byte(`Resource not found`)) 115 | return 116 | } 117 | // Be lazy here, just return the content as text/plain 118 | rw.Write(json_contents) 119 | return 120 | } else if req.Method == http.MethodDelete { 121 | rw.WriteHeader(http.StatusNotImplemented) 122 | } else { 123 | rw.WriteHeader(http.StatusMethodNotAllowed) 124 | } 125 | return 126 | } 127 | 128 | func handleFlashCardSets(rw http.ResponseWriter, req *http.Request) { 129 | if req.URL.String() != `/` { 130 | // this function only handles '/' 131 | rw.WriteHeader(http.StatusNotFound) 132 | rw.Write([]byte("Resource not found\n")) 133 | return 134 | } 135 | if req.Method == "GET" { 136 | acceptTypes := parseValueQuality(req.Header.Get("Accept")) 137 | fmt.Println(acceptTypes) 138 | 139 | q_xml := qualityOfValue(flashcard_xml, acceptTypes) 140 | q_json := qualityOfValue(flashcard_json, acceptTypes) 141 | if q_xml == 0 && q_json == 0 { 142 | // can't find XML or JSON in Accept header 143 | rw.Header().Set("Content-Type", "flashcards+xml, flashcards+json") 144 | rw.WriteHeader(http.StatusNotAcceptable) 145 | return 146 | } 147 | 148 | files, err := os.ReadDir(`flashcardsets`) 149 | checkError(err) 150 | numfiles := len(files) 151 | cardSets := make([]FlashcardSet, numfiles, numfiles) 152 | for n, file := range files { 153 | fmt.Println(file.Name()) 154 | cardSets[n].Name = file.Name() 155 | // should be PathEscape, not in go 1.6 156 | cardSets[n].Link = `/flashcardSets/` + url.QueryEscape(file.Name()) 157 | } 158 | parseFile := "xml/ListFlashcardSets.xml" 159 | flashcardType := flashcard_xml 160 | if q_xml < q_json { 161 | parseFile = "json/ListFlashcardSets.json" 162 | flashcardType = flashcard_json 163 | } 164 | t, err := template.ParseFiles(parseFile) 165 | if err != nil { 166 | fmt.Println("Template error") 167 | http.Error(rw, err.Error(), http.StatusInternalServerError) 168 | return 169 | } 170 | rw.Header().Set("Content-Type", flashcardType) 171 | t.Execute(rw, cardSets) 172 | } else if req.Method == "POST" { 173 | name := req.FormValue(`name`) 174 | if hasIllegalChars(name) { 175 | rw.WriteHeader(http.StatusForbidden) 176 | return 177 | } 178 | // lose all spaces as they are a nuisance 179 | name = strings.Replace(name, ` `, ``, -1) 180 | err := os.Mkdir(`flashcardsets/`+name, 181 | (os.ModeDir | os.ModePerm)) 182 | if err != nil { 183 | rw.WriteHeader(http.StatusForbidden) 184 | return 185 | } 186 | rw.WriteHeader(http.StatusCreated) 187 | base_url := req.URL.String() 188 | new_url := base_url + `flashcardSets/` + name + `/` 189 | rw.Write([]byte(new_url)) 190 | } else { 191 | rw.WriteHeader(http.StatusMethodNotAllowed) 192 | } 193 | return 194 | } 195 | 196 | func handleOneFlashCardSet(rw http.ResponseWriter, req *http.Request) { 197 | cooked_url, _ := url.QueryUnescape(req.URL.String()) 198 | fmt.Println("Handling one set for: ", cooked_url) 199 | 200 | if req.Method == http.MethodGet { 201 | acceptTypes := parseValueQuality(req.Header.Get("Accept")) 202 | fmt.Println(acceptTypes) 203 | 204 | q_xml := qualityOfValue(flashcard_xml, acceptTypes) 205 | q_json := qualityOfValue(flashcard_json, acceptTypes) 206 | if q_xml == 0 && q_json == 0 { 207 | // can't find XML or JSON in Accept header 208 | rw.Header().Set("Content-Type", "flashcards+xml, flashcards+json") 209 | rw.WriteHeader(http.StatusNotAcceptable) 210 | return 211 | } 212 | 213 | path := req.URL.String() 214 | // lose leading / 215 | relative_path := path[1:] 216 | files, err := os.ReadDir(relative_path) 217 | checkError(err) 218 | numfiles := len(files) 219 | cards := make([]Flashcard, numfiles, numfiles) 220 | for n, file := range files { 221 | fmt.Println(file.Name()) 222 | cards[n].Name = file.Name() 223 | // should be PathEscape, not in go 1.6 224 | cards[n].Link = path + `/` + url.QueryEscape(file.Name()) 225 | } 226 | 227 | if q_xml >= q_json { 228 | // XML preferred 229 | t, err := template.ParseFiles("xml/ListOneFlashcardSet.xml") 230 | if err != nil { 231 | fmt.Println("Template error") 232 | http.Error(rw, err.Error(), http.StatusInternalServerError) 233 | return 234 | } 235 | rw.Header().Set("Content-Type", flashcard_xml) 236 | t.Execute(os.Stdout, cards) 237 | t.Execute(rw, cards) 238 | } else { 239 | // JSON preferred 240 | t, err := template.ParseFiles("json/ListOneFlashcardSet.json") 241 | if err != nil { 242 | fmt.Println("Template error", err) 243 | http.Error(rw, err.Error(), http.StatusInternalServerError) 244 | return 245 | } 246 | rw.Header().Set("Content-Type", flashcard_json) 247 | t.Execute(rw, cards) 248 | 249 | } 250 | } else if req.Method == http.MethodPost { 251 | name := req.FormValue(`name`) 252 | if hasIllegalChars(name) { 253 | rw.WriteHeader(http.StatusForbidden) 254 | return 255 | } 256 | err := os.Mkdir(`flashcardsets/`+name, 257 | (os.ModeDir | os.ModePerm)) 258 | if err != nil { 259 | rw.WriteHeader(http.StatusForbidden) 260 | return 261 | } 262 | rw.WriteHeader(http.StatusCreated) 263 | base_url := req.URL.String() 264 | new_url := base_url + `flashcardSets/` + name 265 | _, _ = rw.Write([]byte(new_url)) 266 | } else if req.Method == http.MethodDelete { 267 | rw.WriteHeader(http.StatusNotImplemented) 268 | } else { 269 | rw.WriteHeader(http.StatusMethodNotAllowed) 270 | } 271 | return 272 | } 273 | 274 | func checkError(err error) { 275 | if err != nil { 276 | log.Fatalln("Fatal error ", err.Error()) 277 | } 278 | } 279 | -------------------------------------------------------------------------------- /ch14/xml/ListFlashcardSets.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {{range .}} 5 | 6 | 7 | {{.Name}} 8 | 9 | 10 | {{end}} 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /ch14/xml/ListOneFlashcardSet.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {{range .}} 5 | 6 | 7 | {{.Name}} 8 | 9 | 10 | {{end}} 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /ch15/echoclient.go: -------------------------------------------------------------------------------- 1 | /* EchoClient 2 | */ 3 | package main 4 | 5 | import ( 6 | "fmt" 7 | "golang.org/x/net/websocket" 8 | "io" 9 | "os" 10 | "log" 11 | ) 12 | 13 | func main() { 14 | if len(os.Args) != 2 { 15 | log.Fatalln("Usage: ", os.Args[0], "ws://host:port") 16 | } 17 | service := os.Args[1] 18 | conn, err := websocket.Dial(service, "", 19 | "http://localhost:12345") 20 | checkError(err) 21 | var msg string 22 | for { 23 | err := websocket.Message.Receive(conn, &msg) 24 | if err != nil { 25 | if err == io.EOF { 26 | // graceful shutdown by server 27 | break 28 | } 29 | fmt.Println("Couldn't receive msg " + 30 | err.Error()) 31 | break 32 | } 33 | fmt.Println("Received from server: " + msg) 34 | // return the msg 35 | err = websocket.Message.Send(conn, msg) 36 | if err != nil { 37 | fmt.Println("Couldn't return msg") 38 | break 39 | } 40 | } 41 | } 42 | func checkError(err error) { 43 | if err != nil { 44 | log.Fatalln("Fatal error ", err.Error()) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /ch15/echoclientgorilla.go: -------------------------------------------------------------------------------- 1 | /* EchoClientGorilla 2 | */ 3 | package main 4 | 5 | import ( 6 | "fmt" 7 | "github.com/gorilla/websocket" 8 | "io" 9 | "log" 10 | "net/http" 11 | "os" 12 | ) 13 | 14 | func main() { 15 | if len(os.Args) != 2 { 16 | log.Fatalln("Usage: ", os.Args[0], "ws://host:port") 17 | } 18 | service := os.Args[1] 19 | header := make(http.Header) 20 | header.Add("Origin", "http://localhost:12345") 21 | conn, _, err := websocket.DefaultDialer.Dial(service, header) 22 | checkError(err) 23 | for { 24 | _, reply, err := conn.ReadMessage() 25 | if err != nil { 26 | if err == io.EOF { 27 | // graceful shutdown by server 28 | fmt.Println(`EOF from server`) 29 | break 30 | } 31 | if websocket.IsCloseError(err, 32 | websocket.CloseAbnormalClosure) { 33 | fmt.Println(`Close from server`) 34 | break 35 | } 36 | fmt.Println("Couldn't receive msg " + 37 | err.Error()) 38 | break 39 | } 40 | fmt.Println("Received from server: " + 41 | string(reply[:])) 42 | // return the msg 43 | err = conn.WriteMessage(websocket.TextMessage, reply) 44 | if err != nil { 45 | fmt.Println("Couldn't return msg") 46 | break 47 | } 48 | } 49 | } 50 | func checkError(err error) { 51 | if err != nil { 52 | log.Fatalln("Fatal error ", err.Error()) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /ch15/echoclienttls.go: -------------------------------------------------------------------------------- 1 | /* EchoClientTLS 2 | */ 3 | package main 4 | 5 | import ( 6 | "crypto/tls" 7 | "fmt" 8 | "golang.org/x/net/websocket" 9 | "io" 10 | "log" 11 | "os" 12 | ) 13 | 14 | func main() { 15 | if len(os.Args) != 2 { 16 | log.Fatalln("Usage: ", os.Args[0], "wss://host:port") 17 | } 18 | config, err := websocket.NewConfig(os.Args[1], 19 | "http://localhost") 20 | checkError(err) 21 | tlsConfig := &tls.Config{InsecureSkipVerify: false} 22 | config.TlsConfig = tlsConfig 23 | conn, err := websocket.DialConfig(config) 24 | checkError(err) 25 | var msg string 26 | for { 27 | err := websocket.Message.Receive(conn, &msg) 28 | if err != nil { 29 | if err == io.EOF { 30 | // graceful shutdown by server 31 | break 32 | } 33 | fmt.Println("Couldn't receive msg " + 34 | err.Error()) 35 | break 36 | } 37 | fmt.Println("Received from server: " + msg) 38 | // return the msg 39 | err = websocket.Message.Send(conn, msg) 40 | if err != nil { 41 | fmt.Println("Couldn't return msg") 42 | break 43 | } 44 | } 45 | } 46 | func checkError(err error) { 47 | if err != nil { 48 | log.Fatalln("Fatal error ", err.Error()) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /ch15/echoserver.go: -------------------------------------------------------------------------------- 1 | /* EchoServer 2 | */ 3 | package main 4 | 5 | import ( 6 | "fmt" 7 | "golang.org/x/net/websocket" 8 | "log" 9 | "net/http" 10 | ) 11 | 12 | func Echo(ws *websocket.Conn) { 13 | fmt.Println("Echoing") 14 | for n := 0; n < 10; n++ { 15 | msg := "Hello " + string(n+48) 16 | fmt.Println("Sending to client: " + msg) 17 | err := websocket.Message.Send(ws, msg) 18 | if err != nil { 19 | fmt.Println("Can't send") 20 | break 21 | } 22 | var reply string 23 | err = websocket.Message.Receive(ws, &reply) 24 | if err != nil { 25 | fmt.Println("Can't receive") 26 | break 27 | } 28 | fmt.Println("Received back from client: " + reply) 29 | } 30 | } 31 | func main() { 32 | http.Handle("/", websocket.Handler(Echo)) 33 | err := http.ListenAndServe(":12345", nil) 34 | checkError(err) 35 | } 36 | func checkError(err error) { 37 | if err != nil { 38 | log.Fatalln("Fatal error ", err.Error()) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /ch15/echoservergorilla.go: -------------------------------------------------------------------------------- 1 | /* EchoServerGorilla 2 | */ 3 | package main 4 | 5 | import ( 6 | "fmt" 7 | "github.com/gorilla/websocket" 8 | "log" 9 | "net/http" 10 | ) 11 | 12 | var upgrader = websocket.Upgrader{ 13 | ReadBufferSize: 1024, 14 | WriteBufferSize: 1024, 15 | } 16 | 17 | func Handler(w http.ResponseWriter, r *http.Request) { 18 | fmt.Println("Handling /") 19 | conn, err := upgrader.Upgrade(w, r, nil) 20 | if err != nil { 21 | fmt.Println(err) 22 | return 23 | } 24 | for n := 0; n < 10; n++ { 25 | msg := "Hello " + string(n+48) 26 | fmt.Println("Sending to client: " + msg) 27 | err = conn.WriteMessage(websocket.TextMessage, 28 | []byte(msg)) 29 | _, reply, err := conn.ReadMessage() 30 | if err != nil { 31 | fmt.Println("Can't receive") 32 | break 33 | } 34 | fmt.Println("Received back from client: " + 35 | string(reply[:])) 36 | } 37 | conn.Close() 38 | } 39 | func main() { 40 | http.HandleFunc("/", Handler) 41 | err := http.ListenAndServe("localhost:12345", nil) 42 | checkError(err) 43 | } 44 | func checkError(err error) { 45 | if err != nil { 46 | log.Fatalln("Fatal error ", err.Error()) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /ch15/echoservertls.go: -------------------------------------------------------------------------------- 1 | /* EchoServerTLS 2 | */ 3 | package main 4 | 5 | import ( 6 | "log" 7 | "fmt" 8 | "golang.org/x/net/websocket" 9 | "net/http" 10 | ) 11 | 12 | func Echo(ws *websocket.Conn) { 13 | fmt.Println("Echoing") 14 | for n := 0; n < 10; n++ { 15 | msg := "Hello " + string(n+48) 16 | fmt.Println("Sending to client: " + msg) 17 | err := websocket.Message.Send(ws, msg) 18 | if err != nil { 19 | fmt.Println("Can't send") 20 | break 21 | } 22 | var reply string 23 | err = websocket.Message.Receive(ws, &reply) 24 | if err != nil { 25 | fmt.Println("Can't receive") 26 | break 27 | } 28 | fmt.Println("Received back from client: " + reply) 29 | } 30 | } 31 | func main() { 32 | http.Handle("/", websocket.Handler(Echo)) 33 | err := http.ListenAndServeTLS(":12345", 34 | "jan.newmarch.name.pem", 35 | "private.pem", nil) 36 | checkError(err) 37 | } 38 | func checkError(err error) { 39 | if err != nil { 40 | log.Fatalln("Fatal error ", err.Error()) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /ch15/personclientjson.go: -------------------------------------------------------------------------------- 1 | /* PersonClientJSON 2 | */ 3 | package main 4 | 5 | import ( 6 | "fmt" 7 | "golang.org/x/net/websocket" 8 | "log" 9 | "os" 10 | ) 11 | 12 | type Person struct { 13 | Name string 14 | Emails []string 15 | } 16 | 17 | func main() { 18 | if len(os.Args) != 2 { 19 | log.Fatalln("Usage: ", os.Args[0], "ws://host:port") 20 | } 21 | service := os.Args[1] 22 | conn, err := websocket.Dial(service, "", 23 | "http://localhost") 24 | checkError(err) 25 | person := Person{Name: "Jan", 26 | Emails: []string{"ja@newmarch.name", 27 | "jan.newmarch@gmail.com"}, 28 | } 29 | err = websocket.JSON.Send(conn, person) 30 | if err != nil { 31 | fmt.Println("Couldn't send msg " + err.Error()) 32 | } 33 | } 34 | func checkError(err error) { 35 | if err != nil { 36 | log.Fatalln("Fatal error ", err.Error()) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /ch15/personclientxml.go: -------------------------------------------------------------------------------- 1 | /* PersonClientXML 2 | */ 3 | package main 4 | 5 | import ( 6 | "fmt" 7 | "golang.org/x/net/websocket" 8 | "log" 9 | "os" 10 | ) 11 | 12 | type Person struct { 13 | Name string 14 | Emails []string 15 | } 16 | 17 | func main() { 18 | if len(os.Args) != 2 { 19 | log.Fatalln("Usage: ", os.Args[0], "ws://host:port") 20 | } 21 | service := os.Args[1] 22 | conn, err := websocket.Dial(service, "", "http://localhost") 23 | checkError(err) 24 | person := Person{Name: "Jan", 25 | Emails: []string{"ja@newmarch.name", 26 | "jan.newmarch@gmail.com"}, 27 | } 28 | err = XMLCodec.Send(conn, person) 29 | if err != nil { 30 | fmt.Println("Couldn't send msg " + err.Error()) 31 | } 32 | os.Exit(0) 33 | } 34 | func checkError(err error) { 35 | if err != nil { 36 | log.Fatalln("Fatal error ", err.Error()) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /ch15/personserverjson.go: -------------------------------------------------------------------------------- 1 | /* PersonServerJSON 2 | */ 3 | package main 4 | 5 | import ( 6 | "fmt" 7 | "golang.org/x/net/websocket" 8 | "log" 9 | "net/http" 10 | ) 11 | 12 | type Person struct { 13 | Name string 14 | Emails []string 15 | } 16 | 17 | func ReceivePerson(ws *websocket.Conn) { 18 | var person Person 19 | err := websocket.JSON.Receive(ws, &person) 20 | if err != nil { 21 | fmt.Println("Can't receive") 22 | } else { 23 | fmt.Println("Name: " + person.Name) 24 | for _, e := range person.Emails { 25 | fmt.Println("An email: " + e) 26 | } 27 | } 28 | } 29 | func main() { 30 | http.Handle("/", websocket.Handler(ReceivePerson)) 31 | err := http.ListenAndServe(":12345", nil) 32 | checkError(err) 33 | } 34 | func checkError(err error) { 35 | if err != nil { 36 | log.Fatalln("Fatal error ", err.Error()) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /ch15/personserverxml.go: -------------------------------------------------------------------------------- 1 | /* PersonServerXML 2 | */ 3 | package main 4 | 5 | import ( 6 | "fmt" 7 | "golang.org/x/net/websocket" 8 | "log" 9 | "net/http" 10 | ) 11 | 12 | type Person struct { 13 | Name string 14 | Emails []string 15 | } 16 | 17 | func ReceivePerson(ws *websocket.Conn) { 18 | var person Person 19 | err := XMLCodec.Receive(ws, &person) 20 | if err != nil { 21 | fmt.Println("Can't receive") 22 | } else { 23 | fmt.Println("Name: " + person.Name) 24 | for _, e := range person.Emails { 25 | fmt.Println("An email: " + e) 26 | } 27 | } 28 | } 29 | func main() { 30 | http.Handle("/", websocket.Handler(ReceivePerson)) 31 | err := http.ListenAndServe(":12345", nil) 32 | checkError(err) 33 | } 34 | func checkError(err error) { 35 | if err != nil { 36 | log.Fatalln("Fatal error ", err.Error()) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /ch15/sensors.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | echo $(( ( RANDOM % 100 ) + 1 )) 3 | -------------------------------------------------------------------------------- /ch15/temperatureserver.go: -------------------------------------------------------------------------------- 1 | /* TemperatureServer 2 | */ 3 | package main 4 | 5 | import ( 6 | "fmt" 7 | "golang.org/x/net/websocket" 8 | "log" 9 | "net/http" 10 | "os/exec" 11 | "time" 12 | ) 13 | 14 | var ROOT_DIR = "." 15 | 16 | func GetTemp(ws *websocket.Conn) { 17 | for { 18 | msg, err := exec.Command(ROOT_DIR + "/sensors.sh").CombinedOutput() 19 | checkError(err) 20 | fmt.Println("Sending to client: " + string(msg[:])) 21 | err = websocket.Message.Send(ws, string(msg[:])) 22 | if err != nil { 23 | fmt.Println("Can't send") 24 | break 25 | } 26 | time.Sleep(time.Duration(2) * time.Second) 27 | var reply string 28 | err = websocket.Message.Receive(ws, &reply) 29 | if err != nil { 30 | fmt.Println("Can't receive") 31 | break 32 | } 33 | fmt.Println("Received back from client: " + reply) 34 | } 35 | } 36 | func main() { 37 | fileServer := http.FileServer(http.Dir(ROOT_DIR)) 38 | http.Handle("/GetTemp", websocket.Handler(GetTemp)) 39 | http.Handle("/", fileServer) 40 | err := http.ListenAndServe(":12345", nil) 41 | checkError(err) 42 | } 43 | func checkError(err error) { 44 | if err != nil { 45 | log.Fatalln("Fatal error ", err.Error()) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /ch15/websocket.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 38 | 39 | 40 |
41 | Run temperature 42 | sensor 43 |
44 | 45 | 46 | -------------------------------------------------------------------------------- /ch15/xmlcodec.go: -------------------------------------------------------------------------------- 1 | /* XMLCodec 2 | */ 3 | package main 4 | 5 | import ( 6 | "encoding/xml" 7 | "golang.org/x/net/websocket" 8 | ) 9 | 10 | func xmlMarshal(v interface{}) (msg []byte, payloadType byte, err error) { 11 | msg, err = xml.Marshal(v) 12 | return msg, websocket.TextFrame, nil 13 | } 14 | func xmlUnmarshal(msg []byte, payloadType byte, v interface{}) (err error) { 15 | err = xml.Unmarshal(msg, v) 16 | return err 17 | } 18 | 19 | var XMLCodec = websocket.Codec{xmlMarshal, xmlUnmarshal} 20 | -------------------------------------------------------------------------------- /ch16/contenttype.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/gorilla/handlers" 5 | "github.com/gorilla/mux" 6 | "net/http" 7 | ) 8 | 9 | func buildHandler(message string) func(http.ResponseWriter, *http.Request) { 10 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 11 | w.Write([]byte(message)) 12 | }) 13 | } 14 | 15 | func main() { 16 | r := mux.NewRouter() 17 | r.HandleFunc("/", buildHandler("HomeHandler")) 18 | r.HandleFunc("/products", buildHandler("ProductsHandler")) 19 | l := handlers.ContentTypeHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 20 | w.Write([]byte("json articles only")) 21 | }), "application/json") 22 | r.Handle("/articles", l) 23 | http.ListenAndServe(":8080", r) 24 | } 25 | -------------------------------------------------------------------------------- /ch16/gmux.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/gorilla/mux" 5 | "net/http" 6 | ) 7 | 8 | func buildHandler(message string) func(http.ResponseWriter, *http.Request) { 9 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 10 | w.Write([]byte(message)) 11 | }) 12 | } 13 | 14 | func main() { 15 | r := mux.NewRouter() 16 | r.HandleFunc("/", buildHandler("HomeHandler")) 17 | r.HandleFunc("/products", buildHandler("ProductsHandler")) 18 | r.HandleFunc("/articles", buildHandler("ArticlesHandler")) 19 | http.ListenAndServe(":8080", r) 20 | } 21 | -------------------------------------------------------------------------------- /ch16/logging.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/gorilla/mux" 5 | "github.com/gorilla/handlers" 6 | "os" 7 | "net/http" 8 | ) 9 | 10 | func buildHandler(message string) func(http.ResponseWriter, *http.Request) { 11 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 12 | w.Write([]byte(message)) 13 | }) 14 | } 15 | 16 | func main() { 17 | r := mux.NewRouter() 18 | r.HandleFunc("/", buildHandler("HomeHandler")) 19 | r.HandleFunc("/products", buildHandler("ProductsHandler")) 20 | r.HandleFunc("/articles", buildHandler("ArticlesHandler")).Host("example.com").Methods("GET").Schemes("http") 21 | loggedRouter := handlers.LoggingHandler(os.Stdout, r) 22 | http.ListenAndServe(":8080", loggedRouter) 23 | } 24 | -------------------------------------------------------------------------------- /ch16/matching.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/gorilla/mux" 5 | "net/http" 6 | ) 7 | 8 | func buildHandler(message string) func(http.ResponseWriter, *http.Request) { 9 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 10 | w.Write([]byte(message)) 11 | }) 12 | } 13 | 14 | func main() { 15 | r := mux.NewRouter() 16 | r.HandleFunc("/", buildHandler("HomeHandler")) 17 | r.HandleFunc("/products", buildHandler("ProductsHandler")) 18 | r.HandleFunc("/articles", buildHandler("ArticlesHandler")).Host("example.com").Methods("GET").Schemes("http") 19 | http.ListenAndServe(":8080", nil) 20 | } 21 | -------------------------------------------------------------------------------- /ch16/rpc.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/gorilla/rpc" 5 | "github.com/gorilla/rpc/json" 6 | "net/http" 7 | ) 8 | 9 | type HelloArgs struct { 10 | Who string 11 | } 12 | 13 | type HelloReply struct { 14 | Message string 15 | } 16 | 17 | type HelloService struct{} 18 | 19 | func (h *HelloService) Say(r *http.Request, args *HelloArgs, reply *HelloReply) error { 20 | reply.Message = "Hello, " + args.Who + "!" 21 | return nil 22 | } 23 | 24 | func main() { 25 | s := rpc.NewServer() 26 | s.RegisterCodec(json.NewCodec(), "application/json") 27 | s.RegisterService(new(HelloService), "") 28 | http.Handle("/rpc", s) 29 | http.ListenAndServe(":8080", nil) 30 | } 31 | -------------------------------------------------------------------------------- /ch16/schema.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/gorilla/schema" 6 | "net/http" 7 | ) 8 | 9 | var decoder = schema.NewDecoder() 10 | 11 | type Person struct { 12 | Name string 13 | Phone string 14 | } 15 | 16 | func main() { 17 | http.HandleFunc("/schema", func(res http.ResponseWriter, req *http.Request) { 18 | req.ParseMultipartForm(0) 19 | var person Person 20 | decoder.Decode(&person, req.PostForm) 21 | message := fmt.Sprintf("Hello %v from area %v", person.Name, person.Phone) 22 | fmt.Printf("%v", req.PostForm) 23 | res.Write([]byte(message)) 24 | }) 25 | 26 | http.ListenAndServe(":8080", nil) 27 | } 28 | -------------------------------------------------------------------------------- /ch16/securecookie.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/gorilla/securecookie" 6 | "net/http" 7 | ) 8 | 9 | var hashKey = []byte("very-secret") 10 | var s = securecookie.New(hashKey, nil) 11 | 12 | func SetCookieHandler(w http.ResponseWriter, r *http.Request) { 13 | value := map[string]string{ 14 | "foo": "bar", 15 | } 16 | if encoded, err := s.Encode("cookie-name", value); err == nil { 17 | cookie := &http.Cookie{ 18 | Name: "cookie-name", 19 | Value: encoded, 20 | Path: "/", 21 | } 22 | http.SetCookie(w, cookie) 23 | 24 | fmt.Println(cookie) 25 | } 26 | } 27 | 28 | func ReadCookieHandler(w http.ResponseWriter, r *http.Request) { 29 | if cookie, err := r.Cookie("cookie-name"); err == nil { 30 | value := make(map[string]string) 31 | fmt.Println(cookie.Value) 32 | if err = s.Decode("cookie-name", cookie.Value, &value); err == nil { 33 | fmt.Println("im ok!") 34 | fmt.Fprintf(w, "The value of foo is %q", value["foo"]) 35 | } 36 | 37 | fmt.Println(cookie, err) 38 | } 39 | 40 | } 41 | 42 | func main() { 43 | http.HandleFunc("/set", SetCookieHandler) 44 | http.HandleFunc("/read", ReadCookieHandler) 45 | http.ListenAndServe(":8080", nil) 46 | } 47 | -------------------------------------------------------------------------------- /ch17/basic_http_test.go: -------------------------------------------------------------------------------- 1 | package ch17 2 | 3 | import ( 4 | "net" 5 | "net/http" 6 | "net/http/httptest" 7 | "testing" 8 | "time" 9 | ) 10 | 11 | func TestHTTPRoundTripNoConnection(t *testing.T) { 12 | path := "/" 13 | req := httptest.NewRequest("GET", path, nil) 14 | res := httptest.NewRecorder() 15 | 16 | f := func(w http.ResponseWriter, req *http.Request) { 17 | time.Sleep(5 * time.Second) // holding connection 18 | } 19 | 20 | f(res, req) 21 | 22 | if res == nil || res.Result().StatusCode != http.StatusOK { 23 | t.Error(res) 24 | } 25 | } 26 | 27 | func TestHTTPRoundTrip(t *testing.T) { 28 | path := "/" 29 | c := make(chan struct{}) 30 | //server 31 | go func() { 32 | http.HandleFunc(path, func(w http.ResponseWriter, req *http.Request) { 33 | time.Sleep(5 * time.Second) // holding connection 34 | }) 35 | http.ListenAndServe(":8080", nil) 36 | }() 37 | 38 | //client 39 | go func() { 40 | resp, err := http.Get("http://localhost:8080" + path) 41 | if err != nil { 42 | t.Error(err) 43 | } else { 44 | if resp == nil || resp.StatusCode != http.StatusOK { 45 | t.Error(resp) 46 | } 47 | } 48 | 49 | defer func() { 50 | c <- struct{}{} 51 | }() 52 | }() 53 | <-c 54 | } 55 | 56 | func TestHTTPTestRoundTripTimeout(t *testing.T) { 57 | ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { 58 | time.Sleep(5 * time.Second) // holding connection 59 | })) 60 | defer ts.Close() 61 | 62 | var c = &http.Client{ 63 | Timeout: time.Second * 2, 64 | } 65 | req, _ := http.NewRequest("GET", ts.URL, nil) 66 | res, err := c.Do(req) 67 | if err != nil { 68 | t.Fatal(err) 69 | } 70 | 71 | err = res.Body.Close() 72 | if err != nil { 73 | t.Fatal(err) 74 | } 75 | 76 | } 77 | 78 | func TestHTTPRoundTripTimeout(t *testing.T) { 79 | path := "/unique" 80 | c := make(chan struct{}) 81 | //server 82 | go func() { 83 | http.HandleFunc(path, func(w http.ResponseWriter, req *http.Request) { 84 | time.Sleep(5 * time.Second) // holding connection 85 | }) 86 | http.ListenAndServe(":8080", nil) 87 | }() 88 | 89 | //client 90 | go func() { 91 | var client = &http.Client{ 92 | Timeout: time.Second * 2, 93 | } 94 | resp, err := client.Get("http://localhost:8080" + path) 95 | if err != nil { 96 | t.Error(err) 97 | } else { 98 | if resp == nil || resp.StatusCode != http.StatusOK { 99 | t.Error(resp) 100 | } 101 | } 102 | 103 | defer func() { 104 | c <- struct{}{} 105 | }() 106 | }() 107 | <-c 108 | } 109 | 110 | func TestPipe(t *testing.T) { 111 | c := make(chan struct{}) 112 | server, client := net.Pipe() 113 | go func() { 114 | time.Sleep(2 * time.Second) 115 | req := make([]byte, 15) 116 | server.SetDeadline(time.Now().Add(1 * time.Second)) 117 | _, err := server.Read(req) 118 | t.Log(string(req)) 119 | if err != nil { 120 | t.Error(err) 121 | } 122 | defer func() { 123 | server.Close() 124 | c <- struct{}{} 125 | }() 126 | }() 127 | client.SetDeadline(time.Now().Add(1 * time.Second)) 128 | _, err := client.Write([]byte("my http request")) 129 | if err != nil { 130 | t.Error(err) 131 | } 132 | defer func() { 133 | client.Close() 134 | }() 135 | <-c 136 | } 137 | -------------------------------------------------------------------------------- /ch3/daytimeserver.go: -------------------------------------------------------------------------------- 1 | /* DaytimeServer 2 | */ 3 | package main 4 | 5 | import ( 6 | "log" 7 | "net" 8 | "time" 9 | ) 10 | 11 | func main() { 12 | service := ":1200" 13 | tcpAddr, err := net.ResolveTCPAddr("tcp", service) 14 | checkError(err) 15 | listener, err := net.ListenTCP("tcp", tcpAddr) 16 | checkError(err) 17 | for { 18 | conn, err := listener.Accept() 19 | if err != nil { 20 | continue 21 | } 22 | daytime := time.Now().String() 23 | conn.Write([]byte(daytime)) // don't care about return value 24 | conn.Close() // we're finished with this client 25 | } 26 | } 27 | func checkError(err error) { 28 | if err != nil { 29 | log.Fatalln("Fatal error: %s", err.Error()) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /ch3/getheadinfo.go: -------------------------------------------------------------------------------- 1 | /* GetHeadInfo 2 | */ 3 | package main 4 | 5 | import ( 6 | "fmt" 7 | "io/ioutil" 8 | "log" 9 | "net" 10 | "os" 11 | ) 12 | 13 | func main() { 14 | if len(os.Args) != 2 { 15 | log.Fatalf("Usage: %s host:port ", os.Args[0]) 16 | } 17 | service := os.Args[1] 18 | tcpAddr, err := net.ResolveTCPAddr("tcp4", service) 19 | checkError(err) 20 | conn, err := net.DialTCP("tcp", nil, tcpAddr) 21 | checkError(err) 22 | _, err = conn.Write([]byte("HEAD / HTTP/1.0\r\n\r\n")) 23 | checkError(err) 24 | result, err := ioutil.ReadAll(conn) 25 | checkError(err) 26 | fmt.Println(string(result)) 27 | } 28 | func checkError(err error) { 29 | if err != nil { 30 | log.Fatalf("Fatal error: %s", err.Error()) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /ch3/ip.go: -------------------------------------------------------------------------------- 1 | /* IP 2 | */ 3 | package main 4 | 5 | import ( 6 | "fmt" 7 | "log" 8 | "net" 9 | "os" 10 | ) 11 | 12 | func main() { 13 | if len(os.Args) != 2 { 14 | log.Fatalf("Usage: %s ip-addr\n", os.Args[0]) 15 | } 16 | name := os.Args[1] 17 | addr := net.ParseIP(name) 18 | if addr == nil { 19 | fmt.Println("Invalid address") 20 | } else { 21 | fmt.Println("The address is ", addr.String()) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /ch3/ipgetheadinfo.go: -------------------------------------------------------------------------------- 1 | /* IPGetHeadInfo 2 | */ 3 | package main 4 | 5 | import ( 6 | "bytes" 7 | "fmt" 8 | "io" 9 | "log" 10 | "net" 11 | "os" 12 | ) 13 | 14 | func main() { 15 | if len(os.Args) != 2 { 16 | log.Fatalf("Usage: %s host:port", os.Args[0]) 17 | } 18 | service := os.Args[1] 19 | conn, err := net.Dial("tcp", service) 20 | checkError(err) 21 | _, err = conn.Write([]byte("HEAD / HTTP/1.0\r\n\r\n")) 22 | checkError(err) 23 | result, err := readFully(conn) 24 | checkError(err) 25 | fmt.Println(string(result)) 26 | } 27 | func readFully(conn net.Conn) ([]byte, error) { 28 | defer conn.Close() 29 | result := bytes.NewBuffer(nil) 30 | var buf [512]byte 31 | for { 32 | n, err := conn.Read(buf[0:]) 33 | result.Write(buf[0:n]) 34 | if err != nil { 35 | if err == io.EOF { 36 | break 37 | } 38 | return nil, err 39 | } 40 | } 41 | return result.Bytes(), nil 42 | } 43 | func checkError(err error) { 44 | if err != nil { 45 | log.Fatalf("Fatal error: %s", err.Error()) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /ch3/ipv4mask.go: -------------------------------------------------------------------------------- 1 | /* IPv4Mask 2 | */ 3 | package main 4 | 5 | import ( 6 | "fmt" 7 | "log" 8 | "net" 9 | "os" 10 | ) 11 | 12 | func main() { 13 | if len(os.Args) != 2 { 14 | log.Fatalf("Usage: %s dotted-ip-addr\n", os.Args[0]) 15 | } 16 | dotAddr := os.Args[1] 17 | addr := net.ParseIP(dotAddr) 18 | if addr == nil { 19 | log.Fatalln("nil Invalid address") 20 | } 21 | mask := addr.DefaultMask() 22 | network := addr.Mask(mask) 23 | ones, bits := mask.Size() 24 | fmt.Println("Address is ", addr.String(), 25 | "\nDefault mask length is ", bits, 26 | "\nLeading ones count is ", ones, 27 | "\nMask is (hex) ", mask.String(), 28 | "\nNetwork is ", network.String()) 29 | derivedMask := net.IPv4Mask(255, 255, 0, 0) // working on mask 30 | fmt.Printf("Network using %s: %s\n", derivedMask, addr.Mask(derivedMask)) 31 | } 32 | -------------------------------------------------------------------------------- /ch3/ipv4router.go: -------------------------------------------------------------------------------- 1 | /* IPv4Router 2 | */ 3 | package main 4 | 5 | import ( 6 | "fmt" 7 | "net" 8 | ) 9 | 10 | func main() { 11 | routingTable := []struct { 12 | subnetmask net.IP 13 | network net.IP 14 | nextHop net.IP 15 | }{ 16 | {net.IP{255, 255, 255, 240}, net.IP{192, 17, 7, 208}, net.IP{192, 12, 7, 15}}, 17 | {net.IP{255, 255, 255, 240}, net.IP{192, 17, 7, 144}, net.IP{192, 12, 7, 67}}, 18 | {net.IP{255, 255, 255, 0}, net.IP{192, 17, 7, 0}, net.IP{192, 12, 7, 251}}, 19 | {net.IP{0, 0, 0, 0}, net.IP{0, 0, 0, 0}, net.IP{10, 10, 10, 10}}, 20 | } 21 | incomingPacketsToRoute := []struct { 22 | sourceAddr net.IP 23 | destinationAddr net.IP 24 | data string 25 | }{ 26 | {net.IP{1, 2, 3, 4}, net.IP{2, 3, 4, 5}, "who knows, send to 0.0.0.0"}, 27 | {net.IP{1, 2, 3, 4}, net.IP{192, 17, 7, 20}, "better be 192.17.7.251"}, 28 | } 29 | for _, packetToRoute := range incomingPacketsToRoute { 30 | for _, routingEntry := range routingTable { 31 | r := packetToRoute.destinationAddr.Mask(net.IPMask(routingEntry.subnetmask)) 32 | if r.Equal(routingEntry.network) { 33 | fmt.Printf("For destination %s nexthop is %s\n", packetToRoute.destinationAddr, routingEntry.nextHop) 34 | break //check remaining ips 35 | } 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /ch3/lookuphost.go: -------------------------------------------------------------------------------- 1 | /* LookupHost 2 | */ 3 | package main 4 | 5 | import ( 6 | "fmt" 7 | "log" 8 | "net" 9 | "os" 10 | ) 11 | 12 | func main() { 13 | if len(os.Args) != 2 { 14 | log.Fatalf("Usage: %s hostname\n", os.Args[0]) 15 | } 16 | name := os.Args[1] 17 | cname, _ := net.LookupCNAME(name) 18 | fmt.Println(cname) 19 | addrs, err := net.LookupHost(cname) 20 | if err != nil { 21 | log.Fatalln("Error: ", err.Error()) 22 | } 23 | for _, addr := range addrs { 24 | fmt.Println(addr) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /ch3/lookupport.go: -------------------------------------------------------------------------------- 1 | /* LookupPort 2 | */ 3 | package main 4 | 5 | import ( 6 | "fmt" 7 | "log" 8 | "net" 9 | "os" 10 | ) 11 | 12 | func main() { 13 | if len(os.Args) != 3 { 14 | log.Fatalf("Usage: %s network-type service\n", os.Args[0]) 15 | } 16 | networkType := os.Args[1] 17 | service := os.Args[2] 18 | port, err := net.LookupPort(networkType, service) 19 | if err != nil { 20 | log.Fatalln("Error: ", err.Error()) 21 | } 22 | fmt.Println("Service port ", port) 23 | } 24 | -------------------------------------------------------------------------------- /ch3/mask.go: -------------------------------------------------------------------------------- 1 | /* Mask 2 | */ 3 | package main 4 | 5 | import ( 6 | "fmt" 7 | "log" 8 | "net" 9 | "os" 10 | "strconv" 11 | ) 12 | 13 | func main() { 14 | if len(os.Args) != 4 { 15 | log.Fatalf("Usage: %s dotted-ip-addr ones bits\n", os.Args[0]) 16 | } 17 | dotAddr := os.Args[1] 18 | ones, _ := strconv.Atoi(os.Args[2]) 19 | bits, _ := strconv.Atoi(os.Args[3]) 20 | addr := net.ParseIP(dotAddr) 21 | if addr == nil { 22 | log.Fatalln("nil Invalid address") 23 | } 24 | mask := net.CIDRMask(ones, bits) 25 | computedOnes, computedBits := mask.Size() 26 | network := addr.Mask(mask) 27 | fmt.Println("Address is ", addr.String(), 28 | "\nMask length is ", computedBits, 29 | "\nLeading ones count is ", computedOnes, 30 | "\nMask is (hex) ", mask.String(), 31 | "\nNetwork is ", network.String()) 32 | } 33 | -------------------------------------------------------------------------------- /ch3/ping.go: -------------------------------------------------------------------------------- 1 | /* Ping 2 | */ 3 | package main 4 | 5 | import ( 6 | "fmt" 7 | "log" 8 | "net" 9 | "os" 10 | ) 11 | 12 | // change this to your own IP address or leave set to 0.0.0.0 13 | const myIPAddress = "0.0.0.0" 14 | const ipv4HeaderSize = 20 15 | 16 | func main() { 17 | if len(os.Args) != 2 { 18 | log.Fatalln("Usage: ", os.Args[0], "host") 19 | } 20 | localAddr, err := net.ResolveIPAddr("ip4", myIPAddress) 21 | checkError(err) 22 | 23 | remoteAddr, err := net.ResolveIPAddr("ip4", os.Args[1]) 24 | checkError(err) 25 | 26 | conn, err := net.DialIP("ip4:icmp", localAddr, remoteAddr) 27 | checkError(err) 28 | 29 | var msg [512]byte 30 | msg[0] = 8 // echo 31 | msg[1] = 0 // code 0 32 | msg[2] = 0 // checksum, fix later 33 | msg[3] = 0 // checksum, fix later 34 | msg[4] = 0 // identifier[0] 35 | msg[5] = 13 // identifier[1] (arbitrary) 36 | msg[6] = 0 // sequence[0] 37 | msg[7] = 37 // sequence[1] (arbitrary) 38 | len := 8 39 | 40 | // now fix checksum bytes 41 | check := checkSum(msg[0:len]) 42 | msg[2] = byte(check >> 8) 43 | msg[3] = byte(check & 255) 44 | 45 | // send the message 46 | _, err = conn.Write(msg[0:len]) 47 | checkError(err) 48 | fmt.Print("Message sent: ") 49 | for n := 0; n < 8; n++ { 50 | fmt.Print(" ", msg[n]) 51 | } 52 | fmt.Println() 53 | 54 | // receive a reply 55 | size, err2 := conn.Read(msg[0:]) 56 | checkError(err2) 57 | fmt.Print("Message received:") 58 | for n := ipv4HeaderSize; n < size; n++ { 59 | fmt.Print(" ", msg[n]) 60 | } 61 | fmt.Println() 62 | os.Exit(0) 63 | } 64 | func checkSum(msg []byte) uint16 { 65 | sum := 0 66 | // assume even for now 67 | for n := 0; n < len(msg); n += 2 { 68 | sum += int(msg[n])*256 + int(msg[n+1]) 69 | } 70 | sum = (sum >> 16) + (sum & 0xffff) 71 | sum += (sum >> 16) 72 | var answer uint16 = uint16(^sum) 73 | return answer 74 | } 75 | func checkError(err error) { 76 | if err != nil { 77 | log.Fatalln("Fatal error: %s", err.Error()) 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /ch3/resolveip.go: -------------------------------------------------------------------------------- 1 | /* ResolveIP 2 | */ 3 | package main 4 | 5 | import ( 6 | "fmt" 7 | "log" 8 | "net" 9 | "os" 10 | ) 11 | 12 | func main() { 13 | if len(os.Args) != 2 { 14 | log.Fatalf("Usage: %s hostname\n", os.Args[0]) 15 | } 16 | name := os.Args[1] 17 | addr, err := net.ResolveIPAddr("ip", name) 18 | if err != nil { 19 | log.Fatalln("Resolution error", err.Error()) 20 | } 21 | fmt.Println("Resolved address is ", addr.String()) 22 | } 23 | -------------------------------------------------------------------------------- /ch3/simpleechoserver.go: -------------------------------------------------------------------------------- 1 | /* SimpleEchoServer 2 | */ 3 | package main 4 | 5 | import ( 6 | "fmt" 7 | "log" 8 | "net" 9 | ) 10 | 11 | func main() { 12 | service := ":1201" 13 | tcpAddr, err := net.ResolveTCPAddr("tcp4", service) 14 | checkError(err) 15 | listener, err := net.ListenTCP("tcp", tcpAddr) 16 | checkError(err) 17 | for { 18 | conn, err := listener.Accept() 19 | if err != nil { 20 | continue 21 | } 22 | handleClient(conn) 23 | conn.Close() // we're finished 24 | } 25 | } 26 | func handleClient(conn net.Conn) { 27 | var buf [512]byte 28 | for { 29 | n, err := conn.Read(buf[0:]) 30 | checkError(err) 31 | fmt.Println(string(buf[0:])) 32 | _, err = conn.Write(buf[0:n]) 33 | checkError(err) 34 | } 35 | } 36 | func checkError(err error) { 37 | if err != nil { 38 | log.Fatalln("Fatal error: %s", err.Error()) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /ch3/threadedechoserver.go: -------------------------------------------------------------------------------- 1 | /* ThreadedEchoServer 2 | */ 3 | package main 4 | 5 | import ( 6 | "fmt" 7 | "log" 8 | "net" 9 | ) 10 | 11 | func main() { 12 | service := ":1201" 13 | tcpAddr, err := net.ResolveTCPAddr("tcp4", service) 14 | checkError(err) 15 | listener, err := net.ListenTCP("tcp", tcpAddr) 16 | checkError(err) 17 | for { 18 | conn, err := listener.Accept() 19 | if err != nil { 20 | continue 21 | } 22 | // run as a goroutine 23 | go handleClient(conn) 24 | } 25 | } 26 | func handleClient(conn net.Conn) { 27 | // close connection on exit 28 | defer conn.Close() 29 | var buf [512]byte 30 | for { 31 | // read up to 512 bytes 32 | n, err := conn.Read(buf[0:]) 33 | checkError(err) 34 | fmt.Println(string(buf[0:])) 35 | // write the n bytes read 36 | _, err = conn.Write(buf[0:n]) 37 | checkError(err) 38 | } 39 | } 40 | func checkError(err error) { 41 | if err != nil { 42 | log.Fatalf("Fatal error: %s", err.Error()) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /ch3/threadedipechoserver.go: -------------------------------------------------------------------------------- 1 | /* ThreadedIPEchoServer 2 | */ 3 | package main 4 | 5 | import ( 6 | "log" 7 | "net" 8 | ) 9 | 10 | func main() { 11 | service := ":1200" 12 | listener, err := net.Listen("tcp", service) 13 | checkError(err) 14 | for { 15 | conn, err := listener.Accept() 16 | if err != nil { 17 | continue 18 | } 19 | go handleClient(conn) 20 | } 21 | } 22 | func handleClient(conn net.Conn) { 23 | defer conn.Close() 24 | var buf [512]byte 25 | for { 26 | n, err := conn.Read(buf[0:]) 27 | checkError(err) 28 | 29 | _, err = conn.Write(buf[0:n]) 30 | checkError(err) 31 | } 32 | } 33 | func checkError(err error) { 34 | if err != nil { 35 | log.Fatalf("Fatal error: %s", err.Error()) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /ch3/udpdaytimeclient.go: -------------------------------------------------------------------------------- 1 | /* UDPDaytimeClient 2 | */ 3 | package main 4 | 5 | import ( 6 | "fmt" 7 | "log" 8 | "net" 9 | "os" 10 | ) 11 | 12 | func main() { 13 | if len(os.Args) != 2 { 14 | log.Fatalf("Usage: %s host:port", os.Args[0]) 15 | } 16 | service := os.Args[1] 17 | udpAddr, err := net.ResolveUDPAddr("udp", service) 18 | checkError(err) 19 | conn, err := net.DialUDP("udp", nil, udpAddr) 20 | checkError(err) 21 | _, err = conn.Write([]byte("anything")) 22 | checkError(err) 23 | var buf [512]byte 24 | n, err := conn.Read(buf[0:]) 25 | checkError(err) 26 | fmt.Println(string(buf[0:n])) 27 | } 28 | func checkError(err error) { 29 | if err != nil { 30 | log.Fatalln("Fatal error ", err.Error()) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /ch3/udpdaytimeserver.go: -------------------------------------------------------------------------------- 1 | /* UDPDaytimeServer 2 | */ 3 | package main 4 | 5 | import ( 6 | "log" 7 | "net" 8 | "time" 9 | ) 10 | 11 | func main() { 12 | service := ":1200" 13 | udpAddr, err := net.ResolveUDPAddr("udp", service) 14 | checkError(err) 15 | conn, err := net.ListenUDP("udp", udpAddr) 16 | checkError(err) 17 | for { 18 | handleClient(conn) 19 | } 20 | } 21 | func handleClient(conn *net.UDPConn) { 22 | var buf [512]byte 23 | _, addr, err := conn.ReadFromUDP(buf[0:]) 24 | checkError(err) 25 | daytime := time.Now().String() 26 | conn.WriteToUDP([]byte(daytime), addr) 27 | } 28 | func checkError(err error) { 29 | if err != nil { 30 | log.Fatalln("Fatal error ", err.Error()) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /ch4/asn1.go: -------------------------------------------------------------------------------- 1 | /* 2 | ASN1 example. 3 | */ 4 | package main 5 | 6 | import ( 7 | "encoding/asn1" 8 | "fmt" 9 | "time" 10 | ) 11 | 12 | func thirteen() { 13 | val := 13 14 | mdata, _ := asn1.Marshal(val) 15 | var n int 16 | _, _ = asn1.Unmarshal(mdata, &n) 17 | fmt.Printf("Before marshal: %v, After unmarshal: %v\n", val, n) 18 | } 19 | 20 | func ascii() { 21 | s := "hello" 22 | mdata, _ := asn1.Marshal(s) 23 | var newstr string 24 | _, _ = asn1.Unmarshal(mdata, &newstr) 25 | fmt.Printf("Before marshal: %v, After unmarshal: %v\n", s, newstr) 26 | } 27 | 28 | func myTime() { 29 | t := time.Now() 30 | mdata, _ := asn1.Marshal(t) 31 | var newtime = new(time.Time) 32 | _, _ = asn1.Unmarshal(mdata, newtime) 33 | fmt.Printf("Before marshal: %v, After unmarshal: %v\n", t, newtime) 34 | } 35 | 36 | func main() { 37 | thirteen() 38 | ascii() 39 | myTime() 40 | } 41 | -------------------------------------------------------------------------------- /ch4/asn1basic.go: -------------------------------------------------------------------------------- 1 | /* ASN.1 Basic 2 |  */ 3 | 4 | package main 5 | 6 | import ( 7 | "encoding/asn1" 8 | "fmt" 9 | "time" 10 | ) 11 | 12 | func main() { 13 | // time pointer to time value 14 | t := time.Now() 15 | fmt.Println("Before marshalling: ", t.String()) 16 | mdata, _ := asn1.Marshal(t) 17 | var newtime = new(time.Time) 18 | asn1.Unmarshal(mdata, newtime) 19 | fmt.Println("After marshal/unmarshal: ", newtime.String()) 20 | 21 | // vulgar fraction, string to string 22 | s := "hello \u00bc" 23 | fmt.Println("Before marshalling: ", s) 24 | mdata2, _ := asn1.Marshal(s) 25 | var newstr string 26 | asn1.Unmarshal(mdata2, &newstr) 27 | fmt.Println("After marshal/unmarshal: ", newstr) 28 | } 29 | -------------------------------------------------------------------------------- /ch4/asn1fields.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/asn1" 5 | "fmt" 6 | "log" 7 | ) 8 | 9 | type MyType struct { 10 | F1 rune 11 | F2 int 12 | } 13 | 14 | type YourType struct { 15 | F3 rune 16 | } 17 | 18 | type TheirType struct { 19 | F4 byte 20 | } 21 | 22 | func main() { 23 | // this first example works 24 | t1 := MyType{'ロ', 1} 25 | mdata1, _ := asn1.Marshal(t1) 26 | 27 | t2 := new(YourType) 28 | _, err := asn1.Unmarshal(mdata1, t2) 29 | fmt.Printf("Before marshal: %v, after unmarshal: %v\n", t1, t2) 30 | checkError(err) 31 | 32 | // syntax error (fails to fill all fields) 33 | y := YourType{'ロ'} 34 | mdata2, _ := asn1.Marshal(y) 35 | z := new(MyType) 36 | _, err = asn1.Unmarshal(mdata2, z) 37 | fmt.Printf("Before marshal: %v, after unmarshal: %v\n", y, z) 38 | checkError(err) 39 | 40 | // structural error (incorrect Go type byte != rune) 41 | t3 := new(TheirType) 42 | _, err = asn1.Unmarshal(mdata1, t3) 43 | fmt.Printf("Before marshal: %v, after unmarshal: %v\n", t1, t3) 44 | checkError(err) 45 | } 46 | 47 | func checkError(err error) { 48 | if err != nil { 49 | log.Println(err.Error()) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /ch4/asn1pointers.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/asn1" 5 | "fmt" 6 | ) 7 | 8 | type T struct { 9 | S string 10 | I int 11 | } 12 | 13 | func main() { 14 | // using variables 15 | t1 := T{"ok", 1} 16 | mdata1, _ := asn1.Marshal(t1) 17 | var newT1 T 18 | asn1.Unmarshal(mdata1, &newT1) 19 | fmt.Printf("Before marshal: %v, after unmarshal: %v\n", t1, newT1) 20 | 21 | // using pointers 22 | var t2 = new(T) 23 | t2.S = "still ok" 24 | t2.I = 2 25 | mdata2, _ := asn1.Marshal(*t2) 26 | var newT2 = new(T) 27 | asn1.Unmarshal(mdata2, newT2) 28 | fmt.Printf("Before marshal: %v, after unmarshal: %v\n", t2, newT2) 29 | } 30 | -------------------------------------------------------------------------------- /ch4/asndaytimeclient.go: -------------------------------------------------------------------------------- 1 | /* ASN.1 DaytimeClient 2 |  */ 3 | package main 4 | 5 | import ( 6 | "bytes" 7 | "encoding/asn1" 8 | "fmt" 9 | "io" 10 | "log" 11 | "net" 12 | "os" 13 | "time" 14 | ) 15 | 16 | func main() { 17 | if len(os.Args) != 2 { 18 | log.Fatalln("Usage: %s host:port", os.Args[0]) 19 | } 20 | service := os.Args[1] 21 | conn, err := net.Dial("tcp", service) 22 | checkError(err) 23 | result, err := readFully(conn) 24 | checkError(err) 25 | var newtime time.Time 26 | _, err1 := asn1.Unmarshal(result, &newtime) 27 | checkError(err1) 28 | fmt.Println("After marshal/unmarshal: ", newtime.String()) 29 | } 30 | 31 | func readFully(conn net.Conn) ([]byte, error) { 32 | defer conn.Close() 33 | result := bytes.NewBuffer(nil) 34 | var buf [512]byte 35 | for { 36 | n, err := conn.Read(buf[0:]) 37 | result.Write(buf[0:n]) 38 | if err != nil { 39 | if err == io.EOF { 40 | break 41 | } 42 | return nil, err 43 | } 44 | } 45 | return result.Bytes(), nil 46 | } 47 | 48 | func checkError(err error) { 49 | if err != nil { 50 | log.Fatalln("Fatal error: %s", err.Error()) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /ch4/asndaytimeserver.go: -------------------------------------------------------------------------------- 1 | /* ASN1 DaytimeServer 2 |  */ 3 | package main 4 | 5 | import ( 6 | "encoding/asn1" 7 | "log" 8 | "net" 9 | "time" 10 | ) 11 | 12 | func main() { 13 | service := ":1200" 14 | tcpAddr, err := net.ResolveTCPAddr("tcp", service) 15 | checkError(err) 16 | listener, err := net.ListenTCP("tcp", tcpAddr) 17 | checkError(err) 18 | for { 19 | conn, err := listener.Accept() 20 | if err != nil { 21 | continue 22 | } 23 | daytime := time.Now() 24 | // ignore returned errors 25 | mdata, _ := asn1.Marshal(daytime) 26 | conn.Write(mdata) 27 | conn.Close() // we're finished 28 | } 29 | } 30 | 31 | func checkError(err error) { 32 | if err != nil { 33 | log.Fatalln("Fatal error: %s", err.Error()) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /ch4/badtype/code.go: -------------------------------------------------------------------------------- 1 | /* ./p/code.go 2 | */ 3 | package p 4 | 5 | type T struct { 6 | f int 7 | F int 8 | } 9 | 10 | -------------------------------------------------------------------------------- /ch4/badtype/go.mod: -------------------------------------------------------------------------------- 1 | module badtype 2 | 3 | go 1.18 4 | -------------------------------------------------------------------------------- /ch4/base64.go: -------------------------------------------------------------------------------- 1 | /* 2 | Base64 3 | */ 4 | package main 5 | 6 | import ( 7 | "encoding/base64" 8 | "fmt" 9 | ) 10 | 11 | func main() { 12 | eightBitData := []byte{1, 2, 3, 4, 5, 6, 7, 8} 13 | enc := base64.StdEncoding.EncodeToString(eightBitData) 14 | dec, _ := base64.StdEncoding.DecodeString(enc) 15 | fmt.Println("Original data ", eightBitData) 16 | fmt.Println("Encoded string ", enc) 17 | fmt.Println("Decoded data ", dec) 18 | } 19 | -------------------------------------------------------------------------------- /ch4/driver.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | /* driver.go 4 | */ 5 | 6 | import ( 7 | "encoding/asn1" 8 | "fmt" 9 | "badtype" 10 | ) 11 | 12 | func main() { 13 | // using variables 14 | t1 := p.T{F:1} 15 | mdata1, err := asn1.Marshal(t1) 16 | fmt.Println(err) 17 | var newT1 p.T 18 | _, err = asn1.Unmarshal(mdata1, &newT1) 19 | fmt.Println(err) 20 | } 21 | 22 | -------------------------------------------------------------------------------- /ch4/gobechoclient.go: -------------------------------------------------------------------------------- 1 | /* Gob EchoClient 2 | */ 3 | package main 4 | 5 | import ( 6 | "encoding/gob" 7 | "log" 8 | "net" 9 | "os" 10 | ) 11 | 12 | type Person struct { 13 | Name Name 14 | Email []Email 15 | } 16 | 17 | type Name struct { 18 | Family string 19 | Personal string 20 | } 21 | 22 | type Email struct { 23 | Kind string 24 | Address string 25 | } 26 | 27 | func main() { 28 | if len(os.Args) != 2 { 29 | log.Fatalln("Usage: ", os.Args[0], "host:port") 30 | } 31 | person := Person{ 32 | Name: Name{Family: "Newmarch", Personal: "Jan"}, 33 | Email: []Email{ 34 | Email{Kind: "home", Address: "jan@newmarch.name"}, 35 | Email{Kind: "work", Address: "j.newmarch@boxhill.edu.au"}, 36 | }, 37 | } 38 | service := os.Args[1] 39 | conn, err := net.Dial("tcp", service) 40 | checkError(err) 41 | encoder := gob.NewEncoder(conn) 42 | decoder := gob.NewDecoder(conn) 43 | for n := 0; n < 10; n++ { 44 | encoder.Encode(person) 45 | var newPerson Person 46 | decoder.Decode(&newPerson) 47 | 48 | } 49 | } 50 | 51 | func checkError(err error) { 52 | if err != nil { 53 | log.Fatalln("Fatal error ", err.Error()) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /ch4/gobechoserver.go: -------------------------------------------------------------------------------- 1 | /* Gob EchoServer 2 | */ 3 | package main 4 | 5 | import ( 6 | "encoding/gob" 7 | "fmt" 8 | "log" 9 | "net" 10 | ) 11 | 12 | type Person struct { 13 | Name Name 14 | Email []Email 15 | } 16 | 17 | type Name struct { 18 | Family string 19 | Personal string 20 | } 21 | type Email struct { 22 | Kind string 23 | Address string 24 | } 25 | 26 | func main() { 27 | service := "0.0.0.0:1200" 28 | tcpAddr, err := net.ResolveTCPAddr("tcp", service) 29 | checkError(err) 30 | listener, err := net.ListenTCP("tcp", tcpAddr) 31 | checkError(err) 32 | for { 33 | conn, err := listener.Accept() 34 | if err != nil { 35 | continue 36 | } 37 | encoder := gob.NewEncoder(conn) 38 | decoder := gob.NewDecoder(conn) 39 | for n := 0; n < 10; n++ { 40 | var person Person 41 | decoder.Decode(&person) 42 | fmt.Println(person) 43 | encoder.Encode(person) 44 | } 45 | conn.Close() // we're finished 46 | } 47 | } 48 | 49 | func checkError(err error) { 50 | if err != nil { 51 | log.Fatalln("Fatal error ", err.Error()) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /ch4/jsonechoclient.go: -------------------------------------------------------------------------------- 1 | /* JSON EchoClient 2 | */ 3 | package main 4 | 5 | import ( 6 | "bytes" 7 | "encoding/json" 8 | "fmt" 9 | "io" 10 | "log" 11 | "net" 12 | "os" 13 | ) 14 | 15 | type Person struct { 16 | Name Name 17 | Email []Email 18 | } 19 | 20 | type Name struct { 21 | Family string 22 | Personal string 23 | } 24 | 25 | type Email struct { 26 | Kind string 27 | Address string 28 | } 29 | 30 | func main() { 31 | if len(os.Args) != 2 { 32 | log.Fatalln("Usage: ", os.Args[0], "host:port") 33 | } 34 | person := Person{ 35 | Name: Name{Family: "Newmarch", Personal: "Jan"}, 36 | Email: []Email{ 37 | Email{Kind: "home", Address: "jan@newmarch.name"}, 38 | Email{Kind: "work", Address: "j.newmarch@boxhill.edu.au"}, 39 | }, 40 | } 41 | service := os.Args[1] 42 | conn, err := net.Dial("tcp", service) 43 | checkError(err) 44 | defer conn.Close() 45 | for n := 0; n < 10; n++ { 46 | data, _ := json.Marshal(person) 47 | conn.Write(data) 48 | 49 | var newPerson Person 50 | buf, _ := readFully(conn) 51 | err = json.Unmarshal(buf, &newPerson) 52 | fmt.Println(newPerson) 53 | } 54 | } 55 | 56 | func readFully(conn net.Conn) ([]byte, error) { 57 | result := bytes.NewBuffer(nil) 58 | var buf [512]byte 59 | for { 60 | n, err := conn.Read(buf[0:]) 61 | result.Write(buf[0:n]) 62 | 63 | if err != nil { 64 | if err == io.EOF { 65 | break 66 | } 67 | return nil, err 68 | } 69 | if n < 512 { 70 | break 71 | } 72 | } 73 | return result.Bytes(), nil 74 | } 75 | 76 | func checkError(err error) { 77 | if err != nil { 78 | log.Fatalln("Fatal error ", err.Error()) 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /ch4/jsonechoserver.go: -------------------------------------------------------------------------------- 1 | /* JSON EchoServer 2 | */ 3 | package main 4 | 5 | import ( 6 | "bytes" 7 | "encoding/json" 8 | "fmt" 9 | "io" 10 | "net" 11 | "log" 12 | ) 13 | 14 | type Person struct { 15 | Name Name 16 | Email []Email 17 | } 18 | 19 | type Name struct { 20 | Family string 21 | Personal string 22 | } 23 | 24 | type Email struct { 25 | Kind string 26 | Address string 27 | } 28 | 29 | func main() { 30 | service := "0.0.0.0:1200" 31 | tcpAddr, err := net.ResolveTCPAddr("tcp", service) 32 | checkError(err) 33 | listener, err := net.ListenTCP("tcp", tcpAddr) 34 | checkError(err) 35 | for { 36 | conn, err := listener.Accept() 37 | if err != nil { 38 | continue 39 | } 40 | for n := 0; n < 10; n++ { 41 | var person Person 42 | buf, _ := readFully(conn) 43 | err = json.Unmarshal(buf, &person) 44 | 45 | fmt.Println(person) 46 | 47 | data, _ := json.Marshal(person) 48 | conn.Write(data) 49 | } 50 | conn.Close() // we're finished 51 | } 52 | } 53 | 54 | func readFully(conn net.Conn) ([]byte, error) { 55 | result := bytes.NewBuffer(nil) 56 | var buf [512]byte 57 | for { 58 | n, err := conn.Read(buf[0:]) 59 | result.Write(buf[0:n]) 60 | 61 | if err != nil { 62 | if err == io.EOF { 63 | break 64 | } 65 | return nil, err 66 | } 67 | if n < 512 { 68 | break 69 | } 70 | } 71 | return result.Bytes(), nil 72 | } 73 | 74 | func checkError(err error) { 75 | if err != nil { 76 | log.Fatalln("Fatal error ", err.Error()) 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /ch4/loadgob.go: -------------------------------------------------------------------------------- 1 | /* LoadGob 2 | */ 3 | package main 4 | 5 | import ( 6 | "encoding/gob" 7 | "fmt" 8 | "log" 9 | "os" 10 | ) 11 | 12 | type Person struct { 13 | Name Name 14 | Email []Email 15 | } 16 | 17 | type Name struct { 18 | Family string 19 | Personal string 20 | } 21 | 22 | type Email struct { 23 | Kind string 24 | Address string 25 | } 26 | 27 | func main() { 28 | var person Person 29 | loadGob("person.gob", &person) 30 | fmt.Println(person) 31 | } 32 | 33 | func loadGob(fileName string, key interface{}) { 34 | inFile, err := os.Open(fileName) 35 | checkError(err) 36 | decoder := gob.NewDecoder(inFile) 37 | err = decoder.Decode(key) 38 | checkError(err) 39 | inFile.Close() 40 | } 41 | 42 | func checkError(err error) { 43 | if err != nil { 44 | log.Fatalln("Fatal error ", err.Error()) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /ch4/loadjson.go: -------------------------------------------------------------------------------- 1 | /* LoadJSON 2 | */ 3 | package main 4 | 5 | import ( 6 | "encoding/json" 7 | "fmt" 8 | "log" 9 | "os" 10 | ) 11 | 12 | type Person struct { 13 | Name Name 14 | Email []Email 15 | } 16 | 17 | type Name struct { 18 | Family string 19 | Personal string 20 | } 21 | 22 | type Email struct { 23 | Kind string 24 | Address string 25 | } 26 | 27 | func main() { 28 | var person Person 29 | loadJSON("person.json", &person) 30 | fmt.Printf("%v\n", person) 31 | } 32 | 33 | func loadJSON(fileName string, key interface{}) { 34 | data, err := os.ReadFile(fileName) 35 | checkError(err) 36 | err = json.Unmarshal(data, key) 37 | checkError(err) 38 | } 39 | 40 | func checkError(err error) { 41 | if err != nil { 42 | log.Fatalln("Fatal error ", err.Error()) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /ch4/myapp/protocolbuffer.go: -------------------------------------------------------------------------------- 1 | /* ProtocolBuffer 2 | */ 3 | 4 | package main 5 | 6 | import ( 7 | "myapp/protos" 8 | 9 | "google.golang.org/protobuf/proto" 10 | 11 | "fmt" 12 | ) 13 | 14 | func main() { 15 | name := protos.Person_Name{ 16 | Family: "newmarch", 17 | Personal: "jan", 18 | } 19 | email1 := protos.Person_Email{ 20 | Kind: "home", 21 | Address: "jan@newmarch.name", 22 | } 23 | email2 := protos.Person_Email{ 24 | Kind: "work", 25 | Address: "j.newmarch@boxhill.edu.au", 26 | } 27 | emails := []*protos.Person_Email{&email1, &email2} 28 | 29 | p := protos.Person{ 30 | Name: &name, 31 | Email: emails, 32 | } 33 | 34 | fmt.Println(p) 35 | 36 | data, _ := proto.Marshal(&p) 37 | 38 | newP := protos.Person{} 39 | 40 | proto.Unmarshal(data, &newP) 41 | 42 | fmt.Printf("%v\n", newP) 43 | 44 | if p.Name.Personal == newP.Name.Personal && p.Email[0].Address == newP.Email[0].Address { 45 | fmt.Println("same") 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /ch4/myapp/protos/personv3.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go. DO NOT EDIT. 2 | // versions: 3 | // protoc-gen-go v1.27.1 4 | // protoc v3.19.1 5 | // source: personv3.proto 6 | 7 | package protos 8 | 9 | import ( 10 | protoreflect "google.golang.org/protobuf/reflect/protoreflect" 11 | protoimpl "google.golang.org/protobuf/runtime/protoimpl" 12 | reflect "reflect" 13 | sync "sync" 14 | ) 15 | 16 | const ( 17 | // Verify that this generated code is sufficiently up-to-date. 18 | _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) 19 | // Verify that runtime/protoimpl is sufficiently up-to-date. 20 | _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) 21 | ) 22 | 23 | type Person struct { 24 | state protoimpl.MessageState 25 | sizeCache protoimpl.SizeCache 26 | unknownFields protoimpl.UnknownFields 27 | 28 | Name *Person_Name `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` 29 | Email []*Person_Email `protobuf:"bytes,2,rep,name=email,proto3" json:"email,omitempty"` 30 | } 31 | 32 | func (x *Person) Reset() { 33 | *x = Person{} 34 | if protoimpl.UnsafeEnabled { 35 | mi := &file_personv3_proto_msgTypes[0] 36 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 37 | ms.StoreMessageInfo(mi) 38 | } 39 | } 40 | 41 | func (x *Person) String() string { 42 | return protoimpl.X.MessageStringOf(x) 43 | } 44 | 45 | func (*Person) ProtoMessage() {} 46 | 47 | func (x *Person) ProtoReflect() protoreflect.Message { 48 | mi := &file_personv3_proto_msgTypes[0] 49 | if protoimpl.UnsafeEnabled && x != nil { 50 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 51 | if ms.LoadMessageInfo() == nil { 52 | ms.StoreMessageInfo(mi) 53 | } 54 | return ms 55 | } 56 | return mi.MessageOf(x) 57 | } 58 | 59 | // Deprecated: Use Person.ProtoReflect.Descriptor instead. 60 | func (*Person) Descriptor() ([]byte, []int) { 61 | return file_personv3_proto_rawDescGZIP(), []int{0} 62 | } 63 | 64 | func (x *Person) GetName() *Person_Name { 65 | if x != nil { 66 | return x.Name 67 | } 68 | return nil 69 | } 70 | 71 | func (x *Person) GetEmail() []*Person_Email { 72 | if x != nil { 73 | return x.Email 74 | } 75 | return nil 76 | } 77 | 78 | type Person_Name struct { 79 | state protoimpl.MessageState 80 | sizeCache protoimpl.SizeCache 81 | unknownFields protoimpl.UnknownFields 82 | 83 | Family string `protobuf:"bytes,1,opt,name=family,proto3" json:"family,omitempty"` 84 | Personal string `protobuf:"bytes,2,opt,name=personal,proto3" json:"personal,omitempty"` 85 | } 86 | 87 | func (x *Person_Name) Reset() { 88 | *x = Person_Name{} 89 | if protoimpl.UnsafeEnabled { 90 | mi := &file_personv3_proto_msgTypes[1] 91 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 92 | ms.StoreMessageInfo(mi) 93 | } 94 | } 95 | 96 | func (x *Person_Name) String() string { 97 | return protoimpl.X.MessageStringOf(x) 98 | } 99 | 100 | func (*Person_Name) ProtoMessage() {} 101 | 102 | func (x *Person_Name) ProtoReflect() protoreflect.Message { 103 | mi := &file_personv3_proto_msgTypes[1] 104 | if protoimpl.UnsafeEnabled && x != nil { 105 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 106 | if ms.LoadMessageInfo() == nil { 107 | ms.StoreMessageInfo(mi) 108 | } 109 | return ms 110 | } 111 | return mi.MessageOf(x) 112 | } 113 | 114 | // Deprecated: Use Person_Name.ProtoReflect.Descriptor instead. 115 | func (*Person_Name) Descriptor() ([]byte, []int) { 116 | return file_personv3_proto_rawDescGZIP(), []int{0, 0} 117 | } 118 | 119 | func (x *Person_Name) GetFamily() string { 120 | if x != nil { 121 | return x.Family 122 | } 123 | return "" 124 | } 125 | 126 | func (x *Person_Name) GetPersonal() string { 127 | if x != nil { 128 | return x.Personal 129 | } 130 | return "" 131 | } 132 | 133 | type Person_Email struct { 134 | state protoimpl.MessageState 135 | sizeCache protoimpl.SizeCache 136 | unknownFields protoimpl.UnknownFields 137 | 138 | Kind string `protobuf:"bytes,1,opt,name=kind,proto3" json:"kind,omitempty"` 139 | Address string `protobuf:"bytes,2,opt,name=address,proto3" json:"address,omitempty"` 140 | } 141 | 142 | func (x *Person_Email) Reset() { 143 | *x = Person_Email{} 144 | if protoimpl.UnsafeEnabled { 145 | mi := &file_personv3_proto_msgTypes[2] 146 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 147 | ms.StoreMessageInfo(mi) 148 | } 149 | } 150 | 151 | func (x *Person_Email) String() string { 152 | return protoimpl.X.MessageStringOf(x) 153 | } 154 | 155 | func (*Person_Email) ProtoMessage() {} 156 | 157 | func (x *Person_Email) ProtoReflect() protoreflect.Message { 158 | mi := &file_personv3_proto_msgTypes[2] 159 | if protoimpl.UnsafeEnabled && x != nil { 160 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 161 | if ms.LoadMessageInfo() == nil { 162 | ms.StoreMessageInfo(mi) 163 | } 164 | return ms 165 | } 166 | return mi.MessageOf(x) 167 | } 168 | 169 | // Deprecated: Use Person_Email.ProtoReflect.Descriptor instead. 170 | func (*Person_Email) Descriptor() ([]byte, []int) { 171 | return file_personv3_proto_rawDescGZIP(), []int{0, 1} 172 | } 173 | 174 | func (x *Person_Email) GetKind() string { 175 | if x != nil { 176 | return x.Kind 177 | } 178 | return "" 179 | } 180 | 181 | func (x *Person_Email) GetAddress() string { 182 | if x != nil { 183 | return x.Address 184 | } 185 | return "" 186 | } 187 | 188 | var File_personv3_proto protoreflect.FileDescriptor 189 | 190 | var file_personv3_proto_rawDesc = []byte{ 191 | 0x0a, 0x0e, 0x70, 0x65, 0x72, 0x73, 0x6f, 0x6e, 0x76, 0x33, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 192 | 0x12, 0x06, 0x70, 0x65, 0x72, 0x73, 0x6f, 0x6e, 0x22, 0xd0, 0x01, 0x0a, 0x06, 0x50, 0x65, 0x72, 193 | 0x73, 0x6f, 0x6e, 0x12, 0x27, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 194 | 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x65, 0x72, 0x73, 0x6f, 0x6e, 0x2e, 0x50, 0x65, 0x72, 0x73, 0x6f, 195 | 0x6e, 0x2e, 0x4e, 0x61, 0x6d, 0x65, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x2a, 0x0a, 0x05, 196 | 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x70, 0x65, 197 | 0x72, 0x73, 0x6f, 0x6e, 0x2e, 0x50, 0x65, 0x72, 0x73, 0x6f, 0x6e, 0x2e, 0x45, 0x6d, 0x61, 0x69, 198 | 0x6c, 0x52, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x1a, 0x3a, 0x0a, 0x04, 0x4e, 0x61, 0x6d, 0x65, 199 | 0x12, 0x16, 0x0a, 0x06, 0x66, 0x61, 0x6d, 0x69, 0x6c, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 200 | 0x52, 0x06, 0x66, 0x61, 0x6d, 0x69, 0x6c, 0x79, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x65, 0x72, 0x73, 201 | 0x6f, 0x6e, 0x61, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x65, 0x72, 0x73, 202 | 0x6f, 0x6e, 0x61, 0x6c, 0x1a, 0x35, 0x0a, 0x05, 0x45, 0x6d, 0x61, 0x69, 0x6c, 0x12, 0x12, 0x0a, 203 | 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6b, 0x69, 0x6e, 204 | 0x64, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x02, 0x20, 0x01, 205 | 0x28, 0x09, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x42, 0x09, 0x5a, 0x07, 0x2f, 206 | 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, 207 | } 208 | 209 | var ( 210 | file_personv3_proto_rawDescOnce sync.Once 211 | file_personv3_proto_rawDescData = file_personv3_proto_rawDesc 212 | ) 213 | 214 | func file_personv3_proto_rawDescGZIP() []byte { 215 | file_personv3_proto_rawDescOnce.Do(func() { 216 | file_personv3_proto_rawDescData = protoimpl.X.CompressGZIP(file_personv3_proto_rawDescData) 217 | }) 218 | return file_personv3_proto_rawDescData 219 | } 220 | 221 | var file_personv3_proto_msgTypes = make([]protoimpl.MessageInfo, 3) 222 | var file_personv3_proto_goTypes = []interface{}{ 223 | (*Person)(nil), // 0: person.Person 224 | (*Person_Name)(nil), // 1: person.Person.Name 225 | (*Person_Email)(nil), // 2: person.Person.Email 226 | } 227 | var file_personv3_proto_depIdxs = []int32{ 228 | 1, // 0: person.Person.name:type_name -> person.Person.Name 229 | 2, // 1: person.Person.email:type_name -> person.Person.Email 230 | 2, // [2:2] is the sub-list for method output_type 231 | 2, // [2:2] is the sub-list for method input_type 232 | 2, // [2:2] is the sub-list for extension type_name 233 | 2, // [2:2] is the sub-list for extension extendee 234 | 0, // [0:2] is the sub-list for field type_name 235 | } 236 | 237 | func init() { file_personv3_proto_init() } 238 | func file_personv3_proto_init() { 239 | if File_personv3_proto != nil { 240 | return 241 | } 242 | if !protoimpl.UnsafeEnabled { 243 | file_personv3_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { 244 | switch v := v.(*Person); i { 245 | case 0: 246 | return &v.state 247 | case 1: 248 | return &v.sizeCache 249 | case 2: 250 | return &v.unknownFields 251 | default: 252 | return nil 253 | } 254 | } 255 | file_personv3_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { 256 | switch v := v.(*Person_Name); i { 257 | case 0: 258 | return &v.state 259 | case 1: 260 | return &v.sizeCache 261 | case 2: 262 | return &v.unknownFields 263 | default: 264 | return nil 265 | } 266 | } 267 | file_personv3_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { 268 | switch v := v.(*Person_Email); i { 269 | case 0: 270 | return &v.state 271 | case 1: 272 | return &v.sizeCache 273 | case 2: 274 | return &v.unknownFields 275 | default: 276 | return nil 277 | } 278 | } 279 | } 280 | type x struct{} 281 | out := protoimpl.TypeBuilder{ 282 | File: protoimpl.DescBuilder{ 283 | GoPackagePath: reflect.TypeOf(x{}).PkgPath(), 284 | RawDescriptor: file_personv3_proto_rawDesc, 285 | NumEnums: 0, 286 | NumMessages: 3, 287 | NumExtensions: 0, 288 | NumServices: 0, 289 | }, 290 | GoTypes: file_personv3_proto_goTypes, 291 | DependencyIndexes: file_personv3_proto_depIdxs, 292 | MessageInfos: file_personv3_proto_msgTypes, 293 | }.Build() 294 | File_personv3_proto = out.File 295 | file_personv3_proto_rawDesc = nil 296 | file_personv3_proto_goTypes = nil 297 | file_personv3_proto_depIdxs = nil 298 | } 299 | -------------------------------------------------------------------------------- /ch4/newbase64coders.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "encoding/base64" 6 | "fmt" 7 | "io" 8 | ) 9 | 10 | var ( 11 | enc *base64.Encoding = base64.StdEncoding.WithPadding('|') 12 | input []byte = []byte("∞a∞\x02ab") 13 | w bytes.Buffer 14 | ) 15 | 16 | func restoreViaDecoder() { 17 | var buf *bytes.Buffer = bytes.NewBuffer(w.Bytes()) 18 | var ior io.Reader = base64.NewDecoder(enc, buf) 19 | l := len(input) 20 | 21 | // adjust for unknown padding 22 | if l > 3 && l%3 != 0 { 23 | l = l + 2 24 | } 25 | 26 | restored := make([]byte, l) 27 | ior.Read(restored) 28 | fmt.Printf("%11s: %s %v\n", "viaDecoder", string(restored), restored) 29 | } 30 | 31 | func restoreViaEncoding() { 32 | var dst []byte = make([]byte, len(input)) 33 | enc.Decode(dst, w.Bytes()) 34 | fmt.Printf("%11s: %s %v\n", "viaEncoding", string(dst), dst) 35 | } 36 | 37 | func main() { 38 | fmt.Printf("%11s: %s %v\n", "input", string(input), input) 39 | 40 | var wc io.WriteCloser = base64.NewEncoder(enc, &w) 41 | 42 | wc.Write(input) 43 | wc.Close() 44 | 45 | fmt.Printf("%11s: %s %v\n", "ecoded", string(w.Bytes()), w.Bytes()) 46 | 47 | restoreViaDecoder() 48 | restoreViaEncoding() 49 | } 50 | -------------------------------------------------------------------------------- /ch4/personv3.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | option go_package = "/protos"; 4 | 5 | package person; 6 | 7 | message Person { 8 | message Name { 9 | string family = 1; 10 | string personal = 2; 11 | } 12 | message Email { 13 | string kind = 1; 14 | string address = 2; 15 | } 16 | Name name = 1; 17 | repeated Email email = 2; 18 | } 19 | -------------------------------------------------------------------------------- /ch4/prettyjson.go: -------------------------------------------------------------------------------- 1 | /* SaveJSON 2 | */ 3 | package main 4 | 5 | import ( 6 | "encoding/json" 7 | "log" 8 | "os" 9 | ) 10 | 11 | type Person struct { 12 | Name Name 13 | Email []Email 14 | } 15 | type Name struct { 16 | Family string 17 | Personal string 18 | } 19 | type Email struct { 20 | Kind string 21 | Address string 22 | } 23 | 24 | func main() { 25 | person := Person{ 26 | Name: Name{Family: "Newmarch", Personal: "Jan"}, 27 | Email: []Email{ 28 | Email{Kind: "home", Address: "jan@newmarch.name"}, 29 | Email{Kind: "work", Address: "j.newmarch@boxhill.edu.au"}, 30 | }, 31 | } 32 | prettyJSON("pretty_person.json", person) 33 | } 34 | func prettyJSON(fileName string, key interface{}) { 35 | data, err := json.MarshalIndent(key, " ", " ") 36 | checkError(err) 37 | err = os.WriteFile(fileName, data, 0600) 38 | checkError(err) 39 | } 40 | func checkError(err error) { 41 | if err != nil { 42 | log.Fatalln("Fatal error ", err.Error()) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /ch4/savegob.go: -------------------------------------------------------------------------------- 1 | /* SaveGob 2 | */ 3 | package main 4 | 5 | import ( 6 | "encoding/gob" 7 | "os" 8 | "log" 9 | ) 10 | 11 | type Person struct { 12 | Name Name 13 | Email []Email 14 | } 15 | 16 | type Name struct { 17 | Family string 18 | Personal string 19 | } 20 | 21 | type Email struct { 22 | Kind string 23 | Address string 24 | } 25 | 26 | func main() { 27 | person := Person{ 28 | Name: Name{Family: "Newmarch", Personal: "Jan"}, 29 | Email: []Email{ 30 | Email{Kind: "home", Address: "jan@newmarch.name"}, 31 | Email{Kind: "work", Address: "j.newmarch@boxhill.edu.au"}, 32 | }, 33 | } 34 | saveGob("person.gob", person) 35 | } 36 | 37 | func saveGob(fileName string, key interface{}) { 38 | outFile, err := os.Create(fileName) 39 | checkError(err) 40 | encoder := gob.NewEncoder(outFile) 41 | err = encoder.Encode(key) 42 | checkError(err) 43 | outFile.Close() 44 | } 45 | 46 | func checkError(err error) { 47 | if err != nil { 48 | log.Fatalln("Fatal error ", err.Error()) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /ch4/savejson.go: -------------------------------------------------------------------------------- 1 | /* SaveJSON 2 | */ 3 | package main 4 | 5 | import ( 6 | "encoding/json" 7 | "log" 8 | "os" 9 | ) 10 | 11 | type Person struct { 12 | Name Name 13 | Email []Email 14 | } 15 | 16 | type Name struct { 17 | Family string 18 | Personal string 19 | } 20 | 21 | type Email struct { 22 | Kind string 23 | Address string 24 | } 25 | 26 | func main() { 27 | person := Person{ 28 | Name: Name{Family: "Newmarch", Personal: "Jan"}, 29 | Email: []Email{ 30 | Email{Kind: "home", Address: "jan@newmarch.name"}, 31 | Email{Kind: "work", Address: "j.newmarch@boxhill.edu.au"}, 32 | }, 33 | } 34 | saveJSON("person.json", person) 35 | } 36 | 37 | func saveJSON(fileName string, key interface{}) { 38 | data, err := json.Marshal(key) 39 | checkError(err) 40 | err = os.WriteFile(fileName, data, 0600) 41 | checkError(err) 42 | } 43 | 44 | func checkError(err error) { 45 | if err != nil { 46 | log.Fatalln("Fatal error ", err.Error()) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /ch5/ftpclient.go: -------------------------------------------------------------------------------- 1 | /* FTPClient 2 | */ 3 | package main 4 | 5 | import ( 6 | "bufio" 7 | "bytes" 8 | "fmt" 9 | "net" 10 | "os" 11 | "strings" 12 | ) 13 | 14 | // strings used by the user interface 15 | const ( 16 | uiDir = "dir" 17 | uiCd = "cd" 18 | uiPwd = "pwd" 19 | uiQuit = "quit" 20 | ) 21 | 22 | // strings used across the network 23 | const ( 24 | DIR = "DIR" 25 | CD = "CD" 26 | PWD = "PWD" 27 | ) 28 | 29 | func main() { 30 | if len(os.Args) != 2 { 31 | fmt.Println("Usage: ", os.Args[0], "host") 32 | os.Exit(1) 33 | } 34 | host := os.Args[1] 35 | conn, err := net.Dial("tcp", host+":1202") 36 | checkError(err) 37 | reader := bufio.NewReader(os.Stdin) 38 | for { 39 | line, err := reader.ReadString('\n') 40 | // lose trailing whitespace 41 | line = strings.TrimRight(line, " \t\r\n") 42 | if err != nil { 43 | break 44 | } 45 | // split into command + arg 46 | strs := strings.SplitN(line, " ", 2) 47 | // decode user request 48 | switch strs[0] { 49 | case uiDir: 50 | dirRequest(conn) 51 | case uiCd: 52 | if len(strs) != 2 { 53 | fmt.Println("cd ") 54 | continue 55 | } 56 | fmt.Println("CD \"", strs[1], "\"") 57 | cdRequest(conn, strs[1]) 58 | case uiPwd: 59 | pwdRequest(conn) 60 | case uiQuit: 61 | conn.Close() 62 | os.Exit(0) 63 | default: 64 | fmt.Println("Unknown command") 65 | } 66 | } 67 | } 68 | 69 | func dirRequest(conn net.Conn) { 70 | conn.Write([]byte(DIR + " ")) 71 | var buf [512]byte 72 | result := bytes.NewBuffer(nil) 73 | for { 74 | // read till we hit a blank line 75 | n, _ := conn.Read(buf[0:]) 76 | result.Write(buf[0:n]) 77 | length := result.Len() 78 | contents := result.Bytes() 79 | if string(contents[length-4:]) == "\r\n\r\n" { 80 | fmt.Println(string(contents[0 : length-4])) 81 | return 82 | } 83 | } 84 | } 85 | 86 | func cdRequest(conn net.Conn, dir string) { 87 | conn.Write([]byte(CD + " " + dir)) 88 | var response [512]byte 89 | n, _ := conn.Read(response[0:]) 90 | s := string(response[0:n]) 91 | if s != "OK" { 92 | fmt.Println("Failed to change dir") 93 | } 94 | } 95 | 96 | func pwdRequest(conn net.Conn) { 97 | conn.Write([]byte(PWD)) 98 | var response [512]byte 99 | n, _ := conn.Read(response[0:]) 100 | s := string(response[0:n]) 101 | fmt.Println("Current dir \"" + s + "\"") 102 | } 103 | 104 | func checkError(err error) { 105 | if err != nil { 106 | fmt.Println("Fatal error ", err.Error()) 107 | os.Exit(1) 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /ch5/ftpserver.go: -------------------------------------------------------------------------------- 1 | /* FTP Server 2 | */ 3 | package main 4 | 5 | import ( 6 | "fmt" 7 | "net" 8 | "os" 9 | ) 10 | 11 | const ( 12 | DIR = "DIR" 13 | CD = "CD" 14 | PWD = "PWD" 15 | ) 16 | 17 | func main() { 18 | service := "0.0.0.0:1202" 19 | tcpAddr, err := net.ResolveTCPAddr("tcp", service) 20 | checkError(err) 21 | listener, err := net.ListenTCP("tcp", tcpAddr) 22 | checkError(err) 23 | for { 24 | conn, err := listener.Accept() 25 | if err != nil { 26 | continue 27 | } 28 | go handleClient(conn) 29 | } 30 | } 31 | 32 | func handleClient(conn net.Conn) { 33 | defer conn.Close() 34 | var buf [512]byte 35 | for { 36 | n, err := conn.Read(buf[0:]) 37 | if err != nil { 38 | conn.Close() 39 | return 40 | } 41 | s := string(buf[0:n]) 42 | // decode request 43 | if s[0:2] == CD { 44 | chdir(conn, s[3:]) 45 | } else if s[0:3] == DIR { 46 | dirList(conn) 47 | } else if s[0:3] == PWD { 48 | pwd(conn) 49 | } 50 | } 51 | } 52 | 53 | func chdir(conn net.Conn, s string) { 54 | if os.Chdir(s) == nil { 55 | conn.Write([]byte("OK")) 56 | } else { 57 | conn.Write([]byte("ERROR")) 58 | } 59 | } 60 | 61 | func pwd(conn net.Conn) { 62 | s, err := os.Getwd() 63 | if err != nil { 64 | conn.Write([]byte("")) 65 | return 66 | } 67 | conn.Write([]byte(s)) 68 | } 69 | 70 | func dirList(conn net.Conn) { 71 | // send a blank line on termination 72 | defer conn.Write([]byte("\r\n")) 73 | dir, err := os.Open(".") 74 | if err != nil { 75 | return 76 | } 77 | names, err := dir.Readdirnames(-1) 78 | if err != nil { 79 | return 80 | } 81 | for _, nm := range names { 82 | conn.Write([]byte(nm + "\r\n")) 83 | } 84 | } 85 | 86 | func checkError(err error) { 87 | if err != nil { 88 | fmt.Println("Fatal error ", err.Error()) 89 | os.Exit(1) 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /ch5/textprotoclient.go: -------------------------------------------------------------------------------- 1 | /* textproto 2 | */ 3 | 4 | package main 5 | 6 | import ( 7 | "fmt" 8 | "net/textproto" 9 | "os" 10 | ) 11 | 12 | func checkerror(err error) { 13 | if err != nil { 14 | fmt.Println("error: ", err) 15 | os.Exit(1) 16 | } 17 | } 18 | 19 | func main() { 20 | conn, e := textproto.Dial("unix", "/tmp/fakewebserver") 21 | checkerror(e) 22 | defer conn.Close() 23 | fmt.Println("Sending request to retrieve /mypage") 24 | id, e := conn.Cmd("GET /mypage") 25 | checkerror(e) 26 | conn.StartResponse(id) 27 | defer conn.EndResponse(id) 28 | // fake sending back a 200 via nc or your own server 29 | code, stringResult, error := conn.ReadCodeLine(200) 30 | checkerror(e) 31 | fmt.Println(code, "\n", stringResult, "\n", error) 32 | 33 | } 34 | -------------------------------------------------------------------------------- /ch6/isotounicode.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "unicode/utf8" 6 | ) 7 | 8 | func str2int(str string) []int { 9 | r := []rune(str) 10 | b := make([]int, utf8.RuneCountInString(str)) 11 | for i, v := range r { 12 | b[i] = int(v) 13 | } 14 | return b 15 | } 16 | 17 | //unicode to 8859-4 18 | var unicodeToISOMap = map[int]uint8{ 19 | // example match ascii 0x0021: 0x21, // ! 20 | 0x012e: 0xc7, // Į 21 | 0x010c: 0xc8, // Č 22 | 0x0112: 0xaa, // Ē 23 | 0x0118: 0xca, // Ę 24 | // example match 0x00c9: 0xc9, // É 25 | // plus more 26 | } 27 | 28 | /* Turn a UTF-8 string into an ISO 8859 encoded byte array 29 | */ 30 | func unicodeStrToISO(str string) []byte { 31 | // get the unicode code points 32 | codePoints := str2int(str) //[]int(str) 33 | // create a byte array of the same length 34 | bytes := make([]byte, len(codePoints)) 35 | for n, v := range codePoints { 36 | // see if the point is in the exception map 37 | iso, ok := unicodeToISOMap[v] 38 | if !ok { 39 | // just use the value 40 | iso = uint8(v) 41 | } 42 | bytes[n] = iso 43 | } 44 | return bytes 45 | } 46 | 47 | // inverse of unicodeToISOMap 48 | var isoToUnicodeMap = map[uint8]int{ 49 | 0xc7: 0x012e, 50 | 0xc8: 0x010c, 51 | 0xaa: 0x0112, 52 | 0xca: 0x0118, 53 | // and more 54 | } 55 | 56 | func isoBytesToUnicode(bytes []byte) string { 57 | codePoints := make([]int, len(bytes)) 58 | for n, v := range bytes { 59 | unicode, ok := isoToUnicodeMap[v] 60 | if !ok { 61 | unicode = int(v) 62 | } 63 | codePoints[n] = unicode 64 | } 65 | return fmt.Sprintf("%q == %U", codePoints, codePoints) 66 | } 67 | 68 | func main() { 69 | x := "ĮĘa!" 70 | fmt.Printf("UTF-8: %s\n", x) 71 | 72 | fmt.Println("unicode to 8859-4") 73 | b := unicodeStrToISO(x) 74 | fmt.Printf("8859-4(hex): %x\n\n", b) 75 | 76 | fmt.Println("8859-4 to Unicode") 77 | fmt.Printf("Unicode: %v\n", isoBytesToUnicode(b)) 78 | } 79 | -------------------------------------------------------------------------------- /ch6/runeprint.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func main() { 6 | str := "百度一下, 你就知道" 7 | fmt.Println("String length: ", len([]rune(str))) 8 | fmt.Println("Byte length: ", len(str)) 9 | } 10 | -------------------------------------------------------------------------------- /ch6/utf16client.go: -------------------------------------------------------------------------------- 1 | /* UTF16 Client 2 | */ 3 | package main 4 | 5 | import ( 6 | "fmt" 7 | "log" 8 | "net" 9 | "os" 10 | "unicode/utf16" 11 | ) 12 | 13 | const BOM = '\ufffe' 14 | 15 | func main() { 16 | if len(os.Args) != 2 { 17 | log.Fatalln("Usage: ", os.Args[0], "host:port") 18 | } 19 | service := os.Args[1] 20 | conn, err := net.Dial("tcp", service) 21 | checkError(err) 22 | shorts := readShorts(conn) 23 | ints := utf16.Decode(shorts) 24 | str := string(ints) 25 | fmt.Println(str) 26 | } 27 | func readShorts(conn net.Conn) []uint16 { 28 | var buf [512]byte 29 | // read everything into the buffer 30 | n, err := conn.Read(buf[0:2]) // start with BOM 31 | for { 32 | m, err := conn.Read(buf[n:]) // read remaining byte pairs (originally unit16) 33 | if m == 0 || err != nil { 34 | break 35 | } 36 | n += m 37 | } 38 | checkError(err) 39 | var shorts []uint16 40 | shorts = make([]uint16, n/2) 41 | 42 | // We are checking for endianess 43 | // first - big endian 0xfffe 44 | // second - little endian 0xfeff 45 | // else - unknown 46 | // the inner loops are reading one byte per iteration 47 | // depending on endianess, places in the correct byte order 48 | // *warning* our server only supports big-endian 49 | if buf[0] == 0xff && buf[1] == 0xfe { 50 | for i := 2; i < n; i += 2 { 51 | shorts[i/2] = uint16(buf[i])<<8 + uint16(buf[i+1]) 52 | } 53 | } else if buf[0] == 0xfe && buf[1] == 0xff { 54 | for i := 2; i < n; i += 2 { 55 | shorts[i/2] = uint16(buf[i+1])<<8 + uint16(buf[i]) 56 | } 57 | } else { 58 | // unknown byte order 59 | fmt.Println("Unknown order") 60 | } 61 | return shorts 62 | } 63 | func checkError(err error) { 64 | if err != nil { 65 | log.Fatalln("Fatal error ", err.Error()) 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /ch6/utf16encodedecode.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "unicode/utf16" 5 | "fmt" 6 | ) 7 | 8 | func main() { 9 | str := "百度一下, 你就知道" 10 | fmt.Println("Before encoding:", str) 11 | 12 | runes := utf16.Encode([]rune(str)) 13 | ints := utf16.Decode(runes) 14 | 15 | str = string(ints) 16 | fmt.Println("After encoding:", str) 17 | } 18 | -------------------------------------------------------------------------------- /ch6/utf16server.go: -------------------------------------------------------------------------------- 1 | /* UTF16 Server 2 | */ 3 | package main 4 | 5 | import ( 6 | "log" 7 | "net" 8 | "unicode/utf16" 9 | ) 10 | 11 | // warning, our server currently only supports big endian 12 | const BOM = '\ufeff' 13 | 14 | func main() { 15 | service := "0.0.0.0:1210" 16 | tcpAddr, err := net.ResolveTCPAddr("tcp", service) 17 | checkError(err) 18 | listener, err := net.ListenTCP("tcp", tcpAddr) 19 | checkError(err) 20 | for { 21 | conn, err := listener.Accept() 22 | if err != nil { 23 | continue 24 | } 25 | // eg. Ŵ is 0x0174, à is 0x00c3 26 | str := "Ŵj'ai arrêté" 27 | shorts := utf16.Encode([]rune(str)) 28 | writeShorts(conn, shorts) 29 | conn.Close() 30 | } 31 | } 32 | func writeShorts(conn net.Conn, shorts []uint16) { 33 | var bytes [2]byte 34 | // send the BOM as first two bytes 35 | bytes[0] = BOM >> 8 // taking ff from BOM 36 | bytes[1] = BOM & 255 // taking fe from BOM 37 | _, err := conn.Write(bytes[0:]) // send BOM 38 | checkError(err) 39 | for _, v := range shorts { 40 | // breakup the unit16 into two bytes, then send 41 | bytes[0] = byte(v >> 8) 42 | bytes[1] = byte(v & 255) 43 | _, err = conn.Write(bytes[0:]) 44 | if err != nil { 45 | return 46 | } 47 | } 48 | } 49 | func checkError(err error) { 50 | if err != nil { 51 | log.Fatalln("Fatal error ", err.Error()) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /ch6/utfnorm.go: -------------------------------------------------------------------------------- 1 | /* UTFNorm 2 | */ 3 | package main 4 | 5 | import ( 6 | "fmt" 7 | "golang.org/x/text/unicode/norm" 8 | ) 9 | 10 | func main() { 11 | str1 := "\u04d6" 12 | str2 := "\u0415\u0306" 13 | norm_str2 := norm.NFC.String(str2) 14 | bytes1 := []byte(str1) 15 | bytes2 := []byte(str2) 16 | norm_bytes2 := []byte(norm_str2) 17 | fmt.Println("Single char ", str1, " bytes ", bytes1) 18 | fmt.Println("Composed char ", str2, " bytes ", bytes2) 19 | fmt.Println("Normalized char", norm_str2, " bytes ", 20 | norm_bytes2) 21 | } 22 | -------------------------------------------------------------------------------- /ch7/aes.go: -------------------------------------------------------------------------------- 1 | /* Aes 2 | */ 3 | package main 4 | 5 | import ( 6 | "bytes" 7 | "crypto/aes" 8 | "fmt" 9 | ) 10 | 11 | func main() { 12 | key := []byte("my key, len 16 b") 13 | cipher, err := aes.NewCipher(key) 14 | if err != nil { 15 | fmt.Println(err.Error()) 16 | } 17 | src := []byte("hello 16 b block") 18 | var enc [16]byte 19 | cipher.Encrypt(enc[0:], src) 20 | var decrypt [16]byte 21 | cipher.Decrypt(decrypt[0:], enc[0:]) 22 | result := bytes.NewBuffer(nil) 23 | result.Write(decrypt[0:]) 24 | fmt.Println(string(result.Bytes())) 25 | } 26 | -------------------------------------------------------------------------------- /ch7/genrsakeys.go: -------------------------------------------------------------------------------- 1 | /* GenRSAKeys 2 | */ 3 | package main 4 | 5 | import ( 6 | "crypto/rand" 7 | "crypto/rsa" 8 | "crypto/x509" 9 | "encoding/gob" 10 | "encoding/pem" 11 | "fmt" 12 | "log" 13 | "os" 14 | ) 15 | 16 | func main() { 17 | reader := rand.Reader 18 | bitSize := 2048 19 | key, err := rsa.GenerateKey(reader, bitSize) 20 | checkError(err) 21 | fmt.Printf("Private key primes:\n[0]:%s\n[1]:%s\n", key.Primes[0].String(), 22 | key.Primes[1].String()) 23 | fmt.Println("Private key exponent:\n", key.D.String()) 24 | publicKey := key.PublicKey 25 | fmt.Println("Public key modulus:\n", publicKey.N.String()) 26 | fmt.Println("Public key exponent:\n", publicKey.E) 27 | saveGobKey("private.key", key) 28 | saveGobKey("public.key", publicKey) 29 | savePEMKey("private.pem", key) 30 | } 31 | func saveGobKey(fileName string, key interface{}) { 32 | outFile, err := os.Create(fileName) 33 | checkError(err) 34 | encoder := gob.NewEncoder(outFile) 35 | err = encoder.Encode(key) 36 | checkError(err) 37 | outFile.Close() 38 | } 39 | func savePEMKey(fileName string, key *rsa.PrivateKey) { 40 | outFile, err := os.Create(fileName) 41 | checkError(err) 42 | var privateKey = &pem.Block{Type: "RSA PRIVATE KEY", 43 | Bytes: x509.MarshalPKCS1PrivateKey(key)} 44 | pem.Encode(outFile, privateKey) 45 | outFile.Close() 46 | } 47 | func checkError(err error) { 48 | if err != nil { 49 | log.Fatalln("Fatal error ", err.Error()) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /ch7/genx509cert.go: -------------------------------------------------------------------------------- 1 | /* GenX509Cert 2 | */ 3 | package main 4 | 5 | import ( 6 | "crypto/rand" 7 | "crypto/rsa" 8 | "crypto/x509" 9 | "crypto/x509/pkix" 10 | "encoding/gob" 11 | "encoding/pem" 12 | "log" 13 | "math/big" 14 | "os" 15 | "time" 16 | ) 17 | 18 | func main() { 19 | random := rand.Reader 20 | var key rsa.PrivateKey 21 | loadKey("private.key", &key) 22 | now := time.Now() 23 | then := now.Add(60 * 60 * 24 * 365 * 1000 * 1000 * 1000) 24 | // one year 25 | template := x509.Certificate{ 26 | SerialNumber: big.NewInt(1), 27 | Subject: pkix.Name{ 28 | CommonName: "jan.newmarch.name", 29 | Organization: []string{"Jan Newmarch"}, 30 | }, 31 | NotBefore: now, 32 | NotAfter: then, 33 | SubjectKeyId: []byte{1, 2, 3, 4}, 34 | KeyUsage: x509.KeyUsageCertSign | 35 | x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, 36 | BasicConstraintsValid: true, 37 | IsCA: true, 38 | DNSNames: []string{"jan.newmarch.name", 39 | "localhost"}, 40 | } 41 | derBytes, err := x509.CreateCertificate(random, &template, 42 | &template, &key.PublicKey, &key) 43 | checkError(err) 44 | certCerFile, err := os.Create("jan.newmarch.name.cer") 45 | checkError(err) 46 | certCerFile.Write(derBytes) 47 | certCerFile.Close() 48 | certPEMFile, err := os.Create("jan.newmarch.name.pem") 49 | checkError(err) 50 | pem.Encode(certPEMFile, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}) 51 | certPEMFile.Close() 52 | keyPEMFile, err := os.Create("private.pem") 53 | checkError(err) 54 | pem.Encode(keyPEMFile, &pem.Block{Type: "RSA PRIVATE KEY", 55 | Bytes: x509.MarshalPKCS1PrivateKey(&key)}) 56 | keyPEMFile.Close() 57 | } 58 | func loadKey(fileName string, key interface{}) { 59 | inFile, err := os.Open(fileName) 60 | checkError(err) 61 | decoder := gob.NewDecoder(inFile) 62 | err = decoder.Decode(key) 63 | checkError(err) 64 | inFile.Close() 65 | } 66 | func checkError(err error) { 67 | if err != nil { 68 | log.Fatalln("Fatal error ", err.Error()) 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /ch7/hmacmd5.go: -------------------------------------------------------------------------------- 1 | /* MD5Hash 2 | */ 3 | package main 4 | 5 | import ( 6 | "crypto/md5" 7 | "crypto/hmac" 8 | "fmt" 9 | ) 10 | 11 | func main() { 12 | hash := hmac.New(md5.New, []byte("secret")) 13 | bytes := []byte("hello\n") 14 | hash.Write(bytes) 15 | hashValue := hash.Sum(nil) 16 | hashSize := hash.Size() 17 | for n := 0; n < hashSize; n += 4 { 18 | var val uint32 19 | val = uint32(hashValue[n])<<24 + 20 | uint32(hashValue[n+1])<<16 + 21 | uint32(hashValue[n+2])<<8 + 22 | uint32(hashValue[n+3]) 23 | fmt.Printf("%x ", val) 24 | } 25 | fmt.Println() 26 | } 27 | -------------------------------------------------------------------------------- /ch7/largekey.go: -------------------------------------------------------------------------------- 1 | /* MD5Hash 2 | */ 3 | package main 4 | 5 | import ( 6 | "crypto/sha256" 7 | "crypto/hmac" 8 | "fmt" 9 | ) 10 | 11 | func main() { 12 | hash := hmac.New(sha256.New, []byte("2Secret!23")) 13 | bytes := []byte("hello\n") 14 | hash.Write(bytes) 15 | hashValue := hash.Sum(nil) 16 | hashSize := hash.Size() 17 | for n := 0; n < hashSize; n += 4 { 18 | var val uint32 19 | val = uint32(hashValue[n])<<24 + 20 | uint32(hashValue[n+1])<<16 + 21 | uint32(hashValue[n+2])<<8 + 22 | uint32(hashValue[n+3]) 23 | fmt.Printf("%x ", val) 24 | } 25 | fmt.Println() 26 | } 27 | -------------------------------------------------------------------------------- /ch7/loadrsakeys.go: -------------------------------------------------------------------------------- 1 | /* LoadRSAKeys 2 | */ 3 | package main 4 | 5 | import ( 6 | "crypto/rsa" 7 | "encoding/gob" 8 | "fmt" 9 | "log" 10 | "os" 11 | ) 12 | 13 | func main() { 14 | var key rsa.PrivateKey 15 | loadKey("private.key", &key) 16 | fmt.Printf("Private key primes:\n[0]:%s\n[1]:%s\n", key.Primes[0].String(), 17 | key.Primes[1].String()) 18 | fmt.Println("Private key exponent:\n", key.D.String()) 19 | var publicKey rsa.PublicKey 20 | loadKey("public.key", &publicKey) 21 | fmt.Println("Public key modulus:\n", publicKey.N.String()) 22 | fmt.Println("Public key exponent:\n", publicKey.E) 23 | } 24 | func loadKey(fileName string, key interface{}) { 25 | inFile, err := os.Open(fileName) 26 | checkError(err) 27 | decoder := gob.NewDecoder(inFile) 28 | err = decoder.Decode(key) 29 | checkError(err) 30 | inFile.Close() 31 | } 32 | func checkError(err error) { 33 | if err != nil { 34 | log.Fatalln("Fatal error ", err.Error()) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /ch7/loadx509cert.go: -------------------------------------------------------------------------------- 1 | /* LoadX509Cert 2 | */ 3 | package main 4 | 5 | import ( 6 | "crypto/rsa" 7 | "crypto/x509" 8 | "encoding/gob" 9 | "fmt" 10 | "log" 11 | "os" 12 | ) 13 | 14 | func main() { 15 | // load certificate so we can access embedded public key 16 | certCerFile, err := os.Open("jan.newmarch.name.cer") 17 | checkError(err) 18 | derBytes := make([]byte, 1000) // bigger than the file 19 | count, err := certCerFile.Read(derBytes) 20 | checkError(err) 21 | certCerFile.Close() 22 | // trim the bytes to actual length in call 23 | cert, err := x509.ParseCertificate(derBytes[0:count]) 24 | checkError(err) 25 | fmt.Printf("Name %s\n", cert.Subject.CommonName) 26 | fmt.Printf("Not before %s\n", cert.NotBefore.String()) 27 | fmt.Printf("Not after %s\n", cert.NotAfter.String()) 28 | 29 | // load non-emdedded public key 30 | // should be the same as above embedded key 31 | pub, err := os.Open("public.key") 32 | checkError(err) 33 | dec := gob.NewDecoder(pub) 34 | publicKey := new(rsa.PrivateKey) 35 | err = dec.Decode(publicKey) 36 | checkError(err) 37 | pub.Close() 38 | 39 | // genx509cert.go created a public key and certificate 40 | // certificates also embed the public key 41 | // we are comparing the public key and the embedded public key fields 42 | // see go doc crypto/rsa.PublicKey for more 43 | if cert.PublicKey.(*rsa.PublicKey).N.Cmp(publicKey.N) == 0 { 44 | if publicKey.E == cert.PublicKey.(*rsa.PublicKey).E { 45 | fmt.Println("Same public key") 46 | return 47 | } 48 | } 49 | fmt.Println("Different public key") 50 | } 51 | func checkError(err error) { 52 | if err != nil { 53 | log.Fatalln("Fatal error ", err.Error()) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /ch7/md5hash.go: -------------------------------------------------------------------------------- 1 | /* MD5Hash 2 | */ 3 | package main 4 | 5 | import ( 6 | "crypto/md5" 7 | "fmt" 8 | ) 9 | 10 | func main() { 11 | hash := md5.New() 12 | bytes := []byte("hello\n") 13 | hash.Write(bytes) // add data to running hash 14 | hashValue := hash.Sum(nil) // retrieve the hashed data 15 | hashSize := hash.Size() // how many bytes Sum returns (could just len(hashValue) 16 | 17 | // for every 4 bytes of hashValue 18 | // we stuff into an byte of val by shifting 19 | // val[first_byte] = hashValue[n] after shifting 24 20 | // val[second_byte = hashValue[n+1] after shifting 16 21 | // val[third_byte] = hashValue[n+2] after shifting 8 22 | // val[fourth_byte] = hashValue[n+3] 23 | // in the end, we have unint32 value that we print 24 | for n := 0; n < hashSize; n += 4 { 25 | var val uint32 26 | val = uint32(hashValue[n])<<24 + 27 | uint32(hashValue[n+1])<<16 + 28 | uint32(hashValue[n+2])<<8 + 29 | uint32(hashValue[n+3]) 30 | fmt.Printf("%x ", val) 31 | } 32 | fmt.Println() 33 | } 34 | -------------------------------------------------------------------------------- /ch7/sha256.go: -------------------------------------------------------------------------------- 1 | /* MD5Hash 2 | */ 3 | package main 4 | 5 | import ( 6 | "crypto/hmac" 7 | "crypto/sha256" 8 | "fmt" 9 | ) 10 | 11 | func main() { 12 | hash := hmac.New(sha256.New, []byte("secret")) 13 | bytes := []byte("hello\n") 14 | hash.Write(bytes) 15 | hashValue := hash.Sum(nil) 16 | hashSize := hash.Size() 17 | for n := 0; n < hashSize; n += 4 { 18 | var val uint32 19 | val = uint32(hashValue[n])<<24 + 20 | uint32(hashValue[n+1])<<16 + 21 | uint32(hashValue[n+2])<<8 + 22 | uint32(hashValue[n+3]) 23 | fmt.Printf("%x ", val) 24 | } 25 | fmt.Println() 26 | } 27 | -------------------------------------------------------------------------------- /ch7/tlsechoclient.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "crypto/tls" 5 | "crypto/x509" 6 | "fmt" 7 | "os" 8 | ) 9 | 10 | func main() { 11 | rootPEM, err := os.ReadFile("jan.newmarch.name.pem") 12 | // First, create the set of root certificates. For this example we only 13 | // have one. It's also possible to omit this in order to use the 14 | // default root set of the current operating system. 15 | roots := x509.NewCertPool() 16 | if ok := roots.AppendCertsFromPEM(rootPEM); !ok { 17 | panic("failed to parse root certificate") 18 | } 19 | 20 | conn, err := tls.Dial("tcp", "localhost:1200", &tls.Config{ 21 | RootCAs: roots, 22 | // InsecureSkipVerify: false, 23 | }) 24 | if err != nil { 25 | panic("failed to connect: " + err.Error()) 26 | } 27 | 28 | // Now write and read lots 29 | for n := 0; n < 10; n++ { 30 | fmt.Println("Writing...") 31 | conn.Write([]byte("Hello " + string(n+48))) 32 | var buf [512]byte 33 | n, _ := conn.Read(buf[0:]) 34 | fmt.Println(string(buf[0:n])) 35 | } 36 | 37 | conn.Close() 38 | } 39 | -------------------------------------------------------------------------------- /ch7/tlsechoserver.go: -------------------------------------------------------------------------------- 1 | /* TLSEchoServer 2 | */ 3 | package main 4 | 5 | import ( 6 | "crypto/tls" 7 | "fmt" 8 | "log" 9 | "net" 10 | ) 11 | 12 | func main() { 13 | cert, err := tls.LoadX509KeyPair("jan.newmarch.name.pem", 14 | "private.pem") 15 | checkError(err) 16 | config := tls.Config{Certificates: []tls.Certificate{cert}} 17 | service := "0.0.0.0:1200" 18 | listener, err := tls.Listen("tcp", service, &config) 19 | checkError(err) 20 | fmt.Println("Listening") 21 | for { 22 | conn, err := listener.Accept() 23 | if err != nil { 24 | fmt.Println(err.Error()) 25 | continue 26 | } 27 | fmt.Println("Accepted") 28 | go handleClient(conn) 29 | } 30 | } 31 | func handleClient(conn net.Conn) { 32 | defer conn.Close() 33 | var buf [512]byte 34 | for { 35 | fmt.Println("Trying to read") 36 | n, err := conn.Read(buf[0:]) 37 | if err != nil { 38 | fmt.Println(err) 39 | return 40 | } 41 | fmt.Println(string(buf[0:])) 42 | _, err = conn.Write(buf[0:n]) 43 | if err != nil { 44 | return 45 | } 46 | } 47 | } 48 | func checkError(err error) { 49 | if err != nil { 50 | log.Fatalln("Fatal error ", err.Error()) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /ch7/tlsgethead.go: -------------------------------------------------------------------------------- 1 | /* TLSGetHead 2 | */ 3 | package main 4 | 5 | import ( 6 | "crypto/tls" 7 | "fmt" 8 | "io/ioutil" 9 | "log" 10 | "os" 11 | ) 12 | 13 | func main() { 14 | if len(os.Args) != 2 { 15 | log.Fatalln("Usage: ", os.Args[0], "host:port") 16 | } 17 | service := os.Args[1] 18 | // Dial over secure channel 19 | conn, err := tls.Dial("tcp", service, nil) 20 | checkError(err) 21 | _, err = conn.Write([]byte("HEAD / HTTP/1.0\r\n\r\n")) 22 | checkError(err) 23 | result, err := ioutil.ReadAll(conn) 24 | checkError(err) 25 | fmt.Println(string(result)) 26 | conn.Close() 27 | os.Exit(0) 28 | } 29 | func checkError(err error) { 30 | if err != nil { 31 | log.Fatalln("Fatal error ", err.Error()) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /ch8/clientget.go: -------------------------------------------------------------------------------- 1 | /* ClientGet 2 | */ 3 | package main 4 | 5 | import ( 6 | "io" 7 | "fmt" 8 | "log" 9 | "net/http" 10 | "net/http/httputil" 11 | "net/url" 12 | "os" 13 | "strings" 14 | ) 15 | 16 | func main() { 17 | if len(os.Args) != 2 { 18 | log.Fatalln("Usage: ", os.Args[0], "http://host:port/page") 19 | } 20 | url, err := url.Parse(os.Args[1]) 21 | checkError(err) 22 | client := &http.Client{} 23 | request, err := http.NewRequest("HEAD", url.String(), nil) 24 | // only accept UTF-8 25 | request.Header.Add("Accept-Charset", "utf-8;q=1,ISO-8859-1;q=0") 26 | checkError(err) 27 | response, err := client.Do(request) 28 | checkError(err) 29 | if response.StatusCode != http.StatusOK { 30 | log.Fatalln(response.Status) 31 | } 32 | fmt.Println("The response header is") 33 | b, _ := httputil.DumpResponse(response, false) 34 | fmt.Print(string(b)) 35 | chSet := getCharset(response) 36 | if chSet != "utf-8" { 37 | log.Fatalln("Cannot handle", chSet) 38 | } 39 | var buf [512]byte 40 | reader := response.Body 41 | fmt.Println("got body") 42 | for { 43 | n, err := reader.Read(buf[0:]) 44 | if err != nil { 45 | if err == io.EOF { 46 | fmt.Print(string(buf[0:n])) 47 | break 48 | } 49 | checkError(err) 50 | } 51 | fmt.Print(string(buf[0:n])) 52 | } 53 | } 54 | func getCharset(response *http.Response) string { 55 | contentType := response.Header.Get("Content-Type") 56 | if contentType == "" { 57 | // guess 58 | return "utf-8" 59 | } 60 | idx := strings.Index(contentType, "charset=") 61 | if idx == -1 { 62 | // guess 63 | return "utf-8" 64 | } 65 | // we found charset now remove it 66 | chSet := strings.Trim(contentType[idx+8:], " ") 67 | return strings.ToLower(chSet) 68 | } 69 | func checkError(err error) { 70 | if err != nil { 71 | log.Fatalln("Fatal error ", err.Error()) 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /ch8/fileserver.go: -------------------------------------------------------------------------------- 1 | /* File Server 2 | */ 3 | package main 4 | 5 | import ( 6 | "log" 7 | "net/http" 8 | ) 9 | 10 | func main() { 11 | // deliver files from the directory /tmp/www 12 | fileServer := http.FileServer(http.Dir("/tmp/www")) 13 | 14 | // register the handler and deliver requests to it 15 | err := http.ListenAndServe(":8000", fileServer) 16 | if err != nil { 17 | log.Fatalln(err) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /ch8/get.go: -------------------------------------------------------------------------------- 1 | /* Get 2 | */ 3 | package main 4 | 5 | import ( 6 | "fmt" 7 | "io" 8 | "log" 9 | "net/http" 10 | "net/http/httputil" 11 | "os" 12 | "strings" 13 | ) 14 | 15 | func main() { 16 | if len(os.Args) != 2 { 17 | log.Fatalln("Usage: ", os.Args[0], "host:port") 18 | } 19 | url := os.Args[1] 20 | response, err := http.Get(url) 21 | checkError(err) 22 | if response.StatusCode != http.StatusOK { 23 | log.Fatalln(response.StatusCode) 24 | } 25 | fmt.Println("The response header is") 26 | b, _ := httputil.DumpResponse(response, false) 27 | fmt.Print(string(b)) 28 | contentTypes := response.Header["Content-Type"] 29 | if !acceptableCharset(contentTypes) { 30 | log.Fatalln("Cannot handle", contentTypes) 31 | } 32 | fmt.Println("The response body is") 33 | var buf [512]byte 34 | reader := response.Body 35 | for { 36 | n, err := reader.Read(buf[0:]) 37 | if err != nil { 38 | if err == io.EOF { 39 | fmt.Print(string(buf[0:n])) 40 | reader.Close() 41 | break 42 | } 43 | checkError(err) 44 | } 45 | fmt.Print(string(buf[0:n])) 46 | } 47 | } 48 | func acceptableCharset(contentTypes []string) bool { 49 | // each type is like [text/html; charset=utf-8] 50 | // we want the UTF-8 only 51 | for _, cType := range contentTypes { 52 | if strings.Index(cType, "utf-8") != -1 { 53 | return true 54 | } 55 | } 56 | return false 57 | } 58 | 59 | func checkError(err error) { 60 | if err != nil { 61 | log.Fatalln(err) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /ch8/head.go: -------------------------------------------------------------------------------- 1 | /* Head 2 | */ 3 | package main 4 | 5 | import ( 6 | "fmt" 7 | "log" 8 | "net/http" 9 | "os" 10 | ) 11 | 12 | func main() { 13 | if len(os.Args) != 2 { 14 | log.Fatalln("Usage: ", os.Args[0], "host:port") 15 | } 16 | url := os.Args[1] 17 | response, err := http.Head(url) 18 | if err != nil { 19 | log.Fatalln(err) 20 | } 21 | fmt.Println(response.Status) 22 | for k, v := range response.Header { 23 | fmt.Println(k+":", v) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /ch8/httpsfileserver.go: -------------------------------------------------------------------------------- 1 | /* HTTPSFileServer 2 | */ 3 | package main 4 | 5 | import ( 6 | "net/http" 7 | "log" 8 | ) 9 | 10 | func main() { 11 | // deliver files from the directory /tmp/www 12 | fileServer := http.FileServer(http.Dir("/tmp/www")) 13 | // register the handler and deliver requests to it 14 | err := http.ListenAndServeTLS(":8000", "jan.newmarch.name.pem", 15 | "private.pem", fileServer) 16 | if err != nil { 17 | log.Fatalln(err) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /ch8/printenv.go: -------------------------------------------------------------------------------- 1 | /* Print Env 2 | */ 3 | package main 4 | 5 | import ( 6 | "log" 7 | "net/http" 8 | "os" 9 | ) 10 | 11 | func main() { 12 | // file handler for most files 13 | fileServer := http.FileServer(http.Dir("/tmp/www")) 14 | http.Handle("/", fileServer) 15 | // function handler for /cgi-bin/printenv 16 | http.HandleFunc("/cgi-bin/printenv", printEnv) 17 | 18 | // deliver requests to the handlers 19 | err := http.ListenAndServe(":8000", nil) 20 | if err != nil { 21 | log.Fatalln(err) 22 | } 23 | } 24 | func printEnv(writer http.ResponseWriter, req *http.Request) { 25 | env := os.Environ() 26 | writer.Write([]byte("

Environment

"))
27 | 	for _, v := range env {
28 | 		writer.Write([]byte(v + "\n"))
29 | 	}
30 | 	writer.Write([]byte("
")) 31 | } 32 | -------------------------------------------------------------------------------- /ch8/proxyauthget.go: -------------------------------------------------------------------------------- 1 | /* ProxyAuthGet 2 | */ 3 | package main 4 | 5 | import ( 6 | "log" 7 | "encoding/base64" 8 | "fmt" 9 | "io" 10 | "net/http" 11 | "net/http/httputil" 12 | "net/url" 13 | "os" 14 | ) 15 | 16 | const auth = "jannewmarch:mypassword" 17 | 18 | func main() { 19 | if len(os.Args) != 3 { 20 | log.Fatalln("Usage: ", os.Args[0], "http://proxy-host:port http://host:port/page") 21 | } 22 | proxy := os.Args[1] 23 | proxyURL, err := url.Parse(proxy) 24 | checkError(err) 25 | rawURL := os.Args[2] 26 | url, err := url.Parse(rawURL) 27 | checkError(err) 28 | 29 | // encode the auth 30 | basic := "Basic " + 31 | base64.StdEncoding.EncodeToString([]byte(auth)) 32 | 33 | transport := &http.Transport{Proxy: http.ProxyURL(proxyURL)} 34 | client := &http.Client{Transport: transport} 35 | 36 | request, err := http.NewRequest("GET", url.String(), nil) 37 | 38 | request.Header.Add("Proxy-Authorization", basic) 39 | dump, _ := httputil.DumpRequest(request, false) 40 | fmt.Println(string(dump)) 41 | 42 | // send the request 43 | response, err := client.Do(request) 44 | 45 | checkError(err) 46 | fmt.Println("Read ok") 47 | if response.StatusCode != http.StatusOK { 48 | log.Fatalln(response.Status) 49 | } 50 | fmt.Println("Response ok") 51 | 52 | var buf [512]byte 53 | reader := response.Body 54 | for { 55 | n, err := reader.Read(buf[0:]) 56 | if err != nil { 57 | if err == io.EOF { 58 | fmt.Print(string(buf[0:n])) 59 | return 60 | } 61 | checkError(err) 62 | } 63 | fmt.Print(string(buf[0:n])) 64 | } 65 | os.Exit(0) 66 | } 67 | func checkError(err error) { 68 | if err != nil { 69 | log.Fatalln("Fatal error ", err.Error()) 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /ch8/proxyget.go: -------------------------------------------------------------------------------- 1 | /* ProxyGet 2 | */ 3 | package main 4 | 5 | import ( 6 | "fmt" 7 | "io" 8 | "log" 9 | "net/http" 10 | "net/url" 11 | "os" 12 | ) 13 | 14 | func main() { 15 | if len(os.Args) != 2 { 16 | log.Fatalln("Usage: ", os.Args[0], "http://host:port/page") 17 | } 18 | rawURL := os.Args[1] 19 | url, err := url.Parse(rawURL) 20 | checkError(err) 21 | 22 | response, err := http.Get(url.String()) 23 | 24 | checkError(err) 25 | fmt.Println("Read ok") 26 | 27 | if response.StatusCode != http.StatusOK { 28 | log.Fatalln(response.StatusCode) 29 | } 30 | fmt.Println("Response ok") 31 | 32 | var buf [512]byte 33 | reader := response.Body 34 | for { 35 | n, err := reader.Read(buf[0:]) 36 | if err != nil { 37 | if err == io.EOF { 38 | fmt.Print(string(buf[0:n])) 39 | reader.Close() 40 | break 41 | } 42 | checkError(err) 43 | } 44 | fmt.Print(string(buf[0:n])) 45 | } 46 | } 47 | func checkError(err error) { 48 | if err != nil { 49 | log.Fatalln("Fatal error ", err.Error()) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /ch8/punycode.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "golang.org/x/net/idna" 6 | "net/url" 7 | ) 8 | 9 | func main() { 10 | s := "https://日本語.jp:8443" 11 | r1, _ := idna.ToASCII(s) 12 | r2, _ := idna.ToUnicode(r1) 13 | 14 | fmt.Println(r1) 15 | fmt.Println(r2) 16 | fmt.Println(url.QueryEscape(s)) 17 | } 18 | -------------------------------------------------------------------------------- /ch8/serverhandler.go: -------------------------------------------------------------------------------- 1 | /* ServerHandler 2 | */ 3 | 4 | package main 5 | 6 | import ( 7 | "net/http" 8 | ) 9 | 10 | func main() { 11 | myHandler := http.HandlerFunc(func(rw http.ResponseWriter, 12 | request *http.Request) { 13 | 14 | // Just return no content - arbitrary headers can be set, arbitrary body 15 | rw.WriteHeader(http.StatusNoContent) 16 | }) 17 | 18 | http.ListenAndServe(":8080", myHandler) 19 | } 20 | -------------------------------------------------------------------------------- /ch8/tlsunsafeclientget.go: -------------------------------------------------------------------------------- 1 | /* TLSUnsafeClientGet 2 | */ 3 | package main 4 | 5 | import ( 6 | "crypto/tls" 7 | "fmt" 8 | "log" 9 | "net/http" 10 | "net/url" 11 | "os" 12 | "strings" 13 | ) 14 | 15 | func main() { 16 | if len(os.Args) != 2 { 17 | log.Fatalln("Usage: ", os.Args[0], "https://host:port/page") 18 | } 19 | url, err := url.Parse(os.Args[1]) 20 | checkError(err) 21 | if url.Scheme != "https" { 22 | log.Fatalln("Not https scheme ", url.Scheme) 23 | } 24 | 25 | transport := &http.Transport{} 26 | transport.TLSClientConfig = &tls.Config{InsecureSkipVerify: false} 27 | client := &http.Client{Transport: transport} 28 | 29 | request, err := http.NewRequest("GET", url.String(), nil) 30 | // only accept UTF-8 31 | checkError(err) 32 | 33 | response, err := client.Do(request) 34 | checkError(err) 35 | 36 | if response.StatusCode != http.StatusOK { 37 | log.Fatalln(response.Status) 38 | } 39 | fmt.Println("get a response") 40 | 41 | chSet := getCharset(response) 42 | fmt.Printf("got charset %s\n", chSet) 43 | if chSet != "UTF-8" { 44 | log.Fatalln("Cannot handle", chSet) 45 | } 46 | 47 | var buf [512]byte 48 | reader := response.Body 49 | fmt.Println("got body") 50 | for { 51 | n, err := reader.Read(buf[0:]) 52 | checkError(err) 53 | fmt.Print(string(buf[0:n])) 54 | } 55 | } 56 | func getCharset(response *http.Response) string { 57 | contentType := response.Header.Get("Content-Type") 58 | if contentType == "" { 59 | // guess 60 | return "UTF-8" 61 | } 62 | idx := strings.Index(contentType, "charset:") 63 | if idx == -1 { 64 | // guess 65 | return "UTF-8" 66 | } 67 | return strings.Trim(contentType[idx:], " ") 68 | } 69 | func checkError(err error) { 70 | if err != nil { 71 | log.Fatalln("Fatal error ", err.Error()) 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /ch9/printemails.go: -------------------------------------------------------------------------------- 1 | /* PrintEmails 2 | */ 3 | package main 4 | 5 | import ( 6 | "log" 7 | "os" 8 | "strings" 9 | "text/template" 10 | ) 11 | 12 | type Person struct { 13 | Name string 14 | Emails []string 15 | } 16 | 17 | const templ = `The name is {{.Name}}. 18 | {{range .Emails}} 19 | An email is "{{. | emailExpand}}" 20 | {{end}}` 21 | 22 | func main() { 23 | person := Person{ 24 | Name: "jan", 25 | Emails: []string{"jan@newmarch.name", 26 | "jan.newmarch@gmail.com"}, 27 | } 28 | t, err := template.New("Person template").Funcs( 29 | template.FuncMap{ 30 | "emailExpand": func(emailAddress string) string { 31 | return strings.Replace(emailAddress, "@", " at ", -1) 32 | }, 33 | }, 34 | ).Parse(templ) 35 | 36 | err = t.Execute(os.Stdout, person) 37 | checkError(err) 38 | } 39 | 40 | func checkError(err error) { 41 | if err != nil { 42 | log.Fatalln("Fatal error ", err.Error()) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /ch9/printjsonemails.go: -------------------------------------------------------------------------------- 1 | /** 2 | * PrintJSONEmails 3 | */ 4 | package main 5 | 6 | import ( 7 | "bytes" 8 | "encoding/json" 9 | "fmt" 10 | "log" 11 | "os" 12 | "text/template" 13 | ) 14 | 15 | type Person struct { 16 | Name string 17 | Emails []string 18 | } 19 | 20 | const templ = `{"Name": "{{- .Name -}}", "Emails": [ 21 | {{- range $index, $elmt := .Emails -}} 22 | {{- if $index -}} 23 | , "{{- $elmt -}}" 24 | {{- else -}} 25 | "{{- $elmt -}}" 26 | {{- end -}} 27 | {{- end -}} 28 | ] }` 29 | 30 | func main() { 31 | person := Person{ 32 | Name: "jan", 33 | Emails: []string{"jan@newmarch.name", 34 | "jan.newmarch@gmail.com"}, 35 | } 36 | t := template.New("Person template") 37 | t, err := t.Parse(templ) 38 | checkError(err) 39 | err = t.Execute(os.Stdout, person) 40 | checkError(err) 41 | 42 | // check via validity json package 43 | var b bytes.Buffer 44 | err = t.Execute(&b, person) 45 | checkError(err) 46 | if json.Valid(b.Bytes()) { 47 | fmt.Println("\nvalid json") 48 | } 49 | } 50 | func checkError(err error) { 51 | if err != nil { 52 | log.Fatalln("Fatal error ", err.Error()) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /ch9/printnameemails.go: -------------------------------------------------------------------------------- 1 | /** 2 | * PrintNameEmails 3 | */ 4 | package main 5 | 6 | import ( 7 | "log" 8 | "os" 9 | "text/template" 10 | ) 11 | 12 | type Person struct { 13 | Name string 14 | Emails []string 15 | } 16 | 17 | const templ = `{{$name := .Name}} 18 | {{ $numEmails := .Emails | len -}} 19 | {{range $idx, $email := .Emails -}} 20 | Name is {{$name}}, email {{$email}} is {{ $idx | increment }} of {{ $numEmails }} 21 | {{end}} 22 | ` 23 | 24 | func main() { 25 | person := Person{ 26 | Name: "jan", 27 | Emails: []string{"jan@newmarch.name", 28 | "jan.newmarch@gmail.com"}, 29 | } 30 | t, err := template.New("Person template").Funcs( 31 | template.FuncMap{ 32 | "increment": func(val int) int { 33 | return val + 1 34 | }, 35 | }, 36 | ).Parse(templ) 37 | checkError(err) 38 | err = t.Execute(os.Stdout, person) 39 | checkError(err) 40 | } 41 | func checkError(err error) { 42 | if err != nil { 43 | log.Fatalln("Fatal error ", err.Error()) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /ch9/printperson.go: -------------------------------------------------------------------------------- 1 | /* PrintPerson 2 | */ 3 | package main 4 | 5 | import ( 6 | "log" 7 | "os" 8 | "text/template" 9 | ) 10 | 11 | type Person struct { 12 | Name string 13 | Age int 14 | Emails []string 15 | Jobs []Job 16 | } 17 | type Job struct { 18 | Employer string 19 | Role string 20 | } 21 | 22 | const templ = `The name is {{.Name}}. 23 | The age is {{.Age}}. 24 | {{range .Emails}} 25 | An email is {{.}} 26 | {{end}} 27 | {{with .Jobs}} 28 | {{range .}} 29 | An employer is {{.Employer}} 30 | and the role is {{.Role}} 31 | {{end}} 32 | {{end}} 33 | ` 34 | 35 | func main() { 36 | job1 := Job{Employer: "Box Hill Institute", Role: "Director, Commerce and ICT"} 37 | job2 := Job{Employer: "Canberra University", Role: "Adjunct Professor"} 38 | person := Person{ 39 | Name: "jan", 40 | Age: 66, 41 | Emails: []string{"jan@newmarch.name", 42 | "jan.newmarch@gmail.com"}, 43 | Jobs: []Job{job1, job2}, 44 | } 45 | t := template.New("Person template") 46 | t, err := t.Parse(templ) 47 | checkError(err) 48 | err = t.Execute(os.Stdout, person) 49 | checkError(err) 50 | } 51 | func checkError(err error) { 52 | if err != nil { 53 | log.Fatalln("Fatal error ", err.Error()) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /ch9/sequence.go: -------------------------------------------------------------------------------- 1 | /* Sequence.go 2 | * Copyright Roger Peppe 3 | */ 4 | package main 5 | 6 | import ( 7 | "errors" 8 | "fmt" 9 | "os" 10 | "text/template" 11 | ) 12 | 13 | var tmpl = `{{$comma := sequence "" ", "}} 14 | {{range $}}{{$comma.Next}}{{.}}{{end}} 15 | {{$comma := sequence "" ", "}} 16 | {{$colour := cycle "black" "white" "red"}} 17 | {{range $}}{{$comma.Next}}{{.}} in {{$colour.Next}}{{end}} 18 | ` 19 | var fmap = template.FuncMap{ 20 | "sequence": sequenceFunc, 21 | "cycle": cycleFunc, 22 | } 23 | 24 | func main() { 25 | t, err := template.New("").Funcs(fmap).Parse(tmpl) 26 | if err != nil { 27 | fmt.Printf("parse error: %vn", err) 28 | return 29 | } 30 | err = t.Execute(os.Stdout, []string{"a", "b", "c", 31 | "d", "e", "f"}) 32 | if err != nil { 33 | fmt.Printf("exec error: %vn", err) 34 | } 35 | } 36 | 37 | type generator struct { 38 | ss []string 39 | i int 40 | f func(s []string, i int) string 41 | } 42 | 43 | func (seq *generator) Next() string { 44 | s := seq.f(seq.ss, seq.i) 45 | seq.i++ 46 | return s 47 | } 48 | func sequenceGen(ss []string, i int) string { 49 | if i >= len(ss) { 50 | return ss[len(ss)-1] 51 | } 52 | return ss[i] 53 | } 54 | func cycleGen(ss []string, i int) string { 55 | return ss[i%len(ss)] 56 | } 57 | func sequenceFunc(ss ...string) (*generator, error) { 58 | if len(ss) == 0 { 59 | return nil, errors.New("sequence must have at least one element") 60 | } 61 | return &generator{ss, 0, sequenceGen}, nil 62 | } 63 | func cycleFunc(ss ...string) (*generator, error) { 64 | if len(ss) == 0 { 65 | return nil, errors.New("cycle must have at least one element") 66 | } 67 | return &generator{ss, 0, cycleGen}, nil 68 | } 69 | -------------------------------------------------------------------------------- /errata.md: -------------------------------------------------------------------------------- 1 | # Errata for *Network Programming with Go Language* 2 | 3 | On **page xx** [Summary of error]: 4 | 5 | Details of error here. Highlight key pieces in **bold**. 6 | 7 | *** 8 | 9 | On **page xx** [Summary of error]: 10 | 11 | Details of error here. Highlight key pieces in **bold**. 12 | 13 | *** --------------------------------------------------------------------------------