├── README.md ├── base64.go ├── byte.go ├── channel.go ├── channel2.go ├── chat.go ├── chooseone.go ├── cmdproxy.go ├── cobra-flags.go ├── concat.go ├── conversion.go ├── csv.go ├── data-access ├── README.md ├── db.go ├── main.go ├── migrations │ ├── 0001_tables.down.sql │ ├── 0001_tables.up.sql │ ├── 0002_initialData.down.sql │ └── 0002_initialData.up.sql ├── svc │ ├── authSvc.go │ ├── authSvc_test.go │ ├── guids.go │ ├── userDao.go │ ├── userDao_test.go │ └── userSvc.go └── temp.db ├── diff.go ├── dumpthreads.go ├── env.go ├── exec.go ├── file-receive.go ├── file-server.go ├── file.go ├── flags.go ├── git ├── .gitignore ├── README.md ├── create-test-git.sh ├── git.go ├── go.mod ├── go.sum └── util.go ├── go-flags.go ├── go-flags.ini ├── gob.go ├── gpg.go ├── guid.go ├── hashpassword.go ├── hashsalt-custom.go ├── http-cert.pem ├── http-download-with-progress.go ├── http-download.go ├── http-key.pem ├── http-server.go ├── ifconfig.go ├── interface.go ├── intrnl ├── README.md ├── a │ ├── asource.go │ └── internal │ │ └── b │ │ └── bsource.go ├── intrnl ├── main.go └── x │ └── xsource.go ├── json-web-tokens ├── .README.md.swp └── README.md ├── json.go ├── keyvalues.go ├── migrate ├── README.md ├── db.go ├── main.go ├── migrate ├── migrations │ ├── 001_tables.down.sql │ ├── 001_tables.up.sql │ ├── 002_initialData.down.sql │ └── 002_initialData.up.sql └── temp.db ├── password.go ├── pointer.go ├── postgres.go ├── pqueue.go ├── pqueue2.go ├── prompt.go ├── random.go ├── readlines.go ├── reflect.go ├── regex.go ├── rune.go ├── simple-job-scheduler.go ├── smtp.go ├── soap.go ├── soap.txt ├── sort.go ├── spawn.go ├── sqlite.go ├── sqlx-sqlite.go ├── strings.go ├── switch.go ├── template.go ├── time.go ├── trapctlc.go ├── web ├── .gitignore ├── README.md ├── api │ ├── api.go │ └── user-api.go ├── config.go ├── data │ ├── .gitignore │ ├── auth.go │ ├── auth_test.go │ ├── user.go │ └── user_test.go ├── db.go ├── http.go ├── https-cert.pem ├── https-key.pem ├── main.go ├── migrations │ ├── 001_tables.down.sql │ ├── 001_tables.up.sql │ ├── 002_initialData.down.sql │ └── 002_initialData.up.sql ├── smtp.go ├── web.config └── www │ ├── css │ └── bootstrap.min.css │ ├── js │ ├── bootstrap.min.js │ └── jquery.min.js │ ├── login.html │ ├── main.html │ └── sample-nojs.html └── yaml.go /README.md: -------------------------------------------------------------------------------- 1 | # go-examples 2 | Examples of GoLang code that I will use and return to over time. 3 | 4 | To run one: `go run .go` 5 | 6 | There is also a secure web example I'm working on in the web sub-package 7 | -------------------------------------------------------------------------------- /base64.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/base64" 5 | "fmt" 6 | ) 7 | 8 | func main() { 9 | str := "Hi Mom" 10 | fmt.Printf("String: '%s'\n", str) 11 | 12 | // Encode 13 | encoded := base64Encode([]byte(str)) 14 | fmt.Println("Encoded: ", encoded) 15 | 16 | // Decode 17 | decoded, ok := base64Decode(encoded) 18 | fmt.Printf("Decoded: '%s' ok:%t\n", string(decoded), ok) 19 | } 20 | 21 | func base64Encode(bytes []byte) string { 22 | return base64.StdEncoding.EncodeToString(bytes) 23 | } 24 | 25 | func base64Decode(str string) ([]byte, bool) { 26 | bytes, err := base64.StdEncoding.DecodeString(str) 27 | if err != nil { 28 | return []byte{}, false 29 | } 30 | return bytes, true 31 | } 32 | -------------------------------------------------------------------------------- /byte.go: -------------------------------------------------------------------------------- 1 | // 2 | // run with "go run byte.go" 3 | // 4 | package main 5 | 6 | import ( 7 | "fmt" 8 | "io" 9 | "os" 10 | "strings" 11 | ) 12 | 13 | var byteLf = byte(10) // line feed 14 | var str = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ\n" 15 | 16 | func main() { 17 | 18 | fmt.Println(" == by numeric value") 19 | // Sequentially go from 32 to 128 20 | for i := 32; i <= 128; i++ { 21 | b := byte(i) 22 | if b == byteLf { 23 | fmt.Printf("byte: lf %v %b \n", b, b) 24 | } else { 25 | fmt.Printf("byte: %v %s %b\n", b, []byte{b}, b) 26 | } 27 | } 28 | 29 | fmt.Println(" == by character") 30 | // Read through the bytes in the string 31 | // See strings.go for example of reading through the runes in a string 32 | reader := strings.NewReader(str) 33 | for { 34 | b, err := reader.ReadByte() 35 | if err != nil { 36 | if err == io.EOF { 37 | return 38 | } 39 | fmt.Println("error: ", err) 40 | os.Exit(1) 41 | } 42 | if b == byteLf { 43 | fmt.Printf("byte: lf %b %v\n", b, b) 44 | } else { 45 | fmt.Printf("byte: %s %b %v\n", []byte{b}, b, b) 46 | } 47 | } 48 | 49 | // 50 | // See strings.go for example of converting byte array to string and back 51 | // 52 | 53 | } 54 | -------------------------------------------------------------------------------- /channel.go: -------------------------------------------------------------------------------- 1 | // 2 | // Run with "go run channel.go" 3 | // 4 | package main 5 | 6 | import "fmt" 7 | 8 | type Org struct { 9 | id int16 10 | name string 11 | } 12 | 13 | func main() { 14 | fmt.Println("--- channel ---") 15 | // Build the channel 16 | c := make(chan Org) 17 | 18 | // A go routine that sends to the channel 19 | go fillChannel(c) 20 | 21 | // Read from the channel 22 | for org := range c { 23 | fmt.Printf("id:%d, name: %s \n", org.id, org.name) 24 | } 25 | 26 | x := <-c 27 | fmt.Printf("id:%d, name: %s \n", x.id, x.name) 28 | x = <-c 29 | fmt.Printf("id:%d, name: %s \n", x.id, x.name) 30 | } 31 | 32 | func fillChannel(c chan<- Org) { 33 | for i := 0; i < 8; i++ { 34 | org := Org{id: int16(i), name: fmt.Sprintf("org%d", i)} 35 | c <- org 36 | } 37 | // Tell the receiver that we're done sending 38 | close(c) 39 | } 40 | 41 | // ================================================= 42 | -------------------------------------------------------------------------------- /channel2.go: -------------------------------------------------------------------------------- 1 | // 2 | // Demonstrates an untyped channel that receives multiple types 3 | // 4 | // Run with "go run channel2.go" 5 | // 6 | package main 7 | 8 | import ( 9 | "fmt" 10 | "math" 11 | ) 12 | 13 | type Org struct { 14 | id int16 15 | name string 16 | } 17 | 18 | func main() { 19 | fmt.Println("--- channel ---") 20 | // Build the channel 21 | c := make(chan interface{}) 22 | 23 | // A go routine that sends to the channel 24 | go fillChannel(c) 25 | 26 | // Read from the channel 27 | for value := range c { 28 | switch str := value.(type) { 29 | case string: 30 | fmt.Printf("string: %s\n", str) 31 | case Org: 32 | fmt.Printf("org: %s\n", str.name) 33 | default: 34 | fmt.Println("Oops, not a string or an Org") 35 | } 36 | } 37 | 38 | //x := <-c 39 | //fmt.Printf("id:%d, name: %s \n", x.id, x.name) 40 | //x = <-c 41 | //fmt.Printf("id:%d, name: %s \n", x.id, x.name) 42 | } 43 | 44 | func fillChannel(c chan<- interface{}) { 45 | for i := 0; i < 8; i++ { 46 | org := Org{id: int16(i), name: fmt.Sprintf("org%d", i)} 47 | if math.Mod(float64(i), float64(2)) == 0 { 48 | c <- org 49 | } else { 50 | c <- org.name 51 | } 52 | } 53 | // Tell the receiver that we're done sending 54 | close(c) 55 | } 56 | 57 | // ================================================= 58 | -------------------------------------------------------------------------------- /chat.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Thank you to Drew Olson for his simple chat server that replays your chats to all connections (including yourself): 3 | * https://gist.github.com/drewolson/3950226 4 | * 5 | * Pros: 6 | * Simple 7 | * Any number of telnet clients can connect 8 | * Cons: 9 | * Your own chats are mimic'ed back to your console 10 | * 11 | * How to use from any client: 12 | * telnet 7777 13 | */ 14 | package main 15 | 16 | import ( 17 | "bufio" 18 | "net" 19 | ) 20 | 21 | type Client struct { 22 | incoming chan string 23 | outgoing chan string 24 | reader *bufio.Reader 25 | writer *bufio.Writer 26 | } 27 | 28 | func (client *Client) Read() { 29 | for { 30 | line, _ := client.reader.ReadString('\n') 31 | client.incoming <- line 32 | } 33 | } 34 | 35 | func (client *Client) Write() { 36 | for data := range client.outgoing { 37 | client.writer.WriteString(data) 38 | client.writer.Flush() 39 | } 40 | } 41 | 42 | func (client *Client) Listen() { 43 | go client.Read() 44 | go client.Write() 45 | } 46 | 47 | func NewClient(connection net.Conn) *Client { 48 | writer := bufio.NewWriter(connection) 49 | reader := bufio.NewReader(connection) 50 | 51 | client := &Client{ 52 | incoming: make(chan string), 53 | outgoing: make(chan string), 54 | reader: reader, 55 | writer: writer, 56 | } 57 | 58 | client.Listen() 59 | 60 | return client 61 | } 62 | 63 | type ChatRoom struct { 64 | clients []*Client 65 | joins chan net.Conn 66 | incoming chan string 67 | outgoing chan string 68 | } 69 | 70 | func (chatRoom *ChatRoom) Broadcast(data string) { 71 | for _, client := range chatRoom.clients { 72 | client.outgoing <- data 73 | } 74 | } 75 | 76 | func (chatRoom *ChatRoom) Join(connection net.Conn) { 77 | client := NewClient(connection) 78 | chatRoom.clients = append(chatRoom.clients, client) 79 | go func() { 80 | for { 81 | chatRoom.incoming <- <-client.incoming 82 | } 83 | }() 84 | } 85 | 86 | func (chatRoom *ChatRoom) Listen() { 87 | go func() { 88 | for { 89 | select { 90 | case data := <-chatRoom.incoming: 91 | chatRoom.Broadcast(data) 92 | case conn := <-chatRoom.joins: 93 | chatRoom.Join(conn) 94 | } 95 | } 96 | }() 97 | } 98 | 99 | func NewChatRoom() *ChatRoom { 100 | chatRoom := &ChatRoom{ 101 | clients: make([]*Client, 0), 102 | joins: make(chan net.Conn), 103 | incoming: make(chan string), 104 | outgoing: make(chan string), 105 | } 106 | 107 | chatRoom.Listen() 108 | 109 | return chatRoom 110 | } 111 | 112 | func main() { 113 | chatRoom := NewChatRoom() 114 | 115 | listener, _ := net.Listen("tcp", ":7777") 116 | 117 | for { 118 | conn, _ := listener.Accept() 119 | chatRoom.joins <- conn 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /chooseone.go: -------------------------------------------------------------------------------- 1 | // 2 | // Run with "go run chooseone.go" 3 | // 4 | package main 5 | 6 | import ( 7 | "bufio" 8 | "fmt" 9 | "os" 10 | "strings" 11 | ) 12 | 13 | func main() { 14 | value := ChooseOne("Continue, Quit, or Redo? [cqr]:", "c", "q", "r") 15 | fmt.Println("You chose:", value) 16 | } 17 | 18 | // ChooseOne prompts the user to enter one of n values 19 | // Example: answer := misc.PromptOne("Continue, Quit, or Redo? [cqr]: ", ["c", "q", "r"]) 20 | func ChooseOne(prompt string, values ...string) string { 21 | for { 22 | fmt.Print(prompt) 23 | 24 | // Read input 25 | inReader := bufio.NewReader(os.Stdin) 26 | text, _ := inReader.ReadString('\n') 27 | 28 | // Trim leading and trailing spaces, and convert to lower case 29 | text = strings.TrimSpace(text) 30 | if len(text) == 0 { 31 | return values[0] 32 | } 33 | 34 | for _, v := range values { 35 | if text == v { 36 | return v 37 | } 38 | } 39 | 40 | fmt.Println("Invalid entry, try again.") 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /cmdproxy.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "io" 7 | "os" 8 | "os/exec" 9 | ) 10 | 11 | // main runs the bc Linux command and proxies stdin input to it... all in one method 12 | func main() { 13 | cmd := exec.Command("pianobar") 14 | 15 | in, err := cmd.StdinPipe() 16 | if err != nil { 17 | panic(err) 18 | } 19 | out, err := cmd.StdoutPipe() 20 | if err != nil { 21 | panic(err) 22 | } 23 | stderr, err := cmd.StderrPipe() 24 | if err != nil { 25 | panic(err) 26 | } 27 | 28 | // 29 | // Capture standard error and print it 30 | // 31 | go func() { 32 | defer stderr.Close() 33 | errReader := bufio.NewReader(stderr) 34 | errScanner := bufio.NewScanner(errReader) 35 | for errScanner.Scan() { 36 | fmt.Println(errScanner.Text()) 37 | } 38 | }() 39 | 40 | // 41 | // Capture standard input and pass it to the command 42 | // 43 | go func() { 44 | defer in.Close() 45 | consolereader := bufio.NewReader(os.Stdin) 46 | 47 | for { 48 | //fmt.Print("> ") 49 | // inputText, err := consolereader.ReadString('\n') // this will wait for user input 50 | b, err := consolereader.ReadByte() // this will wait for user input 51 | if err != nil { 52 | if err != io.EOF { 53 | panic(err) 54 | } 55 | } else { 56 | _, err := in.Write([]byte{b}) 57 | if err != nil { 58 | panic(err) 59 | } 60 | //fmt.Fprintln(in, inputText) 61 | } 62 | } 63 | }() 64 | 65 | // 66 | // Start the process 67 | // 68 | if err = cmd.Start(); err != nil { 69 | panic(err) 70 | } 71 | 72 | // 73 | // Capture standard output and print it 74 | // 75 | go func() { 76 | defer out.Close() 77 | reader := bufio.NewReader(out) 78 | scanner := bufio.NewScanner(reader) 79 | for scanner.Scan() { 80 | fmt.Println(scanner.Text()) 81 | } 82 | }() 83 | 84 | fmt.Println("Enter bc calculations (like 5*4) and press enter. Enter 'quit' to exit") 85 | cmd.Wait() 86 | 87 | } 88 | -------------------------------------------------------------------------------- /cobra-flags.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/spf13/cobra" 6 | ) 7 | 8 | var Verbose bool 9 | var action string = "up/down" 10 | 11 | var HugoCmd = &cobra.Command{ 12 | Use: "pfs", 13 | Short: "A personal file server", 14 | Long: `A personal file server for sharing and receiving files from friends. 15 | Complete documentation is available at http://github.com/joncrlsn/pfs`, 16 | Run: func(cmd *cobra.Command, args []string) { 17 | // Do Stuff Here 18 | }, 19 | } 20 | 21 | var uploadCmd = &cobra.Command{ 22 | Use: "upload", 23 | Short: "Allow friend to (only) upload files to your computer", 24 | Long: `Starts web server that only allows uploading of files to 25 | the current directory. No downloading is allowed`, 26 | Run: func(cmd *cobra.Command, args []string) { 27 | fmt.Println("uploading", args) 28 | action = "up" 29 | }, 30 | } 31 | 32 | var downloadCmd = &cobra.Command{ 33 | Use: "download", 34 | Short: "Allow friend to download files from the current directory", 35 | Long: `Starts web server that only allows downloading of files from 36 | the current directory. No uploading is allowed`, 37 | Run: func(cmd *cobra.Command, args []string) { 38 | fmt.Println("downloading", args) 39 | action = "down" 40 | }, 41 | } 42 | 43 | func init() { 44 | HugoCmd.AddCommand(uploadCmd) 45 | HugoCmd.AddCommand(downloadCmd) 46 | HugoCmd.PersistentFlags().BoolVarP(&Verbose, "verbose", "v", false, "verbose output") 47 | HugoCmd.Execute() 48 | } 49 | 50 | func main() { 51 | fmt.Println("verbose", Verbose) 52 | fmt.Println("action", action) 53 | } 54 | -------------------------------------------------------------------------------- /concat.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | import "bytes" 5 | 6 | func main() { 7 | fmt.Println("--- concat ---") 8 | _doSprint() 9 | _doBuffer() 10 | } 11 | 12 | func _doSprint() { 13 | a := fmt.Sprint(1, " + ", 2, " == ", 3, " evaluates ", true, ".") 14 | fmt.Println(a) 15 | } 16 | 17 | func _doBuffer() { 18 | a := []interface{}{"Apple", "Banana", "Coffee", "Donut"} 19 | var buffer bytes.Buffer 20 | for i, v := range a { 21 | buffer.WriteString(fmt.Sprint(i, ":", v, ", ")) 22 | } 23 | s := buffer.String() 24 | fmt.Println(s) 25 | } 26 | -------------------------------------------------------------------------------- /conversion.go: -------------------------------------------------------------------------------- 1 | // 2 | // Provides examples of converting between data types 3 | // 4 | package main 5 | 6 | import ( 7 | "fmt" 8 | "strconv" 9 | ) 10 | 11 | func main() { 12 | 13 | // convert string to int 14 | i, _ := strconv.Atoi("100") 15 | 16 | // convert int to string 17 | var str string = strconv.Itoa(i) 18 | fmt.Println("str is back to a string", str) 19 | } 20 | -------------------------------------------------------------------------------- /csv.go: -------------------------------------------------------------------------------- 1 | // 2 | // An example of how to process a csv files 3 | // 4 | package main 5 | 6 | import ( 7 | "encoding/csv" 8 | "fmt" 9 | "io" 10 | "log" 11 | "os" 12 | "strings" 13 | ) 14 | 15 | func main() { 16 | fmt.Println("=== csv ===") 17 | ReadData() 18 | WriteArray() 19 | } 20 | 21 | func WriteArray() { 22 | fmt.Println("Write: ===") 23 | records := [][]string{ 24 | {"first_name", "last_name", "username"}, 25 | {"Rob", "Pike", "rob"}, 26 | {"Ken", "Thompson", "ken"}, 27 | {"Robert", "Griesemer", "gri"}, 28 | } 29 | 30 | w := csv.NewWriter(os.Stdout) 31 | 32 | for _, record := range records { 33 | if err := w.Write(record); err != nil { 34 | log.Fatalln("error writing record to csv:", err) 35 | } 36 | } 37 | 38 | // Write any buffered data to the underlying writer (standard output). 39 | w.Flush() 40 | 41 | if err := w.Error(); err != nil { 42 | log.Fatal(err) 43 | } 44 | } 45 | 46 | func ReadData() { 47 | fmt.Println("Read: ===") 48 | 49 | // The first line is the title line 50 | const in = ` 51 | "Group","Title","Username","Password","URL","Notes" 52 | "Root","Peterson Family Tree","joncrlsn","more-secret-than-you-know","http://www.example.com/phpgedview/","" 53 | "Root","Twitter","jon_carlson","supersecret","http://twitter.com","" 54 | ` 55 | r := csv.NewReader(strings.NewReader(in)) 56 | 57 | for { 58 | record, err := r.Read() 59 | if err == io.EOF { 60 | break 61 | } 62 | if err != nil { 63 | log.Fatal(err) 64 | } 65 | 66 | fmt.Println(record) 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /data-access/README.md: -------------------------------------------------------------------------------- 1 | # data access examples 2 | 3 | This demonstrates a pattern for accessing data in a database. This case specifically authenticating users. 4 | 5 | Service objects are singletons that have business logic. They use the DAO (data access object) singletons when they interact with the database. 6 | 7 | -------------------------------------------------------------------------------- /data-access/db.go: -------------------------------------------------------------------------------- 1 | // 2 | // Sets up the database connection and ensure the schema is migrated. 3 | // 4 | // Copyright (c) 2015 Jon Carlson. All rights reserved. 5 | // Use of this source code is governed by the MIT 6 | // license that can be found in the LICENSE file. 7 | // 8 | package main 9 | 10 | import ( 11 | "database/sql" 12 | "github.com/jmoiron/sqlx" 13 | _ "github.com/mattes/migrate/driver/sqlite3" 14 | "github.com/mattes/migrate/migrate" 15 | _ "github.com/mattn/go-sqlite3" 16 | 17 | "log" 18 | ) 19 | 20 | func setupDb(dbType string, dbName string, migrateSqlPath string) (db *sqlx.DB, err error) { 21 | log.Printf("=== Checking schema version (dbType=%s, dbName=%s, migrate=%s) ===", dbType, dbName, migrateSqlPath) 22 | dbUrl := dbType + "://" + dbName 23 | 24 | var sqlDb *sql.DB 25 | sqlDb, err = sql.Open(dbType, dbName) 26 | if err != nil { 27 | return nil, err 28 | } 29 | sqlDb.Close() 30 | 31 | // Synchronously migrate the database up if needed. 32 | allErrors, ok := migrate.ResetSync(dbUrl, migrateSqlPath) 33 | if !ok { 34 | log.Println("Error migrating schema", allErrors) 35 | return nil, nil // Program should stop 36 | } 37 | 38 | // Get database connection to return 39 | db, err = sqlx.Connect(dbType, dbName) 40 | if err != nil { 41 | log.Println("Error connecting to db", err) 42 | return nil, err 43 | } 44 | 45 | // success 46 | log.Println("success connecting to db") 47 | return db, nil 48 | } 49 | -------------------------------------------------------------------------------- /data-access/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | // 4 | // Copyright (c) 2015 Jon Carlson. All rights reserved. 5 | // Use of this source code is governed by the MIT 6 | // license that can be found in the LICENSE file. 7 | // 8 | 9 | import ( 10 | "github.com/joncrlsn/go-examples/data-access/svc" 11 | _ "github.com/mattn/go-sqlite3" 12 | "log" 13 | ) 14 | 15 | var dbType = "sqlite3" 16 | var dbUrl = "temp.db" 17 | var migrateSqlPath = "./migrations" 18 | 19 | func main() { 20 | 21 | guids := svc.NewGuids() 22 | 23 | // 24 | // Setting up the database 25 | // 26 | db, err := setupDb(dbType, dbUrl, migrateSqlPath) 27 | if err != nil || db == nil { 28 | log.Println("Error setting up the db", err) 29 | return "" 30 | } 31 | 32 | // 33 | // Examples 34 | // 35 | //authSvc := svc.NewAuthSvc(db, guids) 36 | 37 | userSvc := svc.NewUserSvc(db, guids) 38 | var users []svc.User 39 | if users, err = userSvc.FindByEmail("jon@example.com"); err != nil { 40 | log.Println("Error finding user by email", err) 41 | return 42 | } 43 | 44 | log.Println(users) 45 | } 46 | -------------------------------------------------------------------------------- /data-access/migrations/0001_tables.down.sql: -------------------------------------------------------------------------------- 1 | 2 | DROP TABLE IF EXISTS user; 3 | DROP TABLE IF EXISTS org; 4 | -------------------------------------------------------------------------------- /data-access/migrations/0001_tables.up.sql: -------------------------------------------------------------------------------- 1 | -- 2 | -- sqlite3 3 | -- 4 | 5 | CREATE TABLE user ( 6 | user_id BIGINT PRIMARY KEY, 7 | first_name VARCHAR(80) DEFAULT '', 8 | last_name VARCHAR(80) DEFAULT '', 9 | email VARCHAR(250) DEFAULT '', 10 | password VARCHAR(250) DEFAULT '', 11 | org_id BIGINT DEFAULT 0 12 | ); 13 | 14 | CREATE TABLE org ( 15 | org_id BIGINT PRIMARY KEY, 16 | org_name VARCHAR(80) DEFAULT '' 17 | ); 18 | -------------------------------------------------------------------------------- /data-access/migrations/0002_initialData.down.sql: -------------------------------------------------------------------------------- 1 | DELETE FROM user; 2 | DELETE FROM org; 3 | -------------------------------------------------------------------------------- /data-access/migrations/0002_initialData.up.sql: -------------------------------------------------------------------------------- 1 | 2 | INSERT INTO org(org_id, org_name) VALUES (1, 'Default'); 3 | 4 | -- password is 'supersecret' 5 | INSERT INTO user (user_id, first_name, last_name, email, password, org_id) VALUES (1, 'Joe', 'Carter', 'joe@example.com', 'c44tqq+T5R57KwVuAJKfNiriIzmxR+uUcyJNuvAA7PvI84OE7xUpASLiGyMp4/vgYDVJu49u99ugxujEH4g7hw==', 1); 6 | INSERT INTO user (user_id, first_name, last_name, email, password, org_id) VALUES (2, 'Jon', 'Carlson', 'jon@example.com', 'c44tqq+T5R57KwVuAJKfNiriIzmxR+uUcyJNuvAA7PvI84OE7xUpASLiGyMp4/vgYDVJu49u99ugxujEH4g7hw==', 1); 7 | INSERT INTO user (user_id, first_name, last_name, email, org_id) VALUES (3, 'Jane', 'Citizen', 'jane@example.com', 1); 8 | -------------------------------------------------------------------------------- /data-access/svc/authSvc.go: -------------------------------------------------------------------------------- 1 | package svc 2 | 3 | import "github.com/jmoiron/sqlx" 4 | 5 | // 6 | // Provides authentication services 7 | // 8 | // Copyright (c) 2015 Jon Carlson. All rights reserved. 9 | // Use of this source code is governed by the MIT 10 | // license that can be found in the LICENSE file. 11 | // 12 | 13 | // NewAuthSvc builds and returns a new, properly instantiated AuthSvc 14 | func NewAuthSvc(db *sqlx.DB, guids *Guids) AuthSvc { 15 | userDao := newUserDao(db, guids) 16 | return AuthSvc{userDao} 17 | } 18 | 19 | type AuthSvc struct { 20 | userDao *UserDao 21 | } 22 | 23 | // func (svc AuthSvc) SavePassword(db *sqlx.DB, email string, password string) (err error) { 24 | // // Find the userId for the given email 25 | // var userId int 26 | // err = db.Get(&userId, findUserIdByEmail, email) 27 | // svc.userData.findIDAndPasswordByEmail(email string) 28 | // 29 | // if err != nil { 30 | // return err 31 | // } 32 | // if userId == 0 { 33 | // err = errors.New("email not found") 34 | // } else { 35 | // var hashedPassword string 36 | // hashedPassword, err = misc.HashPasswordDefaultCost(password) 37 | // if err == nil { 38 | // _, err = db.Exec(updatePasswordByUserId, hashedPassword, userId) 39 | // } 40 | // } 41 | // 42 | // return err // err may be nil 43 | // } 44 | 45 | // Authenticate method... When successful, the returned user will have a non-negative UserId. 46 | func (svc AuthSvc) Authenticate(email string, testPassword string) (user User, err error) { 47 | // Find the hashed password for the given email 48 | var rows *sql.Rows 49 | rows, err = svc.userDao.findUserIdAndPasswordByEmail(email) 50 | if err != nil { 51 | return 52 | } 53 | for rows.Next() { 54 | var userId int 55 | var hashedPassword sql.NullString 56 | err = rows.Scan(&userId, &hashedPassword) 57 | if err != nil { 58 | log.Println("AuthSvc.Authenticate() Error in Scan:", err) 59 | } else if userId == 0 { 60 | log.Println("AuthSvc.Authenticate() user not found for email:", email) 61 | } else if hashedPassword.Valid { 62 | if misc.ComparePassword(testPassword, hashedPassword.String) { 63 | // We found a match so find the user 64 | user, err = svc.userDao.findById(userId) 65 | } else { 66 | log.Println("AuthSvc.Authentic() Password mismatched for email:", email) 67 | } 68 | } 69 | rows.Close() 70 | } 71 | 72 | // return the user and error 73 | return user, err 74 | } 75 | -------------------------------------------------------------------------------- /data-access/svc/authSvc_test.go: -------------------------------------------------------------------------------- 1 | package svc 2 | 3 | // 4 | // Copyright (c) 2015 Jon Carlson. All rights reserved. 5 | // Use of this source code is governed by the MIT 6 | // license that can be found in the LICENSE file. 7 | // 8 | 9 | import ( 10 | "testing" 11 | 12 | "github.com/jmoiron/sqlx" 13 | "github.com/mattes/migrate/migrate" 14 | ) 15 | 16 | // func Test_SavePassword(t *testing.T) { 17 | // db := resetDb(t) 18 | // 19 | // // Save a new password 20 | // err := SavePassword(db, "joncrlsn@gmail.com", "new-secret") 21 | // assert.Nil(t, err, "error saving password for email %v", err) 22 | // 23 | // // See if we can authenticate using the password we just saved 24 | // user, err2 := Authenticate(db, "joncrlsn@gmail.com", "new-secret") 25 | // assert.Nil(t, err2, "error authenticating valid email address %v", err) 26 | // assert.Equal(t, "joncrlsn@gmail.com", user.Email) 27 | // 28 | // // try an invalid email 29 | // err = SavePassword(db, "xxxx@yyyy.com", "whatever") 30 | // assert.NotNil(t, err, "Did not receive error when saving password for invalid email ") 31 | // } 32 | // 33 | // func Test_Authenticate(t *testing.T) { 34 | // db := resetDb(t) 35 | // 36 | // user, err := Authenticate(db, "xxxx@yyyy.com", "super-duper-secret") 37 | // assert.Nil(t, err, "error authenticating bogus email address %v", err) 38 | // assert.Equal(t, 0, user.UserId) 39 | // 40 | // user, err = Authenticate(db, "joncrlsn@gmail.com", "super-duper-secret") 41 | // assert.Nil(t, err, "error authenticating valid email") 42 | // assert.NotEqual(t, 0, user.UserId) 43 | // 44 | // user, err = Authenticate(db, "joncrlsn@gmail.com", "wrong-password") 45 | // assert.Nil(t, err, "error authenticating valid email and wrong password") 46 | // assert.Equal(t, 0, user.UserId) 47 | // } 48 | 49 | func resetDb(t *testing.T) (db *sqlx.DB) { 50 | // Synchronously setup the DB or migrate it to the latest 51 | allErrors, ok := migrate.ResetSync("sqlite3://"+dbName, "../migrations") 52 | if !ok { 53 | t.Fatal("Error resetting test db", allErrors) 54 | } 55 | 56 | db = sqlx.MustConnect("sqlite3", dbName) 57 | 58 | return db 59 | } 60 | -------------------------------------------------------------------------------- /data-access/svc/guids.go: -------------------------------------------------------------------------------- 1 | package svc 2 | 3 | import ( 4 | "math/rand" 5 | "time" 6 | ) 7 | 8 | const ( 9 | // max24Bits is the max number that can fit in 24 bits (2^24) 10 | max24Bits = 16777216 // 16 million 11 | max39Bits = 549755813888 // 549 billion 12 | ) 13 | 14 | var ( 15 | // janFirst2015 is the 100ths of a second between Jan 1, 1970 and Jan 1, 2015 16 | janFirst2015 = time.Date(2015, time.January, 1, 0, 0, 0, 0, time.UTC).UnixNano() / 1000 / 1000 / 10 17 | ) 18 | 19 | // Guid provides pseudo globally unique identifiers as an int64 20 | // The first 5 bytes are the time portion (100ths of a second since Jan 1, 2015) 21 | // The last 3 bytes are a random number 22 | type Guids struct { 23 | initialized bool 24 | } 25 | 26 | // NewGuidMaker instantiates a GuidMaker 27 | func NewGuids() *Guids { 28 | return &Guids{false} 29 | } 30 | 31 | func (guids *Guids) next() int64 { 32 | if !guids.initialized { 33 | rand.Seed(time.Now().UTC().UnixNano()) 34 | guids.initialized = true 35 | } 36 | 37 | // now is 100ths of a second since Jan 1, 1970 38 | now := time.Now().UTC().UnixNano() / 1000 / 1000 / 10 39 | 40 | // subtract now from Jan 1,2015 41 | timePortion := now - janFirst2015 42 | 43 | // bitshift time portion by 24 bits to the left (we are only losing zero bits) 44 | timePortion = timePortion << 24 45 | 46 | random24 := int64(rand.Intn(max24Bits)) 47 | 48 | // bit or the time portion and the random24 portion 49 | guid := timePortion | random24 50 | 51 | return guid 52 | } 53 | -------------------------------------------------------------------------------- /data-access/svc/userDao.go: -------------------------------------------------------------------------------- 1 | package svc 2 | 3 | // 4 | // Provides private methods for querying and changing the user table 5 | // 6 | // Copyright (c) 2015 Jon Carlson. All rights reserved. 7 | // Use of this source code is governed by the MIT 8 | // license that can be found in the LICENSE file. 9 | // 10 | 11 | import ( 12 | "database/sql" 13 | "errors" 14 | 15 | "github.com/jmoiron/sqlx" 16 | ) 17 | 18 | func newUserDao(db *sqlx.DB, guids *Guids) *UserDao { 19 | return &UserDao{db, guids} 20 | } 21 | 22 | // UserDao is a data access object 23 | type UserDao struct { 24 | Db *sqlx.DB 25 | guids *Guids 26 | } 27 | 28 | // User is a data object that happens to represents a row in the user table 29 | type User struct { 30 | UserID int64 `db:"user_id"` 31 | FirstName string `db:"first_name"` 32 | LastName string `db:"last_name"` 33 | Email string `db:"email"` 34 | } 35 | 36 | const ( 37 | userInsertSql = `INSERT INTO user (user_id, first_name, last_name, email) VALUES (:UserID, :FirstName, :LastName, :Email);` 38 | userUpdateSql = `UPDATE user SET first_name = :FirstName, last_name = :LastName, email = :Email) WHERE user_id = :UserID;` 39 | userFindByEmailSql = `SELECT user_id, first_name, last_name, email FROM user WHERE email = $1;` 40 | userFindByIdSql = `SELECT user_id, first_name, last_name, email FROM user WHERE user_id = $1;` 41 | userFindIdAndPasswordByEmailSql = "SELECT user_id, password FROM user WHERE email = $1" 42 | userIdFindByEmailSql = "SELECT user_id FROM user WHERE email = $1" 43 | userUpdatePasswordByUserIdSql = "UPDATE user SET password = $1 WHERE user_id = $2" 44 | ) 45 | 46 | func (dao *UserDao) create(user *User) error { 47 | if user.UserID != 0 { 48 | return errors.New("When creating a user, UserID must be zero") 49 | } 50 | user.UserID = dao.guids.next() 51 | _, err := dao.Db.NamedExec(userInsertSql, user) 52 | return err 53 | } 54 | 55 | func (dao *UserDao) update(user User) error { 56 | if user.UserID == 0 { 57 | return errors.New("When updating a user, UserID cannot be zero") 58 | } 59 | _, err := dao.Db.NamedExec(userUpdateSql, user) 60 | return err 61 | } 62 | 63 | // FindByEmail returns a slice of User instances 64 | func (dao *UserDao) findByEmail(email string) ([]User, error) { 65 | users := []User{} 66 | err := dao.Db.Select(&users, userFindByEmailSql, email) 67 | return users, err 68 | } 69 | 70 | // FindById returns an instance for the given id. UserID will be 0 if none found. 71 | func (dao *UserDao) findById(id int) (User, error) { 72 | var user User 73 | err := dao.Db.Get(&user, userFindByIdSql, id) 74 | return user, err 75 | } 76 | 77 | func (dao *UserDao) findIdAndPasswordByEmail(email string) (*sql.Rows, error) { 78 | return dao.Db.Query(userFindIdAndPasswordByEmailSql, email) 79 | } 80 | -------------------------------------------------------------------------------- /data-access/svc/userDao_test.go: -------------------------------------------------------------------------------- 1 | package svc 2 | 3 | import ( 4 | _ "github.com/mattes/migrate/driver/sqlite3" 5 | "github.com/mattes/migrate/migrate" 6 | _ "github.com/mattn/go-sqlite3" 7 | "testing" 8 | 9 | "github.com/jmoiron/sqlx" 10 | "github.com/stvp/assert" 11 | ) 12 | 13 | const ( 14 | dbName = "__deleteme.db" 15 | ) 16 | 17 | var ( 18 | userDao *UserDao 19 | db *sqlx.DB 20 | ) 21 | 22 | func initialize(t *testing.T) { 23 | // Synchronously setup the DB or migrate it to the latest 24 | 25 | allErrors, ok := migrate.ResetSync("sqlite3://"+dbName, "../migrations") 26 | if !ok { 27 | t.Fatal("Error resetting test db", allErrors) 28 | } 29 | 30 | var err error 31 | db, err = sqlx.Connect("sqlite3", dbName) 32 | if err != nil { 33 | t.Fatal("Error connectin to db", err) 34 | } 35 | 36 | guids := NewGuids() 37 | userDao = newUserDao(db, guids) 38 | } 39 | 40 | func Test_UserFindByEmail(t *testing.T) { 41 | initialize(t) 42 | 43 | users, err := userDao.findByEmail("joncrlsn@gmail.com") 44 | assert.Nil(t, err, "error finding by email") 45 | assert.Equal(t, 1, len(users), "Expected 1 user returned") 46 | assert.Equal(t, "joncrlsn@gmail.com", users[0].Email, "Wrong email returned from db") 47 | } 48 | -------------------------------------------------------------------------------- /data-access/svc/userSvc.go: -------------------------------------------------------------------------------- 1 | package svc 2 | 3 | // 4 | // Provides private methods for querying and changing the user table 5 | // 6 | // Copyright (c) 2015 Jon Carlson. All rights reserved. 7 | // Use of this source code is governed by the MIT 8 | // license that can be found in the LICENSE file. 9 | // 10 | 11 | import "github.com/jmoiron/sqlx" 12 | 13 | // NewAuthSvc builds and returns a new, properly instantiated AuthSvc 14 | func NewUserSvc(db *sqlx.DB, guids *Guids) UserSvc { 15 | userDao := newUserDao(db, guids) 16 | return UserSvc{userDao} 17 | } 18 | 19 | type UserSvc struct { 20 | userDao *UserDao 21 | } 22 | 23 | // FindByEmail returns a slice of User instances 24 | func (svc UserSvc) FindByEmail(email string) ([]User, error) { 25 | return svc.userDao.findByEmail(email) 26 | } 27 | 28 | func (svc UserSvc) Create(first, last, email string) error { 29 | user := User{} 30 | user.FirstName = first 31 | user.LastName = last 32 | user.Email = email 33 | return svc.userDao.create(&user) 34 | } 35 | -------------------------------------------------------------------------------- /data-access/temp.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joncrlsn/go-examples/2ca6c93433ffb538a742e071ba34426d05ef813e/data-access/temp.db -------------------------------------------------------------------------------- /diff.go: -------------------------------------------------------------------------------- 1 | // 2 | // Compare two string slices, outputting commands to make the second match the first. 3 | // This design is in anticipation of the pgdiff program I am writing. 4 | // 5 | package main 6 | 7 | import "fmt" 8 | 9 | func main() { 10 | fmt.Println("--- diff ---") 11 | fmt.Println(" (Make slice 2 the same as slice 1)") 12 | 13 | s1 := []string{"a", "b", "c", "d", "e", "f", "g"} 14 | s2 := []string{"a", "a1", "b", "d"} 15 | doDiff(s1, s2) 16 | doDiff(s2, s1) 17 | 18 | s1 = []string{"b", "c", "d", "e", "f"} 19 | s2 = []string{"a", "b", "g"} 20 | doDiff(s1, s2) 21 | doDiff(s2, s1) 22 | } 23 | 24 | func doDiff(slice1 []string, slice2 []string) { 25 | fmt.Printf("------\n== slice1: %v\n", slice1) 26 | fmt.Printf("== slice2: %v\n", slice2) 27 | i1 := 0 28 | i2 := 0 29 | v1 := slice1[i1] 30 | v2 := slice2[i2] 31 | for i1 < len(slice1) || i2 < len(slice2) { 32 | if v1 == v2 { 33 | // compareValues(v1, v2) (look for non-identifying changes) 34 | fmt.Printf("Both have %s\n", v1) // add v1 35 | i1 = i1 + 1 36 | if i1 < len(slice1) { 37 | v1 = slice1[i1] // nextValue(db1) 38 | } 39 | i2 = i2 + 1 40 | if i2 < len(slice2) { 41 | v2 = slice2[i2] // nextValue(db2) 42 | } 43 | } else if v1 < v2 { 44 | // slice2 is missing a value slice1 has 45 | i1 = i1 + 1 46 | if i1 < len(slice1) { 47 | fmt.Printf("Add %s to slice 2\n", v1) // add v1 48 | v1 = slice1[i1] // nextValue(db1) 49 | } else { 50 | // slice 2 is longer than slice1 51 | fmt.Printf("Drop %s from slice 2\n", v2) // add v1 52 | i2 = i2 + 1 53 | if i2 < len(slice2) { 54 | v2 = slice2[i2] // nextValue(db1) 55 | } 56 | } 57 | } else if v1 > v2 { 58 | // db2 has an extra column that we don't want 59 | i2 = i2 + 1 60 | if i2 < len(slice2) { 61 | fmt.Printf("Drop %s from slice 2\n", v2) // drop v2 62 | v2 = slice2[i2] // nextValue(db2) 63 | } else { 64 | // slice 1 is longer than slice2 65 | fmt.Printf("Add %s to slice 2\n", v1) // add v1 66 | i1 = i1 + 1 67 | if i1 < len(slice1) { 68 | v1 = slice1[i1] // nextValue(db1) 69 | } 70 | } 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /dumpthreads.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2015 Jon Carlson. All rights reserved. 3 | // Use of this source code is governed by the MIT 4 | // license that can be found in the LICENSE file. 5 | // 6 | package main 7 | 8 | import ( 9 | "fmt" 10 | "log" 11 | "os" 12 | "os/exec" 13 | "strconv" 14 | templ "text/template" 15 | ) 16 | 17 | // This script creates an SSH session and remotely runs a bash script 18 | var dumpThreadsScript = ` 19 | #!/bin/bash 20 | # Remotely dumps Java threads a number of times 21 | ssh -o StrictHostKeyChecking=no {{.host}} 'bash -s' <<-END 22 | #!/bin/bash 23 | COUNT={{.dumpCount}} 24 | # Write the thread dumps to a particular location 25 | FILE="/home/{{.pidOwner}}/logs/threads.\$(date +%Y-%m-%d_%H%M%S).{{.host}}" 26 | PID=\$(ps aux | grep -P '(central|blue).*java' | grep -v grep | grep -v flock | egrep -v 'su (central|blue)' | awk '{print \$2}') 27 | #echo "\$FILE" 28 | #echo "\$PID" 29 | for (( c=1; c<=COUNT; c++ )) ; do 30 | sudo su {{.pidOwner}} -- -c "touch \${FILE}; jstack -l \$PID >> \${FILE}" 31 | echo "Threads dumped... to \$FILE. Sleeping for {{.intervalSeconds}} seconds..." 32 | sleep {{.intervalSeconds}} 33 | done 34 | echo done 35 | END 36 | ` 37 | 38 | // main generates a script to dumps the Java threads on the given host for the given number of times 39 | // This will need modification for monitoring a Windows-based resource because it creates a 40 | // bash script that is executed remotely. 41 | func main(host, user string, dumpCount int, intervalSeconds int) error { 42 | 43 | // Save script file 44 | filename := host + "_dumpThreadScript.sh" 45 | file, err := os.Create(filename) 46 | if err != nil { 47 | return err 48 | } 49 | fmt.Println(" Writing script to file : " + filename) 50 | 51 | t := templ.New("Dump threads script") 52 | templ.Must(t.Parse(dumpThreadsScript)) 53 | context := map[string]string{ 54 | "pidOwner": user, // user is the account process is running as 55 | "dumpCount": strconv.Itoa(dumpCount), 56 | "intervalSeconds": strconv.Itoa(intervalSeconds), 57 | "host": host} 58 | err = t.Execute(file, context) 59 | if err != nil { 60 | return err 61 | } 62 | file.Close() 63 | 64 | cmdStr := "./" + host + "_dumpThreadScript.sh" 65 | log.Printf("Executing: %s \n", cmdStr) 66 | sshCmd := exec.Command(cmdStr) 67 | bytes, err := sshCmd.CombinedOutput() 68 | fmt.Printf("Output:\n %s \n", string(bytes)) 69 | if err != nil { 70 | return err 71 | } 72 | 73 | return nil 74 | } 75 | -------------------------------------------------------------------------------- /env.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | import "os" 5 | 6 | //import "strings" 7 | import "path/filepath" 8 | 9 | func main() { 10 | fmt.Println("--- env ---") 11 | // To set a key/value pair, use `os.Setenv`. To get a 12 | // value for a key, use `os.Getenv`. This will return 13 | // an empty string if the key isn't present in the 14 | // environment. 15 | os.Setenv("FOO", "1") 16 | fmt.Println("FOO:", os.Getenv("FOO")) 17 | fmt.Println("DBHOST:", os.Getenv("DBHOST")) 18 | fmt.Println("HOME:", os.Getenv("HOME")) 19 | 20 | cwd, err := filepath.Abs(filepath.Dir(os.Args[0])) 21 | if err != nil { 22 | panic(err) 23 | } 24 | fmt.Println("Current Working Directory:", cwd) 25 | 26 | // Use `os.Environ` to list all key/value pairs in the 27 | // environment. This returns a slice of strings in the 28 | // form `KEY=value`. You can `strings.Split` them to 29 | // get the key and value. Here we print all the keys. 30 | // fmt.Println() 31 | // for _, e := range os.Environ() { 32 | // pair := strings.Split(e, "=") 33 | // fmt.Printf("%s=%s ", pair[0], pair[1]) 34 | // } 35 | fmt.Println() 36 | } 37 | -------------------------------------------------------------------------------- /exec.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | import "os" 5 | import "os/exec" 6 | import "syscall" 7 | 8 | func main() { 9 | fmt.Println("--- exec ---") 10 | 11 | // For our example we'll exec `ls`. Go requires an 12 | // absolute path to the binary we want to execute, so 13 | // we'll use `exec.LookPath` to find it (probably 14 | // `/bin/ls`). 15 | binary, lookErr := exec.LookPath("ls") 16 | if lookErr != nil { 17 | panic(lookErr) 18 | } 19 | 20 | // `Exec` requires arguments in slice form (as 21 | // apposed to one big string). We'll give `ls` a few 22 | // common arguments. Note that the first argument should 23 | // be the program name. 24 | args := []string{"ls", "-a", "-l", "-h"} 25 | 26 | // `Exec` also needs a set of [environment variables](environment-variables) 27 | // to use. Here we just provide our current 28 | // environment. 29 | env := os.Environ() 30 | 31 | // Here's the actual `syscall.Exec` call. If this call is 32 | // successful, the execution of our process will end 33 | // here and be replaced by the `/bin/ls -a -l -h` 34 | // process. If there is an error we'll get a return 35 | // value. 36 | execErr := syscall.Exec(binary, args, env) 37 | if execErr != nil { 38 | panic(execErr) 39 | } 40 | } 41 | 42 | func doexec2() { 43 | // http://stackoverflow.com/questions/8875038/redirect-stdout-pipe-of-child-process-in-golang 44 | // Send output of the executed command to the console 45 | cmd := exec.Command("ls", "-l") 46 | cmd.Stdin = os.Stdin 47 | cmd.Stdout = os.Stdout 48 | cmd.Stderr = os.Stderr 49 | cmd.Run() 50 | } 51 | -------------------------------------------------------------------------------- /file-receive.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | // 4 | // This example is an HTTP server that can receive a file upload. 5 | // https://www.socketloop.com/tutorials/golang-upload-file 6 | // 7 | // To upload a file, go to :8080/upload 8 | // To download file, go to :8080/ 9 | // 10 | 11 | import ( 12 | "fmt" 13 | "io" 14 | "log" 15 | "net/http" 16 | "os" 17 | ) 18 | 19 | // infoHandler returns an HTML upload form 20 | func uploadHandler(w http.ResponseWriter, r *http.Request) { 21 | if r.Method == "GET" { 22 | fmt.Fprintf(w, ` 23 | 24 | GoLang HTTP Fileserver 25 | 26 | 27 | 28 | 29 |

Upload a file

30 | 31 |
32 | 33 | 34 |
35 | 36 |
37 | 38 | 39 | `) 40 | } 41 | } 42 | 43 | // receiveHandler accepts the file and saves it to the current working directory 44 | func receiveHandler(w http.ResponseWriter, r *http.Request) { 45 | 46 | // the FormFile function takes in the POST input id file 47 | file, header, err := r.FormFile("file") 48 | 49 | if err != nil { 50 | fmt.Fprintln(w, err) 51 | return 52 | } 53 | 54 | defer file.Close() 55 | 56 | out, err := os.Create(header.Filename) 57 | if err != nil { 58 | fmt.Fprintf(w, "Unable to create the file for writing. Check your write access privilege") 59 | return 60 | } 61 | 62 | defer out.Close() 63 | 64 | // write the content from POST to the file 65 | _, err = io.Copy(out, file) 66 | if err != nil { 67 | fmt.Fprintln(w, err) 68 | } 69 | 70 | fmt.Fprintf(w, "File uploaded successfully: ") 71 | fmt.Fprintf(w, header.Filename) 72 | } 73 | 74 | func main() { 75 | dir, err := os.Getwd() 76 | if err != nil { 77 | fmt.Println("err=", err) 78 | os.Exit(1) 79 | } 80 | 81 | http.HandleFunc("/upload", uploadHandler) // Display a form for user to upload file 82 | http.HandleFunc("/receive", receiveHandler) // Handle the incoming file 83 | http.Handle("/", http.FileServer(http.Dir(dir))) 84 | log.Fatal(http.ListenAndServe(":8080", nil)) 85 | } 86 | -------------------------------------------------------------------------------- /file-server.go: -------------------------------------------------------------------------------- 1 | // 2 | // Runs an HTTP static file server on the directory that this is executed from 3 | // 4 | package main 5 | 6 | import ( 7 | "fmt" 8 | "log" 9 | "net/http" 10 | "os" 11 | "strings" 12 | ) 13 | 14 | func main() { 15 | var port string 16 | if len(os.Args) > 1 { 17 | port = os.Args[1] 18 | } else { 19 | port = "8080" 20 | } 21 | 22 | fmt.Printf("Serving files in the current directory on port %s \n", port) 23 | if !strings.HasPrefix(port, ":") { 24 | port = ":" + port 25 | } 26 | 27 | dir, err := os.Getwd() 28 | if err != nil { 29 | fmt.Println("err=", err) 30 | os.Exit(1) 31 | } 32 | http.Handle("/", http.FileServer(http.Dir(dir))) 33 | log.Fatal(http.ListenAndServe(port, nil)) 34 | 35 | // Another way: to do it 36 | // log.Fatal(http.ListenAndServe(":" + port, http.FileServer(http.Dir(dir)))) 37 | } 38 | -------------------------------------------------------------------------------- /file.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "io" 7 | "io/ioutil" 8 | "os" 9 | ) 10 | 11 | func main() { 12 | writing() 13 | reading() 14 | deleting() 15 | } 16 | 17 | // https://gobyexample.com/reading-files 18 | func reading() { 19 | fmt.Println("=== file.reading ===") 20 | 21 | // Perhaps the most basic file reading task is slurping a file's entire contents into memory. 22 | dat, err := ioutil.ReadFile("/tmp/dat") 23 | check(err) 24 | fmt.Print(string(dat)) 25 | 26 | // You'll often want more control over how and what parts of a file are read. For these tasks, start by Opening a file to obtain an os.File value. 27 | f, err := os.Open("/tmp/dat") 28 | check(err) 29 | 30 | // Read some bytes from the beginning of the file. Allow up to 5 to be read but also note how many actually were read. 31 | b1 := make([]byte, 5) 32 | n1, err := f.Read(b1) 33 | check(err) 34 | fmt.Printf("%d bytes: %s\n", n1, string(b1)) 35 | 36 | // You can also Seek to a known location in the file and Read from there. 37 | o2, err := f.Seek(6, 0) 38 | check(err) 39 | b2 := make([]byte, 2) 40 | n2, err := f.Read(b2) 41 | check(err) 42 | fmt.Printf("%d bytes @ %d: %s\n", n2, o2, string(b2)) 43 | 44 | //The io package provides some functions that may be helpful for file reading. For example, reads like the ones above can be more robustly implemented with ReadAtLeast. 45 | o3, err := f.Seek(6, 0) 46 | check(err) 47 | b3 := make([]byte, 2) 48 | n3, err := io.ReadAtLeast(f, b3, 2) 49 | check(err) 50 | fmt.Printf("%d bytes @ %d: %s\n", n3, o3, string(b3)) 51 | 52 | // There is no built-in rewind, but Seek(0, 0) accomplishes this. 53 | _, err = f.Seek(0, 0) 54 | check(err) 55 | 56 | // The bufio package implements a buffered reader that may be useful both for its efficiency with many small reads and because of the additional reading methods it provides. 57 | r4 := bufio.NewReader(f) 58 | b4, err := r4.Peek(5) 59 | check(err) 60 | fmt.Printf("5 bytes: %s\n", string(b4)) 61 | 62 | // Close the file when you're done (usually this would be scheduled immediately after Opening with defer). 63 | f.Close() 64 | } 65 | 66 | // https://gobyexample.com/writing-files 67 | func writing() { 68 | fmt.Println("=== file.writing ===") 69 | 70 | // Here’s how to dump a string (or just bytes) into a file. 71 | f1 := []byte("Hi\nMom\nand Dad.\nI'm on TV!") 72 | err := ioutil.WriteFile("/tmp/dat", f1, 0644) 73 | check(err) 74 | 75 | // For more granular writes, open a file for writing. 76 | f, err := os.Create("/tmp/dat") 77 | // It’s idiomatic to defer a Close immediately after opening a file. 78 | defer f.Close() 79 | 80 | // You can Write byte slices as you'd expect. 81 | d2 := []byte{115, 111, 109, 101, 10} 82 | n2, err := f.Write(d2) 83 | check(err) 84 | fmt.Printf("wrote %d bytes\n", n2) 85 | 86 | // A WriteString is also available. 87 | n3, err := f.WriteString("writes\n") 88 | fmt.Printf("wrote %d bytes\n", n3) 89 | 90 | // Issue a Sync to flush writes to stable storage. 91 | f.Sync() 92 | 93 | // bufio provides buffered writers in addition to buffered readers 94 | w := bufio.NewWriter(f) 95 | n4, err := w.WriteString("buffered\n") 96 | fmt.Printf("wrote %d bytes\n", n4) 97 | 98 | // Use Flush to ensure all buffered operations have been applied to the underlying writer. 99 | w.Flush() 100 | } 101 | 102 | // deleting show how to delete a file 103 | func deleting() { 104 | fmt.Println("=== file.deleting ===") 105 | err := os.Remove("/tmp/dat") 106 | check(err) 107 | } 108 | 109 | func check(e error) { 110 | if e != nil { 111 | panic(e) 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /flags.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "flag" 4 | import "fmt" 5 | import "os" 6 | 7 | func main() { 8 | fmt.Println("--- flag ---") 9 | var port int 10 | var hostPtr = flag.String("host", "localhost", "database host") 11 | flag.IntVar(&port, "port", 5432, "database port") 12 | flag.Parse() 13 | fmt.Printf("host = %s\n", *hostPtr) 14 | fmt.Printf("port = %d\n", port) 15 | fmt.Println("all args:", os.Args) 16 | fmt.Println("remaining args:", flag.Args()) 17 | fmt.Printf("remaining args: %v\n", flag.Args()) 18 | fmt.Println() 19 | } 20 | 21 | func usage() { 22 | fmt.Println("--- usage ---") 23 | fmt.Fprintf(os.Stderr, "usage: %s [-bloviate ] [-port ]\n", os.Args[0]) 24 | flag.PrintDefaults() 25 | os.Exit(2) 26 | } 27 | -------------------------------------------------------------------------------- /git/.gitignore: -------------------------------------------------------------------------------- 1 | git 2 | -------------------------------------------------------------------------------- /git/README.md: -------------------------------------------------------------------------------- 1 | # git 2 | 3 | This example uses the go-git library to query the commit messages (in conventional commit format) since the last commit to determine how to bump up the semantic version tag. i.e. by major, minor, or patch level 4 | -------------------------------------------------------------------------------- /git/create-test-git.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | echo "mkdir test" 4 | mkdir test || exit 1 5 | echo "cd to test" 6 | cd test 7 | git init 8 | 9 | echo "file1" > file1 10 | git add file1 11 | git commit -m "feat: file1" 12 | git tag 0.0.1 13 | 14 | echo "file2" > file2 15 | git add file2 16 | git commit -m "fix: file2" 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /git/git.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "regexp" 7 | "sort" 8 | 9 | "github.com/Masterminds/semver" 10 | git "github.com/go-git/go-git/v5" 11 | "github.com/go-git/go-git/v5/plumbing" 12 | ) 13 | 14 | var semverRegex = regexp.MustCompile(`.*/([a-z]?)((\d+)\.(\d+)\.(\d+)).*`) 15 | 16 | // Git is a wrapper around a repository 17 | type Git struct { 18 | repository git.Repository 19 | } 20 | 21 | func main() { 22 | g, err := NewAt("./test") 23 | if err != nil { 24 | panic(err) 25 | } 26 | 27 | tags, err := g.Tags() 28 | if err != nil { 29 | panic(err) 30 | } 31 | fmt.Printf("%v\n", tags) 32 | } 33 | 34 | func New() (*Git, error) { 35 | dir, err := os.Getwd() 36 | if err != nil { 37 | return nil, err 38 | } 39 | return NewAt(dir) 40 | } 41 | 42 | func NewAt(dirPath string) (*Git, error) { 43 | 44 | opt := &git.PlainOpenOptions{DetectDotGit: true} 45 | repo, err := git.PlainOpenWithOptions(dirPath, opt) 46 | if err != nil { 47 | return nil, err 48 | } 49 | 50 | git := &Git{ 51 | repository: *repo, 52 | } 53 | return git, nil 54 | } 55 | 56 | func IsGit() bool { 57 | _, err := New() 58 | return err == nil 59 | } 60 | 61 | func (g *Git) Revision(longSha bool) (string, error) { 62 | h, err := g.CurrentCommit() 63 | if longSha || err != nil { 64 | return h, err 65 | } 66 | return h[:7], err 67 | } 68 | 69 | func (g *Git) IsDirty() bool { 70 | w, err := g.repository.Worktree() 71 | if err != nil { 72 | return true 73 | } 74 | 75 | status, err := w.Status() 76 | if err != nil { 77 | return true 78 | } 79 | 80 | return !status.IsClean() 81 | 82 | // res, _ := oneliner("git", "status", "--porcelain") 83 | // return len(res) > 0 84 | } 85 | 86 | func (g *Git) Branches() ([]string, error) { 87 | var currentBranchesNames []string 88 | 89 | branchRefs, err := g.repository.Branches() 90 | if err != nil { 91 | return currentBranchesNames, err 92 | } 93 | 94 | headRef, err := g.repository.Head() 95 | if err != nil { 96 | return currentBranchesNames, err 97 | } 98 | 99 | err = branchRefs.ForEach(func(branchRef *plumbing.Reference) error { 100 | if branchRef.Hash() == headRef.Hash() { 101 | currentBranchesNames = append(currentBranchesNames, branchRef.Name().Short()) 102 | 103 | return nil 104 | } 105 | 106 | return nil 107 | }) 108 | if err != nil { 109 | return currentBranchesNames, err 110 | } 111 | 112 | return currentBranchesNames, nil 113 | } 114 | 115 | func (g *Git) CurrentCommit() (string, error) { 116 | headRef, err := g.repository.Head() 117 | if err != nil { 118 | return "", err 119 | } 120 | headSha := headRef.Hash().String() 121 | 122 | return headSha, nil 123 | } 124 | 125 | func (g *Git) Tags() ([]string, error) { 126 | tags, _, err := g.tags() 127 | return tags, err 128 | } 129 | 130 | func (g *Git) LatestTag() (string, error) { 131 | _, tag, err := g.tags() 132 | return tag, err 133 | } 134 | 135 | // tags returns a list of tags sorted with the largest first 136 | func (g *Git) tags() ([]string, string, error) { 137 | var latestTagName string 138 | var tags []string 139 | var semvers semver.Collection 140 | 141 | tagRefs, err := g.repository.Tags() 142 | if err != nil { 143 | return tags, latestTagName, err 144 | } 145 | 146 | var prefix string 147 | err = tagRefs.ForEach(func(tagRef *plumbing.Reference) error { 148 | tag := tagRef.Name().String() 149 | if semverRegex.MatchString(tag) { 150 | if prefix == "" { 151 | prefix = semverRegex.ReplaceAllString(tag, `$1`) 152 | } 153 | semverPart := semverRegex.ReplaceAllString(tag, `$2`) 154 | //fmt.Println(semverPart) 155 | version, err := semver.NewVersion(semverPart) 156 | if err != nil { 157 | fmt.Println("Invalid semver tag:", tag) 158 | return err 159 | } 160 | //fmt.Println("Adding version:", version) 161 | semvers = append(semvers, version) 162 | } 163 | 164 | return nil 165 | }) 166 | 167 | if err != nil { 168 | return tags, latestTagName, err 169 | } 170 | 171 | sort.Sort(sort.Reverse(semvers)) 172 | 173 | // Map the Version objects to strings 174 | tags = make([]string, len(semvers)) 175 | for i, v := range semvers { 176 | if i == 0 { 177 | latestTagName = prefix + v.String() 178 | } 179 | tags[i] = prefix + v.String() 180 | } 181 | 182 | //fmt.Println(tags, latestTagName) 183 | return tags, latestTagName, nil 184 | } 185 | 186 | func (g *Git) CreateTag(version string) error { 187 | head, err := g.repository.Head() 188 | if err != nil { 189 | return err 190 | } 191 | 192 | _, err = g.repository.CreateTag(version, head.Hash(), nil) 193 | return err 194 | 195 | } 196 | -------------------------------------------------------------------------------- /git/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/joncrlsn/go-examples/git 2 | 3 | go 1.16 4 | 5 | require ( 6 | github.com/Masterminds/semver v1.5.0 7 | github.com/go-git/go-git/v5 v5.2.0 8 | golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83 // indirect 9 | golang.org/x/text v0.3.5 // indirect 10 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect 11 | ) 12 | -------------------------------------------------------------------------------- /git/go.sum: -------------------------------------------------------------------------------- 1 | github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= 2 | github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= 3 | github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7 h1:uSoVVbwJiQipAclBbw+8quDsfcvFjOpI5iCf4p/cqCs= 4 | github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs= 5 | github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA= 6 | github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= 7 | github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= 8 | github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= 9 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 10 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 11 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 12 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 13 | github.com/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg= 14 | github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o= 15 | github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= 16 | github.com/gliderlabs/ssh v0.2.2 h1:6zsha5zo/TWhRhwqCD3+EarCAgZ2yN28ipRnGPnwkI0= 17 | github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= 18 | github.com/go-git/gcfg v1.5.0 h1:Q5ViNfGF8zFgyJWPqYwA7qGFoMTEiBmdlkcfRmpIMa4= 19 | github.com/go-git/gcfg v1.5.0/go.mod h1:5m20vg6GwYabIxaOonVkTdrILxQMpEShl1xiMF4ua+E= 20 | github.com/go-git/go-billy/v5 v5.0.0 h1:7NQHvd9FVid8VL4qVUMm8XifBK+2xCoZ2lSk0agRrHM= 21 | github.com/go-git/go-billy/v5 v5.0.0/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0= 22 | github.com/go-git/go-git-fixtures/v4 v4.0.2-0.20200613231340-f56387b50c12 h1:PbKy9zOy4aAKrJ5pibIRpVO2BXnK1Tlcg+caKI7Ox5M= 23 | github.com/go-git/go-git-fixtures/v4 v4.0.2-0.20200613231340-f56387b50c12/go.mod h1:m+ICp2rF3jDhFgEZ/8yziagdT1C+ZpZcrJjappBCDSw= 24 | github.com/go-git/go-git/v5 v5.2.0 h1:YPBLG/3UK1we1ohRkncLjaXWLW+HKp5QNM/jTli2JgI= 25 | github.com/go-git/go-git/v5 v5.2.0/go.mod h1:kh02eMX+wdqqxgNMEyq8YgwlIOsDOa9homkUq1PoTMs= 26 | github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY= 27 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 28 | github.com/imdario/mergo v0.3.9 h1:UauaLniWCFHWd+Jp9oCEkTBj8VO/9DKg3PV3VCNMDIg= 29 | github.com/imdario/mergo v0.3.9/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= 30 | github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= 31 | github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= 32 | github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= 33 | github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd h1:Coekwdh0v2wtGp9Gmz1Ze3eVRAWJMLokvN3QjdzCHLY= 34 | github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= 35 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 36 | github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= 37 | github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= 38 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 39 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 40 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 41 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 42 | github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= 43 | github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= 44 | github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= 45 | github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= 46 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 47 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 48 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 49 | github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= 50 | github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= 51 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 52 | github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= 53 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 54 | github.com/xanzy/ssh-agent v0.2.1 h1:TCbipTQL2JiiCprBWx9frJ2eJlCYT00NmctrHxVAr70= 55 | github.com/xanzy/ssh-agent v0.2.1/go.mod h1:mLlQY/MoOhWBj+gOGMQkOeiEvkx+8pJSI+0Bx9h2kr4= 56 | golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 57 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 58 | golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 59 | golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83 h1:/ZScEX8SfEmUGRHs0gxpqteO5nfNW6axyZbBdw9A12g= 60 | golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= 61 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 62 | golang.org/x/net v0.0.0-20200301022130-244492dfa37a h1:GuSPYbZzB5/dcLNCwLQLsg3obCJtX9IJhpXkvY7kzk0= 63 | golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 64 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 65 | golang.org/x/sys v0.0.0-20190221075227-b4e8571b14e0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 66 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 67 | golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 68 | golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527 h1:uYVVQ9WP/Ds2ROhcaGPeIdVq0RIXVLwsHlnvJ+cT1So= 69 | golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 70 | golang.org/x/term v0.0.0-20201117132131-f5c789dd3221 h1:/ZHdbVpdR/jk3g30/d4yUL0JU9kksj8+F/bnQUVLGDM= 71 | golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= 72 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 73 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 74 | golang.org/x/text v0.3.5 h1:i6eZZ+zk0SOf0xgBpEpPD18qWcJda6q1sxt3S0kzyUQ= 75 | golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 76 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 77 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 78 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 79 | gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 80 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 81 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 82 | gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= 83 | gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= 84 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 85 | gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I= 86 | gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 87 | -------------------------------------------------------------------------------- /git/util.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | // dirExists returns true or false 4 | func dirExists(path string) bool { 5 | fileInfo, err := os.Stat(path) 6 | if err == nil { 7 | return fileInfo.IsDir() 8 | } 9 | return false 10 | } 11 | 12 | // fileExists returns true or false 13 | func fileExists(path string) bool { 14 | fileInfo, err := os.Stat(path) 15 | if err == nil { 16 | return !fileInfo.IsDir() 17 | } 18 | return false 19 | } 20 | -------------------------------------------------------------------------------- /go-flags.go: -------------------------------------------------------------------------------- 1 | // 2 | // Gives an example of basic usages for the go-flags package: 3 | // version, help, and verbose flags 4 | // 5 | // go run go-flags.go -? 6 | // go run go-flags.go --version 7 | // go run go-flags.go --verbose 8 | // go run go-flags.go -v -- -hi mom 9 | // 10 | package main 11 | 12 | import ( 13 | "fmt" 14 | "github.com/jessevdk/go-flags" 15 | "os" 16 | ) 17 | 18 | const ( 19 | version = "0.1" 20 | ) 21 | 22 | // The main options struct 23 | type Options struct { 24 | Verbose []bool `short:"v" long:"verbose" description:"verbose output"` 25 | help HelpOptions `group:"Help Options"` 26 | // Ini string `short:"f" long:"file" description:"ini file name"` 27 | 28 | // Example of a required flag 29 | Name string `short:"n" long:"name" description:"a name is required" required:"true"` 30 | } 31 | 32 | // This is a sub-struct of the Options struct. It holds the 2 common help options (help and version) 33 | type HelpOptions struct { 34 | Help bool `short:"?" long:"help" description:"help text" default:false` 35 | Version bool `short:"V" long:"version" description:"show version info" default:false` 36 | } 37 | 38 | var ( 39 | options Options 40 | parser = flags.NewParser(&options, flags.PrintErrors|flags.PassDoubleDash) 41 | ) 42 | 43 | // init parses the flags that are passed in 44 | func init() { 45 | parser.Usage = "[Options] filename" 46 | 47 | if false { 48 | // Parse an ini file instead of the command-line option flags 49 | iniParser := flags.NewIniParser(parser) 50 | err := iniParser.ParseFile("go-flags.ini") 51 | if err != nil { 52 | fmt.Println("error parsing ini file: ", err) 53 | os.Exit(1) 54 | } 55 | } else { 56 | // Parse the command-line options 57 | args, err := parser.Parse() 58 | if err != nil { 59 | // errors are already written to system err 60 | os.Exit(1) 61 | } 62 | fmt.Printf("The remaining args are: %v\n", args) 63 | 64 | // Write out an ini file using the given command-line option flags 65 | iniParser := flags.NewIniParser(parser) 66 | iniParser.WriteFile("go-flags.ini", flags.IniIncludeDefaults|flags.IniCommentDefaults|flags.IniIncludeComments) 67 | } 68 | } 69 | 70 | // main function 71 | func main() { 72 | 73 | // Write out the help 74 | if options.help.Help { 75 | parser.WriteHelp(os.Stderr) 76 | os.Exit(0) 77 | } 78 | 79 | // Write out the version and copyright information 80 | if options.help.Version { 81 | fmt.Fprintf(os.Stderr, "%s version %s\n", os.Args[0], version) 82 | fmt.Fprintln(os.Stderr, "Copyright (c) 2015 Jon Carlson. All rights reserved.") 83 | fmt.Fprintln(os.Stderr, "Use of this source code is governed by the MIT license") 84 | fmt.Fprintln(os.Stderr, "that can be found here: http://opensource.org/licenses/MIT") 85 | os.Exit(0) 86 | } 87 | 88 | // Print the values of the flags 89 | fmt.Printf("Verbose: %v\n", options.Verbose) 90 | fmt.Printf("Help: %t\n", options.help.Help) 91 | fmt.Printf("Version: %v\n", options.help.Version) 92 | fmt.Printf("Name: %s\n", options.Name) 93 | 94 | // Write out an ini file that could be used in lieu of option flags 95 | iniParser := flags.NewIniParser(parser) 96 | iniParser.WriteFile("go-flags.ini", flags.IniIncludeDefaults|flags.IniCommentDefaults|flags.IniIncludeComments) 97 | } 98 | -------------------------------------------------------------------------------- /go-flags.ini: -------------------------------------------------------------------------------- 1 | [Application Options] 2 | ; verbose output 3 | Verbose = true 4 | 5 | ; a name is required 6 | Name = asdfasdf 7 | 8 | -------------------------------------------------------------------------------- /gob.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "encoding/gob" 6 | "fmt" 7 | "log" 8 | ) 9 | 10 | type I interface { 11 | Run() 12 | } 13 | 14 | type P struct { 15 | X, Y, Z int 16 | Name string 17 | } 18 | 19 | func (p P) Run() { 20 | } 21 | 22 | type Q struct { 23 | X, Y *int32 24 | Name string 25 | } 26 | 27 | func main() { 28 | // Initialize the encoder and decoder. Normally enc and dec would be 29 | // bound to network connections and the encoder and decoder would 30 | // run in different processes. 31 | var network bytes.Buffer // Stand-in for a network connection 32 | enc := gob.NewEncoder(&network) // Will write to network. 33 | dec := gob.NewDecoder(&network) // Will read from network. 34 | // Encode (send) the value. 35 | 36 | encode(enc, P{3, 4, 5, "Pythagoras"}) 37 | 38 | // Decode (receive) the value. 39 | var q Q 40 | err := dec.Decode(&q) 41 | if err != nil { 42 | log.Fatal("decode error:", err) 43 | } 44 | fmt.Printf("%q: {%d,%d}\n", q.Name, *q.X, *q.Y) 45 | } 46 | 47 | func encode(enc *gob.Encoder, obj I) { 48 | err := enc.Encode(obj) 49 | if err != nil { 50 | log.Fatal("encode error:", err) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /gpg.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "golang.org/x/crypto/openpgp" 6 | "io/ioutil" 7 | "log" 8 | "os" 9 | ) 10 | 11 | // create gpg keys with 12 | // $ gpg --gen-key 13 | // ensure you specify correct paths and passphrase 14 | 15 | func main() { 16 | 17 | mySecretString := "this is so very secret!" 18 | prefix, passphrase := "/home/jon/", "xxxxxxxxx" 19 | secretKeyring := prefix + ".gnupg/secring.gpg" 20 | publicKeyring := prefix + ".gnupg/pubring.gpg" 21 | 22 | if 1 == 1 { 23 | bytes, err := ioutil.ReadFile(prefix + ".password-store/jon/facebook:jon_carlson@writeme.com.gpg") 24 | if err != nil { 25 | log.Fatal(err) 26 | } 27 | decBytes, err := DecryptBytes(bytes, secretKeyring, passphrase) 28 | if err != nil { 29 | log.Fatal(err) 30 | } 31 | log.Println("decrypted stuff", string(decBytes)) 32 | } 33 | 34 | // Encrypt string 35 | encBytes, err := EncryptBytes([]byte(mySecretString), publicKeyring) 36 | if err != nil { 37 | log.Fatal(err) 38 | } 39 | 40 | // Decrypt string 41 | decBytes, err := DecryptBytes(encBytes, secretKeyring, passphrase) 42 | if err != nil { 43 | log.Fatal(err) 44 | } 45 | 46 | log.Println("Decrypted Secret:", string(decBytes)) 47 | } 48 | 49 | func EncryptBytes(origBytes []byte, pubKeyringFile string) ([]byte, error) { 50 | 51 | // Read in public key 52 | keyringFileBuffer, _ := os.Open(pubKeyringFile) 53 | defer keyringFileBuffer.Close() 54 | entityList, err := openpgp.ReadKeyRing(keyringFileBuffer) 55 | if err != nil { 56 | return nil, err 57 | } 58 | 59 | // encrypt string 60 | buf := new(bytes.Buffer) 61 | w, err := openpgp.Encrypt(buf, entityList, nil, nil, nil) 62 | if err != nil { 63 | return nil, err 64 | } 65 | _, err = w.Write(origBytes) 66 | if err != nil { 67 | return nil, err 68 | } 69 | err = w.Close() 70 | if err != nil { 71 | return nil, err 72 | } 73 | encryptedBytes, err := ioutil.ReadAll(buf) 74 | if err != nil { 75 | return nil, err 76 | } 77 | 78 | return encryptedBytes, nil 79 | } 80 | 81 | func DecryptBytes(encBytes []byte, privKeyringFile string, passphrase string) ([]byte, error) { 82 | 83 | // init some vars 84 | var entity *openpgp.Entity 85 | var entityList openpgp.EntityList 86 | 87 | // Open the private key file 88 | keyringFileBuffer, err := os.Open(privKeyringFile) 89 | if err != nil { 90 | return nil, err 91 | } 92 | defer keyringFileBuffer.Close() 93 | entityList, err = openpgp.ReadKeyRing(keyringFileBuffer) 94 | if err != nil { 95 | return nil, err 96 | } 97 | entity = entityList[0] 98 | 99 | // Get the passphrase and read the private key. 100 | // Have not touched the encrypted string yet 101 | passphraseByte := []byte(passphrase) 102 | log.Println("Decrypting private key using passphrase") 103 | entity.PrivateKey.Decrypt(passphraseByte) 104 | for _, subkey := range entity.Subkeys { 105 | subkey.PrivateKey.Decrypt(passphraseByte) 106 | } 107 | log.Println("Finished decrypting private key using passphrase") 108 | 109 | // Decrypt it with the contents of the private key 110 | md, err := openpgp.ReadMessage(bytes.NewBuffer(encBytes), entityList, nil, nil) 111 | if err != nil { 112 | return nil, err 113 | } 114 | bytes, err := ioutil.ReadAll(md.UnverifiedBody) 115 | if err != nil { 116 | return nil, err 117 | } 118 | 119 | return bytes, nil 120 | } 121 | -------------------------------------------------------------------------------- /guid.go: -------------------------------------------------------------------------------- 1 | // 2 | // guid is an example of a pseudo globally unique ID that can be stored in a 64-bit signed integer. 3 | // There is a small chance two people could generate the same GUID but it is very small. They 4 | // would have to generate the same random value out of 16 million possible in the same 100th 5 | // of a second. 6 | // 7 | package main 8 | 9 | import ( 10 | "fmt" 11 | //"log" 12 | "math/rand" 13 | "time" // or "runtime" 14 | ) 15 | 16 | // max24Bits is the max number that can fit in 24 bits (2^24) 17 | var max24Bits = 16777216 // 16 million 18 | var max39Bits = 549755813888 // 549 billion 19 | 20 | // janFirst2015 is the 100ths of a second between Jan 1, 1970 and Jan 1, 2015 21 | var janFirst2015 = time.Date(2015, time.January, 1, 0, 0, 0, 0, time.UTC).UnixNano() / 1000 / 1000 / 10 22 | 23 | func main() { 24 | rand.Seed(time.Now().UTC().UnixNano()) 25 | fmt.Println("=== guid ===") 26 | 27 | now := time.Now().UTC().UnixNano() 28 | fmt.Printf("time.Now.UnixNano():\n%v\n%b\n", now, now) 29 | 30 | Guid() 31 | } 32 | 33 | // Guid returns an pseudo globally unique identifier as an int64 34 | // The first 5 bytes are the time portion (100ths of a second since Jan 1, 2015) 35 | // The last 3 bytes are a random number 36 | func Guid() int64 { 37 | // now is 100ths of a second since Jan 1, 1970 38 | now := time.Now().UTC().UnixNano() / 1000 / 1000 / 10 39 | fmt.Printf("== now 100ths:\n%v\n%b\n", now, now) 40 | 41 | // subtract now from Jan 1,2015 42 | timePortion := now - janFirst2015 43 | fmt.Printf("== now - jan2015 100ths:\n%v\n%b\n", timePortion, timePortion) 44 | 45 | // bitshift time portion by 24 bits to the left (we are only losing zero bits) 46 | timePortion = timePortion << 24 47 | fmt.Printf("== time shifted 24 bits:\n%v\n%b\n", timePortion, timePortion) 48 | 49 | random24 := int64(rand.Intn(max24Bits)) 50 | fmt.Printf("== random 24 bits:\n%v\n%b\n", random24, random24) 51 | 52 | // bit or the time portion and the random24 portion 53 | guid := timePortion | random24 54 | 55 | fmt.Printf("== guid:\n%v\n%b\n", guid, guid) 56 | return guid 57 | } 58 | -------------------------------------------------------------------------------- /hashpassword.go: -------------------------------------------------------------------------------- 1 | // 2 | // hashpassword.go wraps the golang bcrypt password hashing library 3 | // with base64 encoding so we can deal in strings 4 | // 5 | // bcrypt is based on this paper: 6 | // https://www.usenix.org/legacy/event/usenix99/provos/provos.pdf 7 | // 8 | package main 9 | 10 | import ( 11 | "encoding/base64" 12 | "fmt" 13 | "golang.org/x/crypto/bcrypt" 14 | ) 15 | 16 | const ( 17 | secretPassword = "hello, my name is inigo montoya" 18 | ) 19 | 20 | func main() { 21 | // Hash the real password 22 | hashBase64, _ := HashPassword(secretPassword) 23 | fmt.Println("hashBase64:", hashBase64) 24 | 25 | // Compare with an invalid password 26 | fmt.Printf("Compare should be false: %t\n", ComparePassword(hashBase64, "some invalid password")) 27 | 28 | // Compare with the right password 29 | fmt.Printf("Compare should be true: %t\n", ComparePassword(hashBase64, secretPassword)) 30 | } 31 | 32 | // HashPassword hashes the clear-text password and encodes it as base64, 33 | func HashPassword(password string) (string, error) { 34 | hashedBytes, err := bcrypt.GenerateFromPassword([]byte(password), 10 /*cost*/) 35 | if err != nil { 36 | return "", err 37 | } 38 | 39 | // Encode the entire thing as base64 and return 40 | hashBase64 := base64.StdEncoding.EncodeToString(hashedBytes) 41 | 42 | return hashBase64, nil 43 | } 44 | 45 | // ComparePassword hashes the test password and then compares 46 | // the two hashes. 47 | func ComparePassword(hashBase64, testPassword string) bool { 48 | 49 | // Decode the real hashed and salted password so we can 50 | // split out the salt 51 | hashBytes, err := base64.StdEncoding.DecodeString(hashBase64) 52 | if err != nil { 53 | fmt.Println("Error, we were given invalid base64 string", err) 54 | return false 55 | } 56 | 57 | err = bcrypt.CompareHashAndPassword(hashBytes, []byte(testPassword)) 58 | return err == nil 59 | } 60 | -------------------------------------------------------------------------------- /hashsalt-custom.go: -------------------------------------------------------------------------------- 1 | // 2 | // hashsalt provides methods to hash a password with a random salt and then 3 | // put the two together so the hash-salt can be compared with an entered password. 4 | // 5 | package main 6 | 7 | import ( 8 | "crypto/rand" 9 | "crypto/sha256" 10 | "encoding/base64" 11 | "fmt" 12 | "io" 13 | "os" 14 | ) 15 | 16 | const ( 17 | saltSize = 32 18 | secretPassword = "hello, my name is inigo montoya" 19 | ) 20 | 21 | func main() { 22 | // Hash and salt the real password 23 | hashSaltBase64, _ := HashAndSaltPassword(secretPassword) 24 | fmt.Println("hashSaltBase64:", hashSaltBase64) 25 | 26 | // Compare with an invalid password 27 | fmt.Printf("Compare should be false: %t\n", ComparePassword("some invalid password", hashSaltBase64)) 28 | 29 | // Compare with the right password 30 | fmt.Printf("Compare should be true: %t\n", ComparePassword(secretPassword, hashSaltBase64)) 31 | } 32 | 33 | // makeSalt generates a random salt 34 | func makeSalt() ([]byte, error) { 35 | salt := make([]byte, saltSize, saltSize) 36 | _, err := io.ReadFull(rand.Reader, salt) 37 | if err != nil { 38 | return nil, err 39 | } 40 | return salt, nil 41 | } 42 | 43 | // HashAndSaltPassword generates a random salt, then hashes the password 44 | // and the salt together, then appends the salt to the end of the hash and 45 | // returns both as a base64 encoded string. 46 | func HashAndSaltPassword(password string) (string, error) { 47 | salt, err := makeSalt() 48 | if err != nil { 49 | return "", err 50 | } 51 | 52 | hashSaltBase64 := HashPassword(password, salt) 53 | return hashSaltBase64, nil 54 | } 55 | 56 | // HashPassword hashes the clear-text password and the given salt together, 57 | // then appends the salt to the end of the hash and returns both as a 58 | // base64 encoded string. 59 | func HashPassword(password string, salt []byte) string { 60 | hash := sha256.New() 61 | 62 | // Hash both the password and the salt together 63 | hash.Write([]byte(password)) 64 | hash.Write(salt) 65 | hashBytes := hash.Sum(nil) 66 | 67 | // Append the salt bytes to the end of the hashed bytes 68 | hashSaltBytes := append(hashBytes, salt...) 69 | 70 | // Encode the entire thing as base64 and return 71 | hashSaltBase64 := base64.StdEncoding.EncodeToString(hashSaltBytes) 72 | 73 | return hashSaltBase64 74 | } 75 | 76 | // ComparePassword hashes the test password with the same salt used for the real 77 | // password and then compares the two hashes. 78 | func ComparePassword(testPassword string, hashSaltBase64 string) bool { 79 | 80 | // Decode the real hashed and salted password so we can 81 | // split out the salt 82 | bytes, err := base64.StdEncoding.DecodeString(hashSaltBase64) 83 | if err != nil { 84 | fmt.Println("Error, given invalid base64 string", err) 85 | os.Exit(1) 86 | } 87 | 88 | // Split out the salt 89 | salt := bytes[len(bytes)-saltSize:] 90 | 91 | // Hash the test password with the same salt used to hash the real password 92 | testHashSaltBase64 := HashPassword(testPassword, salt) 93 | 94 | // Return true if the hashes match 95 | return hashSaltBase64 == testHashSaltBase64 96 | } 97 | -------------------------------------------------------------------------------- /http-cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIC+zCCAeWgAwIBAgIQVNoXWWSi8LciDrbiFWwp8DALBgkqhkiG9w0BAQswEjEQ 3 | MA4GA1UEChMHQWNtZSBDbzAeFw0xNTAzMDcxNzMyMjhaFw0xNjAzMDYxNzMyMjha 4 | MBIxEDAOBgNVBAoTB0FjbWUgQ28wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK 5 | AoIBAQC4vrC4fkp0ufl0Fb3TA16A+vfoqkDkcFMWI4kV4JhRANVg6sQAFyteVVSV 6 | 7oYzKo/ia7voYrL+ycXEm1jckaYN6HXTm2xZHFCMSaV8oU8kokyWJTDNSET1WNEE 7 | lPM9quwB+P59WJiUpwj/FbI4bmxn8ULDo1LzwXBQ+7WQSvL0OUs2up2hfqR93lcF 8 | 2t4oLQCqZ7ETsf47ic+jj6DSHzaFC654rmXYTVkX1aZdRUjfxSeFSHMjvRsKOCFC 9 | N/shorbVLZa2wqlSTDogJ4+AjF4xeGS7lmc6ZSa9yOfrtWRawzDsrcqeaWtA7PUz 10 | /BNWNEPX18Cp9Z/yor+VnqJTGl95AgMBAAGjUTBPMA4GA1UdDwEB/wQEAwIAoDAT 11 | BgNVHSUEDDAKBggrBgEFBQcDATAMBgNVHRMBAf8EAjAAMBoGA1UdEQQTMBGCCWxv 12 | Y2FsaG9zdIcEfwAAATALBgkqhkiG9w0BAQsDggEBAEl7Z/7OgyjJtu4uDPk1aLDR 13 | B7iUft4cQt7b0v/9taWNGcorQFw9YLS+jPsmCWcK0zPW6X71U11LTQBJrYW3SQMP 14 | 5DJI+HNlly9j6TLewj8pkEL3/BD78/XX+oEjj6R5ft+shDcX3pkt9B8fDJ+2sreR 15 | jNhoPeSTY1X4ckoSNY8LIMHjrTJFN07ym91obCfqsg7maLp2bj+qGvJ3aQmRbxWd 16 | p3hzL7zIwnj2NWJtsrkHkzJ20Ty3wGtzesrgM09DS4MK8HxzKroTEiaVBUHemXpA 17 | kBqyO97s2OFZFieaxdz89nNLCIOXXD3peH6U02EjbOysiWrabFwtcAmCgdmF8RI= 18 | -----END CERTIFICATE----- 19 | -------------------------------------------------------------------------------- /http-download-with-progress.go: -------------------------------------------------------------------------------- 1 | // https://golangcode.com/download-a-file-with-progress/ 2 | package main 3 | 4 | import ( 5 | "fmt" 6 | "io" 7 | "net/http" 8 | "os" 9 | "strings" 10 | 11 | "github.com/dustin/go-humanize" 12 | ) 13 | 14 | // WriteCounter counts the number of bytes written to it. It implements to the io.Writer interface 15 | // and we can pass this into io.TeeReader() which will report progress on each write cycle. 16 | type WriteCounter struct { 17 | Total uint64 18 | } 19 | 20 | func (wc *WriteCounter) Write(p []byte) (int, error) { 21 | n := len(p) 22 | wc.Total += uint64(n) 23 | wc.PrintProgress() 24 | return n, nil 25 | } 26 | 27 | func (wc WriteCounter) PrintProgress() { 28 | // Clear the line by using a character return to go back to the start and remove 29 | // the remaining characters by filling it with spaces 30 | fmt.Printf("\r%s", strings.Repeat(" ", 35)) 31 | 32 | // Return again and print current status of download 33 | // We use the humanize package to print the bytes in a meaningful way (e.g. 10 MB) 34 | fmt.Printf("\rDownloading... %s complete", humanize.Bytes(wc.Total)) 35 | } 36 | 37 | func main() { 38 | fmt.Println("Download Started") 39 | 40 | fileUrl := "https://upload.wikimedia.org/wikipedia/commons/d/d6/Wp-w4-big.jpg" 41 | err := DownloadFile("avatar.jpg", fileUrl) 42 | if err != nil { 43 | panic(err) 44 | } 45 | 46 | fmt.Println("Download Finished") 47 | } 48 | 49 | // DownloadFile will download a url to a local file. It's efficient because it will 50 | // write as it downloads and not load the whole file into memory. We pass an io.TeeReader 51 | // into Copy() to report progress on the download. 52 | func DownloadFile(filepath string, url string) error { 53 | 54 | // Create the file, but give it a tmp file extension, this means we won't overwrite a 55 | // file until it's downloaded, but we'll remove the tmp extension once downloaded. 56 | out, err := os.Create(filepath + ".tmp") 57 | if err != nil { 58 | return err 59 | } 60 | 61 | // Get the data 62 | resp, err := http.Get(url) 63 | if err != nil { 64 | out.Close() 65 | return err 66 | } 67 | defer resp.Body.Close() 68 | 69 | // Create our progress reporter and pass it to be used alongside our writer 70 | counter := &WriteCounter{} 71 | if _, err = io.Copy(out, io.TeeReader(resp.Body, counter)); err != nil { 72 | out.Close() 73 | return err 74 | } 75 | 76 | // The progress use the same line so print a new line once it's finished downloading 77 | fmt.Print("\n") 78 | 79 | // Close the file without defer so it can happen before Rename() 80 | out.Close() 81 | 82 | if err = os.Rename(filepath+".tmp", filepath); err != nil { 83 | return err 84 | } 85 | return nil 86 | } 87 | -------------------------------------------------------------------------------- /http-download.go: -------------------------------------------------------------------------------- 1 | // https://golangcode.com/download-a-file-from-a-url/ 2 | package main 3 | 4 | import ( 5 | "io" 6 | "net/http" 7 | "os" 8 | ) 9 | 10 | func main() { 11 | fileUrl := "https://golangcode.com/images/avatar.jpg" 12 | 13 | if err := DownloadFile("avatar.jpg", fileUrl); err != nil { 14 | panic(err) 15 | } 16 | } 17 | 18 | // DownloadFile will download a url to a local file. It's efficient because it will 19 | // write as it downloads and not load the whole file into memory. 20 | func DownloadFile(filepath string, url string) error { 21 | 22 | // Get the data 23 | resp, err := http.Get(url) 24 | if err != nil { 25 | return err 26 | } 27 | defer resp.Body.Close() 28 | 29 | // Create the file 30 | out, err := os.Create(filepath) 31 | if err != nil { 32 | return err 33 | } 34 | defer out.Close() 35 | 36 | // Write the body to file 37 | _, err = io.Copy(out, resp.Body) 38 | return err 39 | } 40 | -------------------------------------------------------------------------------- /http-key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEowIBAAKCAQEAuL6wuH5KdLn5dBW90wNegPr36KpA5HBTFiOJFeCYUQDVYOrE 3 | ABcrXlVUle6GMyqP4mu76GKy/snFxJtY3JGmDeh105tsWRxQjEmlfKFPJKJMliUw 4 | zUhE9VjRBJTzParsAfj+fViYlKcI/xWyOG5sZ/FCw6NS88FwUPu1kEry9DlLNrqd 5 | oX6kfd5XBdreKC0AqmexE7H+O4nPo4+g0h82hQuueK5l2E1ZF9WmXUVI38UnhUhz 6 | I70bCjghQjf7IaK21S2WtsKpUkw6ICePgIxeMXhku5ZnOmUmvcjn67VkWsMw7K3K 7 | nmlrQOz1M/wTVjRD19fAqfWf8qK/lZ6iUxpfeQIDAQABAoIBADA6r/3qElv4tRPG 8 | HUE6LvCzFAcsczZv0HEGI+KPOJRlCE992l8/rTW6RxPBKk2vPdLZVzvqkFoNqNCT 9 | 0ZX7fANDfYcZmyaESs7k5wvrPLMOn7nOybe9tyrp3d85V2rw9R3qt91XRLYCCUo2 10 | islKoohcJpbWS3CRPlYV8CdUOarh3FviDEiSMQdBxi0HbUyok7i1Q7e7DghSaOcY 11 | 3C0zYaU+QXsCS+jCM1uN73YlQo7ZRHgGEZ0IRyFjZBgD4VL/pufxFo2wh6AZTqeX 12 | oeyOSN3BeMCEWiZ/7O1ZYB3HN7a6A7+Jp2s5mp2nasB5ZCrcTFgJfNaXQIepNL2C 13 | ycDpZXUCgYEA6Qxf4iRZbWQK5SEjpd6ySyHSFoy4silIyUJFa2v6gKRxqWdPJoGG 14 | BNkKQ9hQLNno5tWGc3XeSEd+LcqhdtrPYDMr8huNYmyRqhm6EQzqMeRRjzvwvq+h 15 | SmU94rUPJ2LxivkMlDEjAnpDTd/92BO8Pq+aHkPjFPdotfXsW2+TOLMCgYEAyvB8 16 | osLKXdkZdvgCQPqr8j+nzpz1K8LDuOywCrlUJSWgfObSKeGh72JXhuhrgBAoTeT5 17 | Bc/8xsubRRk7fg3NZkpDnzU/8NYfnFN7dijQ4YBVrNg5z5Uacc5XYGFJ4T8ThdFu 18 | +5wMdd1cZOjTpwiV3jf/y2z/t8BpW/QvFBB5ZSMCgYB0+kEmwhghUJYnbPr3x1C3 19 | ZcKOjxQmWZKvcxRlV03mRKTI45JXQayDwm3koC+eU8MUGxnh2sg4f6pWUd+6SRrO 20 | ruDgJfRR8y0qtL8wz8q+QFywCOZO0Nmk/iySH0/79S5JE7m8qO1p0PT3ofdI/p7+ 21 | 2CYECw9w9r8YltiNDPRZAQKBgBKvRapuqNcvL+sGNp7o9X/eYILjbKdUzVRvZl32 22 | ZkrtRfN6BQuaHKC/uzNOnucxlaRmWo8wcSOUh307L4ERoEG0wLkd2/8+l7Z68TkV 23 | 1PWOLVU2q0vzfHXR+7WB/51fg1qkx8Uevv7zHJil3ybX4YSOP1zpcjnm20x+FP+R 24 | 7LtlAoGBAM+DB4t87yKipJGBUBRQ5UohH81IySO+4fdsIq0wzpTB3LjTnWiDvPlf 25 | AREc8uI4Cn4nINRW0rXyz8C0uPSjvtUHiy0EBbaOHr/zYWs1ojugCalfdNwyH+PV 26 | uAM6j0OFHuO2ECGWRNXIRAftmiZWBZYFGieCk5FKPctDkR/3qRcm 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /http-server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "math/rand" 7 | "net/http" 8 | "strings" 9 | "time" 10 | ) 11 | 12 | // randomHandler responds in a random amount of time 13 | func randomHandler(w http.ResponseWriter, r *http.Request) { 14 | if r.Method == "GET" { 15 | duration := time.Duration(rand.Float64()*30) * time.Second 16 | time.Sleep(duration) 17 | fmt.Fprintf(w, "Responding in %s", duration) 18 | } 19 | } 20 | 21 | // errorHandler responds with a 500 Internal Server Error 22 | func errorHandler(w http.ResponseWriter, r *http.Request) { 23 | if r.Method == "GET" { 24 | http.Error(w, "500 Internal Server Error", http.StatusInternalServerError) 25 | } 26 | } 27 | 28 | // notFoundHandler responds with 404 Not Found 29 | func notFoundHandler(w http.ResponseWriter, r *http.Request) { 30 | if r.Method == "GET" { 31 | http.Error(w, "404 Not Found", http.StatusNotFound) 32 | } 33 | } 34 | 35 | // infoHandler returns 200 and info about the request 36 | func infoHandler(w http.ResponseWriter, r *http.Request) { 37 | if r.Method == "GET" { 38 | //fmt.Fprintf(w, "Hello, %q", html.EscapeString(r.URL.Path)) 39 | fmt.Fprintf(w, ` 40 | r.Method = %s
41 | r.RequestURI = %s
42 | r.Host = %s
43 | r.Header = %v
44 | r.RemoteAddr = %s
45 | r.MultipartForm = %v
46 | r.PostForm = %v
47 | r.Form = %v
48 |
49 | r.URL = %s
50 | r.URL.Host = %s
51 | r.URL.Path = %s
52 | r.URL.RawQuery = %s
53 | r.URL.Scheme = %s
54 | r.URL.Opaque = %s
55 | r.URL.User = %s
56 | r.URL.Fragment = %s
57 | `, r.Method, r.RequestURI, r.Host, r.Header, r.RemoteAddr, r.MultipartForm, r.PostForm, r.Form, r.URL, r.URL.Host, r.URL.Path, r.URL.RawQuery, r.URL.Scheme, r.URL.Opaque, r.URL.User, r.URL.Fragment) 58 | } 59 | } 60 | 61 | // Redirects port 8080 to port 8443 and changes the protocol to https 62 | func redirectToHttps(w http.ResponseWriter, req *http.Request) { 63 | newHost := strings.Replace(req.Host, "8080", "8443", 1) 64 | newUrl := fmt.Sprintf("https://%s/%s", newHost, req.RequestURI) 65 | http.Redirect(w, req, newUrl, http.StatusMovedPermanently) 66 | } 67 | 68 | func main() { 69 | http.HandleFunc("/random", randomHandler) 70 | http.HandleFunc("/error", errorHandler) 71 | http.HandleFunc("/notfound", notFoundHandler) 72 | http.HandleFunc("/info", infoHandler) 73 | 74 | fmt.Println(" /random returns in a random amount of time") 75 | fmt.Println(" /error returns an internal-server-error code") 76 | fmt.Println(" /notfound returns a not-found error code") 77 | fmt.Println(" /info returns information about the request\n (try https://localhost:8443/info?x=123&y=456 )") 78 | 79 | var err error 80 | 81 | // Listen for HTTP but redirect to HTTPS 82 | go func() { 83 | fmt.Println("Listening for HTTP on port 8080") 84 | //err = http.ListenAndServe(":8080", nil) 85 | err = http.ListenAndServe(":8080", http.HandlerFunc(redirectToHttps)) 86 | if err != nil { 87 | log.Fatal(err) 88 | } 89 | }() 90 | 91 | // Handle everything on HTTPS 92 | fmt.Println("Listening for HTTPS on port 8443") 93 | err = http.ListenAndServeTLS(":8443", "http-cert.pem", "http-key.pem", nil) 94 | if err != nil { 95 | log.Fatal(err) 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /ifconfig.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | // 4 | // Print network interface information 5 | // 6 | 7 | import ( 8 | "fmt" 9 | "net" 10 | "strings" 11 | ) 12 | 13 | func main() { 14 | fmt.Println("=== interfaces ===") 15 | 16 | ifaces, _ := net.Interfaces() 17 | for _, iface := range ifaces { 18 | fmt.Println("net.Interface:", iface) 19 | 20 | addrs, _ := iface.Addrs() 21 | for _, addr := range addrs { 22 | addrStr := addr.String() 23 | fmt.Println(" net.Addr: ", addr.Network(), addrStr) 24 | 25 | // Must drop the stuff after the slash in order to convert it to an IP instance 26 | split := strings.Split(addrStr, "/") 27 | addrStr0 := split[0] 28 | 29 | // Parse the string to an IP instance 30 | ip := net.ParseIP(addrStr0) 31 | if ip.To4() != nil { 32 | fmt.Println(" ", addrStr0, "is ipv4") 33 | } else { 34 | fmt.Println(" ", addrStr0, "is ipv6") 35 | } 36 | fmt.Println(" ", addrStr0, "is interface-local multicast :", ip.IsInterfaceLocalMulticast()) 37 | fmt.Println(" ", addrStr0, "is link-local multicast :", ip.IsLinkLocalMulticast()) 38 | fmt.Println(" ", addrStr0, "is link-local unicast :", ip.IsLinkLocalUnicast()) 39 | fmt.Println(" ", addrStr0, "is global unicast :", ip.IsGlobalUnicast()) 40 | fmt.Println(" ", addrStr0, "is multicast :", ip.IsMulticast()) 41 | fmt.Println(" ", addrStr0, "is loopback :", ip.IsLoopback()) 42 | } 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /interface.go: -------------------------------------------------------------------------------- 1 | // 2 | // iface gives examples of an interface and a struct that implements it 3 | // 4 | package main 5 | 6 | import "fmt" 7 | 8 | func main() { 9 | fmt.Println("--- interface ---") 10 | var imp Iface = &IfaceImpl{x: 1} 11 | imp.SayHi() 12 | imp.SayBye() 13 | callit(imp) 14 | } 15 | 16 | type Iface interface { 17 | SayHi() 18 | SayBye() 19 | } 20 | 21 | type IfaceImpl struct { 22 | x int 23 | } 24 | 25 | func (i *IfaceImpl) SayHi() { 26 | fmt.Println("Saying Hi before assignment. x=", i.x) 27 | i.x = 11 28 | fmt.Println("Saying Hi after assignment. x=", i.x) 29 | } 30 | 31 | func (i *IfaceImpl) SayBye() { 32 | fmt.Println("Saying Bye. x=", i.x) 33 | } 34 | 35 | func callit(iface Iface) { 36 | iface.SayHi() 37 | iface.SayBye() 38 | } 39 | -------------------------------------------------------------------------------- /intrnl/README.md: -------------------------------------------------------------------------------- 1 | # internal packages 2 | 3 | Examples of internal packages. In this case, package a is accessible, but package b is not. An error occurs during compilation. 4 | 5 | -------------------------------------------------------------------------------- /intrnl/a/asource.go: -------------------------------------------------------------------------------- 1 | package a 2 | 3 | import ( 4 | "fmt" 5 | "github.com/joncrlsn/go-examples/intrnl/a/internal/b" 6 | ) 7 | 8 | var ( 9 | Avar = "avar" 10 | ) 11 | 12 | func PrintStuff() { 13 | fmt.Println("Stuff internal to A =", b.InternalToA) 14 | b.PrintStuff() 15 | } 16 | -------------------------------------------------------------------------------- /intrnl/a/internal/b/bsource.go: -------------------------------------------------------------------------------- 1 | package b 2 | 3 | import ( 4 | "fmt" 5 | "github.com/joncrlsn/go-examples/intrnl/x" 6 | ) 7 | 8 | var ( 9 | InternalToA = "b is internal to a" 10 | ) 11 | 12 | func PrintStuff() { 13 | fmt.Println("Xvar =", x.Xvar) 14 | } 15 | -------------------------------------------------------------------------------- /intrnl/intrnl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joncrlsn/go-examples/2ca6c93433ffb538a742e071ba34426d05ef813e/intrnl/intrnl -------------------------------------------------------------------------------- /intrnl/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | // 4 | // Example of using an internal package. 5 | // This package *can* access the stuff in "a", but not the stuff in "a/internal" 6 | // 7 | 8 | import ( 9 | "fmt" 10 | "github.com/joncrlsn/go-examples/intrnl/a" 11 | // "github.com/joncrlsn/go-examples/intrnl/a/internal/b" // (This is NOT allowed) 12 | ) 13 | 14 | func main() { 15 | 16 | // we can access variables and functions in a 17 | fmt.Println("a.Avar =", a.Avar) 18 | a.PrintStuff() 19 | 20 | // but we cannot access varibles and functions in b 21 | // fmt.Println("b.InternalToA =", b.InternalToA) 22 | // b.PrintStuff() 23 | } 24 | -------------------------------------------------------------------------------- /intrnl/x/xsource.go: -------------------------------------------------------------------------------- 1 | package x 2 | 3 | var Xvar = "Xvar" 4 | -------------------------------------------------------------------------------- /json-web-tokens/.README.md.swp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joncrlsn/go-examples/2ca6c93433ffb538a742e071ba34426d05ef813e/json-web-tokens/.README.md.swp -------------------------------------------------------------------------------- /json-web-tokens/README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | https://auth0.com/blog/2014/12/02/using-json-web-tokens-as-api-keys/ 5 | https://github.com/brainattica/golang-jwt-authentication-api-sample 6 | http://blog.brainattica.com/restful-json-api-jwt-go/ 7 | -------------------------------------------------------------------------------- /json.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io" 7 | "log" 8 | "os" 9 | "strings" 10 | ) 11 | 12 | type Person struct { 13 | Name string `json:"name"` 14 | Age int `json:"age"` 15 | } 16 | 17 | // https://www.socketloop.com/tutorials/golang-http-response-json-encoded-data 18 | func main() { 19 | fmt.Println("=== json ===") 20 | marshal() 21 | unmarshal() 22 | encode() 23 | decode() 24 | } 25 | 26 | // marshal converts a JSON string to objects 27 | func marshal() { 28 | fmt.Println("=== json.marshal ===") 29 | ryan := &Person{"Ryan", 25} 30 | wire, err := json.Marshal(ryan) 31 | check(err) 32 | fmt.Println(string(wire)) 33 | } 34 | 35 | // unmarshal converts an object to a JSON string 36 | func unmarshal() { 37 | fmt.Println("=== json.unmarshal ===") 38 | var jsonBlob = []byte(`[ 39 | {"name": "Bill", "age": 109}, 40 | {"name": "Bob", "age": 5} 41 | ]`) 42 | 43 | var persons []Person 44 | err := json.Unmarshal(jsonBlob, &persons) 45 | check(err) 46 | 47 | fmt.Printf("%+v\n", persons) 48 | } 49 | 50 | // encode writes objects to a JSON *stream* 51 | func encode() { 52 | fmt.Println("=== json.encode ===") 53 | f, err := os.Create("deleteme.json") 54 | check(err) 55 | enc := json.NewEncoder(f) 56 | // or enc := json.NewEncoder(os.Stdout) 57 | p := Person{Name: "Joe", Age: 2} 58 | err = enc.Encode(&p) 59 | check(err) 60 | fmt.Printf("%s: %d\n", p.Name, p.Age) 61 | os.Remove("deleteme.json") 62 | } 63 | 64 | // decode reads a JSON *stream* into objects 65 | func decode() { 66 | fmt.Println("=== json.decode ===") 67 | const jsonStream = ` 68 | {"name": "Ed", "age": 55} 69 | {"name": "Ethan", "age": 33} 70 | {"name": "Elbert", "age": 111} 71 | ` 72 | dec := json.NewDecoder(strings.NewReader(jsonStream)) 73 | for { 74 | var m Person 75 | if err := dec.Decode(&m); err == io.EOF { 76 | break 77 | } else if err != nil { 78 | log.Fatal(err) 79 | } 80 | fmt.Printf("%s: %d\n", m.Name, m.Age) 81 | } 82 | } 83 | 84 | func check(e error) { 85 | if e != nil { 86 | panic(e) 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /keyvalues.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | // 4 | // Wraps boltdb with methods that assume strings for the keys and values 5 | // 6 | 7 | import ( 8 | "bytes" 9 | "errors" 10 | "fmt" 11 | "log" 12 | "time" 13 | 14 | "github.com/boltdb/bolt" 15 | ) 16 | 17 | type KeyValueStore struct { 18 | db *bolt.DB 19 | } 20 | 21 | type KeyValue struct { 22 | Key string 23 | Value string 24 | } 25 | 26 | const ( 27 | bucket = "MyBucket" 28 | ) 29 | 30 | func main() { 31 | 32 | db := NewKeyValueStore("bolt.db.deleteme") 33 | err := db.CreateBucket(bucket) 34 | if err != nil { 35 | fmt.Printf("Error creating bucket: %s", err) 36 | } 37 | 38 | // Put a string in the bucket 39 | err = db.PutString(bucket, "answer", "42") 40 | if err != nil { 41 | fmt.Printf("Error putting to bucket: %s", err) 42 | } 43 | 44 | // Get a known value 45 | var value string 46 | value, err = db.GetString(bucket, "answer") 47 | if err != nil { 48 | fmt.Printf("Error getting from bucket: %s\n", err) 49 | } 50 | fmt.Println("The answer is", value) 51 | 52 | // Get a known missing value 53 | value, err = db.GetString(bucket, "bogus-key") 54 | if err != nil { 55 | if err.Error() == "DoesNotExist" { 56 | fmt.Printf("Good work. Bucket %s does not contain key %s\n", bucket, "bogus-key") 57 | } else { 58 | fmt.Printf("Error getting from bucket: %s\n", err) 59 | } 60 | } 61 | 62 | // Put a string in the bucket 63 | if err := db.PutString(bucket, "jon1", "this is jon1"); err != nil { 64 | fmt.Printf("Error %s\n", err) 65 | } 66 | if err := db.PutString(bucket, "jon2", "this is jon2"); err != nil { 67 | fmt.Printf("Error %s\n", err) 68 | } 69 | if err := db.PutString(bucket, "jon3", "this is jon3"); err != nil { 70 | fmt.Printf("Error %s\n", err) 71 | } 72 | if err := db.PutString(bucket, "jon3", "this was jon3"); err != nil { 73 | fmt.Printf("Error %s\n", err) 74 | } 75 | 76 | kvs, err := db.GetKeyValues(bucket, "jon") 77 | if err != nil { 78 | fmt.Printf("Error getting from bucket: %s\n", err) 79 | } 80 | 81 | fmt.Printf("Pulled %d key values with prefix jon\n", len(kvs)) 82 | for _, kv := range kvs { 83 | fmt.Printf("Key: %s Value: %s\n", kv.Key, kv.Value) 84 | } 85 | } 86 | 87 | func NewKeyValueStore(fileName string) *KeyValueStore { 88 | boltdb, err := bolt.Open(fileName, 0600, &bolt.Options{Timeout: 1 * time.Second}) 89 | if err != nil { 90 | log.Fatal(err) 91 | } 92 | return &KeyValueStore{db: boltdb} 93 | } 94 | 95 | func (kvs *KeyValueStore) CreateBucket(name string) error { 96 | return kvs.db.Update(func(tx *bolt.Tx) error { 97 | _, err := tx.CreateBucketIfNotExists([]byte(bucket)) 98 | return err 99 | }) 100 | } 101 | 102 | // PutString is a boltdb wrapper that assumes both the key and value are strings 103 | func (kvs *KeyValueStore) PutString(bucket string, key string, value string) error { 104 | return kvs.db.Update(func(tx *bolt.Tx) error { 105 | b := tx.Bucket([]byte(bucket)) 106 | err := b.Put([]byte(key), []byte(value)) 107 | return err 108 | }) 109 | } 110 | 111 | // GetString is a boltdb wrapper that assumes both the key and value are strings 112 | func (kvs *KeyValueStore) GetString(bucket string, key string) (string, error) { 113 | var value string 114 | err := kvs.db.View(func(tx *bolt.Tx) error { 115 | b := tx.Bucket([]byte(bucket)) 116 | v := b.Get([]byte(key)) 117 | if v == nil { 118 | return errors.New("DoesNotExist") 119 | } 120 | value = string(v) 121 | return nil 122 | }) 123 | if err != nil { 124 | return "", err 125 | } 126 | return value, nil 127 | } 128 | 129 | // GetKeyValues returns the keys that match the given prefix (with their values) 130 | func (kvs *KeyValueStore) GetKeyValues(bucket string, keyPrefix string) ([]KeyValue, error) { 131 | 132 | var returnValue = []KeyValue{} 133 | // Partial key match example 134 | err := kvs.db.View(func(tx *bolt.Tx) error { 135 | // Assume bucket exists and has keys 136 | c := tx.Bucket([]byte(bucket)).Cursor() 137 | 138 | prefix := []byte(keyPrefix) 139 | for k, v := c.Seek(prefix); k != nil && bytes.HasPrefix(k, prefix); k, v = c.Next() { 140 | kv := KeyValue{string(k), string(v)} 141 | //fmt.Printf("key=%s, value=%s\n", k, v) 142 | returnValue = append(returnValue, kv) 143 | } 144 | 145 | return nil 146 | }) 147 | 148 | return returnValue, err 149 | } 150 | -------------------------------------------------------------------------------- /migrate/README.md: -------------------------------------------------------------------------------- 1 | # migrate 2 | 3 | Provides an example of using the github.com/mattes/migrate library to migrate relational databases to ever newer schemas. 4 | -------------------------------------------------------------------------------- /migrate/db.go: -------------------------------------------------------------------------------- 1 | // 2 | // Sets up the database connection and ensure the schema is migrated. 3 | // 4 | // Copyright (c) 2015 Jon Carlson. All rights reserved. 5 | // Use of this source code is governed by the MIT 6 | // license that can be found in the LICENSE file. 7 | // 8 | package main 9 | 10 | import ( 11 | "database/sql" 12 | "github.com/jmoiron/sqlx" 13 | "github.com/mattes/migrate/migrate" 14 | _ "github.com/mattn/go-sqlite3" 15 | "log" 16 | ) 17 | 18 | func setupDb(dbType string, dbName string, migrateSqlPath string) (db *sqlx.DB, err error) { 19 | log.Printf("=== Checking schema version (dbType=%s, dbName=%s, migrate=%s) ===", dbType, dbName, migrateSqlPath) 20 | dbUrl := dbType + "://" + dbName 21 | 22 | var sqlDb *sql.DB 23 | sqlDb, err = sql.Open(dbType, dbName) 24 | if err != nil { 25 | return nil, err 26 | } 27 | sqlDb.Close() 28 | 29 | // Synchronously migrate the database up if needed. 30 | allErrors, ok := migrate.ResetSync(dbUrl, migrateSqlPath) 31 | if !ok { 32 | log.Println("Error migrating schema", allErrors) 33 | return nil, nil // Program should stop 34 | } 35 | 36 | // Get database connection to return 37 | db, err = sqlx.Connect(dbType, dbName) 38 | if err != nil { 39 | log.Println("Error connecting to db", err) 40 | return nil, err 41 | } 42 | 43 | // success 44 | log.Println("success connecting to db") 45 | return db, nil 46 | } 47 | -------------------------------------------------------------------------------- /migrate/main.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2015 Jon Carlson. All rights reserved. 3 | // Use of this source code is governed by the MIT 4 | // license that can be found in the LICENSE file. 5 | // 6 | package main 7 | 8 | import ( 9 | "database/sql" 10 | _ "github.com/mattes/migrate/driver/sqlite3" 11 | "github.com/mattes/migrate/migrate" 12 | _ "github.com/mattn/go-sqlite3" 13 | "log" 14 | ) 15 | 16 | const ( 17 | dbType = "sqlite3" 18 | dbName = "temp.db" 19 | ) 20 | 21 | func main() { 22 | 23 | var err error 24 | 25 | // 26 | // Ensure we have a database to migrate 27 | // 28 | { 29 | var sqlDb *sql.DB 30 | sqlDb, err = sql.Open(dbType, dbName) 31 | if err != nil { 32 | log.Println("Error opening db", err) 33 | return 34 | } 35 | sqlDb.Close() 36 | } 37 | 38 | // 39 | // Ensure database schema is migrated to the latest 40 | // 41 | dbUrl := dbType + "://" + dbName 42 | allErrors, ok := migrate.ResetSync(dbUrl, "./migrations") 43 | if !ok { 44 | log.Println("Error migrating schema", allErrors) 45 | return 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /migrate/migrate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joncrlsn/go-examples/2ca6c93433ffb538a742e071ba34426d05ef813e/migrate/migrate -------------------------------------------------------------------------------- /migrate/migrations/001_tables.down.sql: -------------------------------------------------------------------------------- 1 | 2 | DROP TABLE IF EXISTS user; 3 | -------------------------------------------------------------------------------- /migrate/migrations/001_tables.up.sql: -------------------------------------------------------------------------------- 1 | -- 2 | -- sqlite3 3 | -- 4 | 5 | CREATE TABLE user ( 6 | user_id INTEGER PRIMARY KEY, 7 | first_name VARCHAR(80) DEFAULT '', 8 | last_name VARCHAR(80) DEFAULT '', 9 | email VARCHAR(250) DEFAULT '', 10 | password VARCHAR(250) DEFAULT NULL, 11 | org_id INTEGER 12 | ); 13 | 14 | CREATE TABLE org ( 15 | org_id INTEGER PRIMARY KEY, 16 | org_name VARCHAR(80) DEFAULT '', 17 | ); 18 | -------------------------------------------------------------------------------- /migrate/migrations/002_initialData.down.sql: -------------------------------------------------------------------------------- 1 | DELETE FROM user WHERE email = 'jane.citizen@example.com'; 2 | -------------------------------------------------------------------------------- /migrate/migrations/002_initialData.up.sql: -------------------------------------------------------------------------------- 1 | 2 | -- password is 'supersecret' 3 | INSERT INTO user (first_name, last_name, email, password) VALUES ('Joe', 'Carter', 'joe@example.com', 'c44tqq+T5R57KwVuAJKfNiriIzmxR+uUcyJNuvAA7PvI84OE7xUpASLiGyMp4/vgYDVJu49u99ugxujEH4g7hw=='); 4 | INSERT INTO user (first_name, last_name, email, password) VALUES ('Jon', 'Carlson', 'jon@example.com', 'c44tqq+T5R57KwVuAJKfNiriIzmxR+uUcyJNuvAA7PvI84OE7xUpASLiGyMp4/vgYDVJu49u99ugxujEH4g7hw=='); 5 | INSERT INTO user (first_name, last_name, email) VALUES ('Jane', 'Citizen', 'jane@example.com'); 6 | -------------------------------------------------------------------------------- /migrate/temp.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joncrlsn/go-examples/2ca6c93433ffb538a742e071ba34426d05ef813e/migrate/temp.db -------------------------------------------------------------------------------- /password.go: -------------------------------------------------------------------------------- 1 | // 2 | // password is an example of reading a password from standard in 3 | // without echoing it back to the user. 4 | // 5 | package main 6 | 7 | import ( 8 | "fmt" 9 | "golang.org/x/crypto/ssh/terminal" 10 | "os" 11 | ) 12 | 13 | func main() { 14 | stdin := 1 15 | fmt.Fprintf(os.Stdin, "Enter a test password: ") 16 | 17 | // Get current state of terminal 18 | // s, err := terminal.MakeRaw(stdin) 19 | // check(err, "making raw terminal, Saving old terminal state") 20 | // defer terminal.Restore(stdin, s) 21 | 22 | // Read password from stdin 23 | b, err := terminal.ReadPassword(stdin) 24 | check(err, "reading from terminal") 25 | 26 | fmt.Println("Your test password is:", string(b)) 27 | } 28 | 29 | func check(err error, action string) { 30 | if err != nil { 31 | fmt.Printf("Error %s: %v\n", action, err) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /pointer.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | type Thing struct { 8 | value string 9 | } 10 | 11 | func main() { 12 | pointerTest1() 13 | } 14 | 15 | // Test passing structs to functions that set values on the struct 16 | func pointerTest1() { 17 | thing := Thing{} 18 | setValue(&thing, "my new value") 19 | fmt.Println("thing.value: ", thing.value) 20 | } 21 | 22 | func setValue(thing *Thing, myVal string) { 23 | thing.value = myVal 24 | } 25 | -------------------------------------------------------------------------------- /postgres.go: -------------------------------------------------------------------------------- 1 | // 2 | // This is an example of accessing Postgres using the standard database/sql API. 3 | // Also included is a method for dynamically converting each row to a string array. 4 | // 5 | package main 6 | 7 | import "fmt" 8 | 9 | //import "reflect" 10 | import "time" 11 | import "os" 12 | import "database/sql" 13 | import _ "github.com/lib/pq" 14 | 15 | const isoFormat = "2006-01-02T15:04:05.000-0700" 16 | 17 | func main() { 18 | db, err := sql.Open("postgres", "user=dbuser password=supersecret dbname=dev-cpc sslmode=disable") 19 | panicIfError(err) 20 | readUsersStatically(db) 21 | readUsersDynamically(db) 22 | } 23 | 24 | func readUsersStatically(db *sql.DB) { 25 | var userId int 26 | var username string 27 | 28 | maxUserId := 21 29 | rows, err := db.Query("SELECT user_id, username FROM t_user WHERE user_id < $1", maxUserId) 30 | panicIfError(err) 31 | 32 | defer rows.Close() 33 | 34 | for rows.Next() { 35 | err := rows.Scan(&userId, &username) 36 | panicIfError(err) 37 | fmt.Println(userId, username) 38 | } 39 | 40 | err = rows.Err() 41 | panicIfError(err) 42 | } 43 | 44 | /* Dynamically convert each row in the result set into a string array */ 45 | func readUsersDynamically(db *sql.DB) { 46 | fmt.Println("--- dynamic column retrieval ---") 47 | 48 | maxUserId := 4 49 | rows, err := db.Query("SELECT user_id, username, active, creation_date, intro_email_date FROM t_user WHERE user_id < $1", maxUserId) 50 | panicIfError(err) 51 | defer rows.Close() 52 | 53 | // http://stackoverflow.com/questions/23507531/is-golangs-sql-package-incapable-of-ad-hoc-exploratory-queries 54 | columnNames, err := rows.Columns() 55 | panicIfError(err) 56 | 57 | vals := make([]interface{}, len(columnNames)) 58 | valPointers := make([]interface{}, len(columnNames)) 59 | for i := 0; i < len(columnNames); i++ { 60 | valPointers[i] = &vals[i] 61 | } 62 | for rows.Next() { 63 | err = rows.Scan(valPointers...) 64 | panicIfError(err) 65 | cells := make([]string, len(columnNames)) 66 | // Convert each cell to a SQL-valid string representation 67 | for i, valPtr := range vals { 68 | //fmt.Println(reflect.TypeOf(valPtr)) 69 | switch valueType := valPtr.(type) { 70 | case nil: 71 | cells[i] = "null" 72 | case []uint8: 73 | cells[i] = "'" + string(valPtr.([]byte)) + "'" 74 | //fmt.Println("Value is a []uint8 (string)", cells[i]) 75 | case string: 76 | cells[i] = "'" + valPtr.(string) + "'" 77 | //fmt.Println("Value is a String", cells[i]) 78 | case int64: 79 | cells[i] = string(valPtr.(int64)) 80 | //fmt.Println("Value is an int64", cells[i]) 81 | case bool: 82 | cells[i] = fmt.Sprintf("%t", valPtr) 83 | //fmt.Println("Value is a bool", cells[i]) 84 | case time.Time: 85 | cells[i] = "'" + valPtr.(time.Time).Format(isoFormat) + "'" 86 | //fmt.Println("Value is a time.Time", cells[i]) 87 | case fmt.Stringer: 88 | cells[i] = fmt.Sprintf("%v", valPtr) 89 | //fmt.Println("Value is an fmt.Stringer", cells[i]) 90 | default: 91 | cells[i] = fmt.Sprintf("%v", valPtr) 92 | fmt.Printf("Column %s is an unhandled type: %v", columnNames[i], valueType) 93 | } 94 | } 95 | fmt.Println("Cells:", cells) 96 | } 97 | } 98 | 99 | func panicIfError(err error) { 100 | if err != nil { 101 | fmt.Println(err) 102 | os.Exit(1) 103 | //panic(err) 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /pqueue.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | // 4 | // Persistent queue 5 | // 6 | 7 | import ( 8 | "github.com/beeker1121/goque" 9 | "log" 10 | "time" 11 | ) 12 | 13 | const ( 14 | dirName = "pqueue" 15 | ) 16 | 17 | type AnObject interface { 18 | Sleep() 19 | } 20 | 21 | type MyObject struct { 22 | Name string 23 | Age int64 24 | } 25 | 26 | func (mo *MyObject) Sleep() { 27 | time.Sleep(time.Duration(mo.Age)) 28 | } 29 | 30 | func main() { 31 | add(&MyObject{"Jon", 30}) 32 | add(&MyObject{"Deb", 31}) 33 | add(&MyObject{"Fluffy", 8}) 34 | add(&MyObject{"Fido", 2}) 35 | 36 | log.Println("got Object:", get()) 37 | log.Println("got Object:", get()) 38 | log.Println("got Object:", get()) 39 | log.Println("got Object:", get()) 40 | } 41 | 42 | // add adds an object to the given prefix 43 | func add(obj AnObject) { 44 | pq, err := goque.OpenQueue(dirName) 45 | if err != nil { 46 | log.Panicln("Error opening queue", err) 47 | } 48 | defer pq.Close() 49 | 50 | item, err := pq.EnqueueObject(obj) 51 | log.Println("item added: ", obj, item.ID) 52 | } 53 | 54 | // getAndLog gets an object from the queue and logs it 55 | func get() AnObject { 56 | pq, err := goque.OpenQueue(dirName) 57 | if err != nil { 58 | log.Panicln("Error opening queue", err) 59 | } 60 | defer pq.Close() 61 | 62 | item, err := pq.Peek() 63 | if err != nil { 64 | log.Panicln("Error peeking in queue", err) 65 | } 66 | 67 | item, err = pq.Dequeue() 68 | if err != nil { 69 | log.Panicln("Error peeking in queue", err) 70 | } 71 | 72 | var obj MyObject 73 | err = item.ToObject(&obj) 74 | return &obj 75 | } 76 | -------------------------------------------------------------------------------- /pqueue2.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | // 4 | // Persistent queue for storing an implementatino of an interface 5 | // 6 | 7 | import ( 8 | "github.com/beeker1121/goque" 9 | "log" 10 | "time" 11 | ) 12 | 13 | const ( 14 | dirName = "pqueue" 15 | ) 16 | 17 | type AnObject interface { 18 | Sleep() 19 | } 20 | 21 | type MyObject struct { 22 | Name string 23 | Age int64 24 | } 25 | 26 | func (mo *MyObject) Sleep() { 27 | time.Sleep(time.Duration(mo.Age)) 28 | } 29 | 30 | func main() { 31 | add(&MyObject{"Jon", 30}) 32 | add(&MyObject{"Deb", 31}) 33 | add(&MyObject{"Fluffy", 8}) 34 | add(&MyObject{"Fido", 2}) 35 | 36 | log.Println("got Object:", get()) 37 | log.Println("got Object:", get()) 38 | log.Println("got Object:", get()) 39 | log.Println("got Object:", get()) 40 | } 41 | 42 | // add adds an object to the given prefix 43 | func add(obj AnObject) { 44 | pq, err := goque.OpenQueue(dirName) 45 | if err != nil { 46 | log.Panicln("Error opening queue", err) 47 | } 48 | defer pq.Close() 49 | 50 | item, err := pq.EnqueueObject(obj) 51 | log.Println("item added: ", obj, item.ID) 52 | } 53 | 54 | // getAndLog gets an object from the queue and logs it 55 | func get() AnObject { 56 | pq, err := goque.OpenQueue(dirName) 57 | if err != nil { 58 | log.Panicln("Error opening queue", err) 59 | } 60 | defer pq.Close() 61 | 62 | item, err := pq.Peek() 63 | if err != nil { 64 | log.Panicln("Error peeking in queue", err) 65 | } 66 | 67 | item, err = pq.Dequeue() 68 | if err != nil { 69 | log.Panicln("Error peeking in queue", err) 70 | } 71 | 72 | var obj MyObject 73 | err = item.ToObject(&obj) 74 | return &obj 75 | } 76 | -------------------------------------------------------------------------------- /prompt.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "os" 7 | ) 8 | 9 | // Prompt user for input and repeat it back 10 | func main() { 11 | // Here is one way to read 12 | reader := bufio.NewReader(os.Stdin) 13 | fmt.Print("Enter text: ") 14 | text, _ := reader.ReadString('\n') 15 | fmt.Println("You entered:", text) 16 | 17 | // Read some more with the same reader 18 | fmt.Print("Enter more text: ") 19 | textmore, _ := reader.ReadString('\n') 20 | fmt.Println("You entered:", textmore) 21 | 22 | // Here is another way that doesn't work 23 | // if there are spaces in what you enter. 24 | fmt.Print("Enter even more text: ") 25 | text2 := "" 26 | fmt.Scanln(&text2) 27 | fmt.Println("You entered:", text2) 28 | 29 | // Not sure about this one 30 | //ln := "" 31 | //fmt.Sscanln("%v", ln) 32 | //fmt.Println(ln) 33 | } 34 | -------------------------------------------------------------------------------- /random.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "math/rand" 6 | "time" 7 | ) 8 | 9 | func main() { 10 | rand.Seed(time.Now().UTC().UnixNano()) 11 | fmt.Println(RandomString(25)) 12 | } 13 | 14 | // RandomString returns a random string of letters of the given length 15 | func RandomString(l int) string { 16 | bytes := make([]byte, l) 17 | for i := 0; i < l; i++ { 18 | rint := RandomInt(65, 117) 19 | if rint > 90 { 20 | rint = rint + 6 21 | } 22 | bytes[i] = byte(rint) 23 | } 24 | return string(bytes) 25 | } 26 | 27 | // RandomInt returns a random integer between the two numbers. 28 | // min is inclusive, max is exclusive 29 | func RandomInt(min int, max int) int { 30 | return min + rand.Intn(max-min) 31 | } 32 | -------------------------------------------------------------------------------- /readlines.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "log" 7 | "os" 8 | ) 9 | 10 | func main() { 11 | fmt.Println("--- readlines ---") 12 | 13 | fileName := "foo.deleteme.txt" 14 | 15 | lines := []string{"hi mom", "hi dad", "this is Jon"} 16 | if err := writeLinesArray(lines, fileName); err != nil { 17 | log.Fatalf("writeLines: %s\n", err) 18 | } 19 | 20 | fmt.Println("Reading line by line:") 21 | c := readLinesChannel(fileName) 22 | for line := range c { 23 | fmt.Printf(" Line: %s\n", line) 24 | } 25 | 26 | fmt.Println("Reading lines into an array:") 27 | lines, err := readLinesArray(fileName) 28 | if err != nil { 29 | log.Fatalf("readLines: %s\n", err) 30 | } 31 | for i, line := range lines { 32 | fmt.Printf(" Line: %d %s\n", i, line) 33 | } 34 | 35 | } 36 | 37 | /* 38 | * Reads a file line by line into an array 39 | * 40 | * lines, err := readLinesArray(fileName) 41 | * if err != nil { 42 | * log.Fatalf("readLines: %s\n", err) 43 | * } 44 | * for i, line := range lines { 45 | * fmt.Printf(" Line: %d %s\n", i, line) 46 | * } 47 | */ 48 | func readLinesArray(path string) ([]string, error) { 49 | file, err := os.Open(path) 50 | if err != nil { 51 | return nil, err 52 | } 53 | defer file.Close() 54 | 55 | var lines []string 56 | scanner := bufio.NewScanner(file) 57 | for scanner.Scan() { 58 | lines = append(lines, scanner.Text()) 59 | } 60 | return lines, scanner.Err() 61 | } 62 | 63 | /* 64 | * Writes the lines to the given file. 65 | */ 66 | func writeLinesArray(lines []string, path string) error { 67 | file, err := os.Create(path) 68 | if err != nil { 69 | return err 70 | } 71 | defer file.Close() 72 | 73 | w := bufio.NewWriter(file) 74 | for _, line := range lines { 75 | fmt.Fprintln(w, line) 76 | } 77 | return w.Flush() 78 | } 79 | 80 | /* 81 | * Reads a file line by line into a channel 82 | * 83 | * c := readLinesChannel(fileName) 84 | * for line := range c { 85 | * fmt.Printf(" Line: %s\n", line) 86 | * } 87 | */ 88 | func readLinesChannel(fileName string) <-chan string { 89 | c := make(chan string) 90 | file, err := os.Open(fileName) 91 | if err != nil { 92 | log.Panic(err) 93 | } 94 | go func() { 95 | defer file.Close() 96 | scanner := bufio.NewScanner(file) 97 | for scanner.Scan() { 98 | c <- scanner.Text() 99 | } 100 | close(c) 101 | }() 102 | return c 103 | } 104 | -------------------------------------------------------------------------------- /reflect.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | ) 7 | 8 | var person Person 9 | 10 | type Person struct { 11 | Name string 12 | } 13 | 14 | func main() { 15 | b := true 16 | s := "" 17 | n := 1 18 | f := 1.0 19 | a := []string{"foo", "bar", "baz"} 20 | 21 | fmt.Println(reflect.TypeOf(b)) 22 | fmt.Println(reflect.TypeOf(s)) 23 | fmt.Println(reflect.TypeOf(n)) 24 | fmt.Println(reflect.TypeOf(f)) 25 | fmt.Println(reflect.TypeOf(a)) 26 | fmt.Println(reflect.TypeOf(person)) 27 | } 28 | -------------------------------------------------------------------------------- /regex.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "log" 7 | "regexp" 8 | "strings" 9 | ) 10 | 11 | func main() { 12 | multilineRegexp() 13 | //regexp1() 14 | //regexp2() 15 | } 16 | 17 | func multilineRegexp() { 18 | fmt.Println("--- multine regexp ---") 19 | str := ` 20 | CREATE OR REPLACE FUNCTION himom() 21 | RETURNS number 22 | LANGUAGE SQL 23 | AS $himom$ 24 | SELECT 'himom'; 25 | $hidad$; 26 | ` 27 | //r := regexp.MustCompile(`(?is)CREATE.*\s+AS\s+.* (\$[[:alnum:]]*\$)`) 28 | r := regexp.MustCompile(`(?is)CREATE.*\s+AS\s+((?U).*)(\$[^\$]*\$)`) 29 | 30 | match := r.FindStringSubmatch(str) 31 | fmt.Printf("%q\n", match) 32 | } 33 | 34 | func regexp1() { 35 | fmt.Println("--- regexp1 ---") 36 | 37 | includeRegex, err := regexp.Compile(`^\s*include\("(\\\"|[^"])+"\);`) 38 | if err != nil { 39 | log.Fatal(err) 40 | } 41 | 42 | for _, line := range strings.Split(` 43 | foo 44 | include "abc.def" 45 | include("file.js"); 46 | include "me\"to\"" 47 | include("please\"!\""); 48 | nothing here 49 | `, "\n") { 50 | if includeRegex.Match([]byte(line)) { 51 | includeFile := includeRegex.FindString(line) 52 | fmt.Println("INCLUDE", includeFile) 53 | } else { 54 | fmt.Printf("no match for \"%s\"\n", line) 55 | } 56 | } 57 | } 58 | 59 | func regexp2() { 60 | fmt.Println("--- regexp2 ---") 61 | 62 | // This tests whether a pattern matches a string. 63 | match1, _ := regexp.MatchString(`(?i)(^|\s)FROM\s`, " from t_user") 64 | fmt.Println(match1) 65 | 66 | // This tests whether a pattern matches a string. 67 | match, _ := regexp.MatchString(`p([a-z]+)ch`, "peach") 68 | fmt.Println(match) 69 | 70 | // Above we used a string pattern directly, but for 71 | // other regexp tasks you'll need to `Compile` an 72 | // optimized `Regexp` struct. 73 | r, _ := regexp.Compile("p([a-z]+)ch") 74 | 75 | // Many methods are available on these structs. Here's 76 | // a match test like we saw earlier. 77 | fmt.Println(r.MatchString("peach")) 78 | 79 | // This finds the match for the regexp. 80 | fmt.Println(r.FindString("peach punch")) 81 | 82 | // The also finds the first match but returns the 83 | // start and end indexes for the match instead of the 84 | // matching text. 85 | fmt.Println(r.FindStringIndex("peach punch")) 86 | 87 | // The `Submatch` variants include information about 88 | // both the whole-pattern matches and the submatches 89 | // within those matches. For example this will return 90 | // information for both `p([a-z]+)ch` and `([a-z]+)`. 91 | fmt.Println(r.FindStringSubmatch("peach punch")) 92 | 93 | // Similarly this will return information about the 94 | // indexes of matches and submatches. 95 | fmt.Println(r.FindStringSubmatchIndex("peach punch")) 96 | 97 | // The `All` variants of these functions apply to all 98 | // matches in the input, not just the first. For 99 | // example to find all matches for a regexp. 100 | fmt.Println(r.FindAllString("peach punch pinch", -1)) 101 | 102 | // These `All` variants are available for the other 103 | // functions we saw above as well. 104 | fmt.Println(r.FindAllStringSubmatchIndex( 105 | "peach punch pinch", -1)) 106 | 107 | // Providing a non-negative integer as the second 108 | // argument to these functions will limit the number 109 | // of matches. 110 | fmt.Println(r.FindAllString("peach punch pinch", 2)) 111 | 112 | // Our examples above had string arguments and used 113 | // names like `MatchString`. We can also provide 114 | // `[]byte` arguments and drop `String` from the 115 | // function name. 116 | fmt.Println(r.Match([]byte("peach"))) 117 | 118 | // When creating constants with regular expressions 119 | // you can use the `MustCompile` variation of 120 | // `Compile`. A plain `Compile` won't work for 121 | // constants because it has 2 return values. 122 | r = regexp.MustCompile("p([a-z]+)ch") 123 | fmt.Println(r) 124 | 125 | // The `regexp` package can also be used to replace 126 | // subsets of strings with other values. 127 | fmt.Println(r.ReplaceAllString("a peach", "")) 128 | 129 | // The `Func` variant allows you to transform matched 130 | // text with a given function. 131 | in := []byte("a peach") 132 | out := r.ReplaceAllFunc(in, bytes.ToUpper) 133 | fmt.Println(string(out)) 134 | } 135 | -------------------------------------------------------------------------------- /rune.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | func main() { 8 | 9 | // Iterate over the runes in a string 10 | str := "Hello äåéö€" 11 | fmt.Println() 12 | for i, roon := range str { 13 | fmt.Println(i, roon, string(roon)) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /simple-job-scheduler.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | // 4 | // Defines a simple job scheduler that runs one (ideally short) job at a time. 5 | // Each job has a current or future RunAt time when it will be run. 6 | // 7 | // Users of this implement Job, JobGetter, and JobRunner 8 | // 9 | 10 | import ( 11 | "fmt" 12 | "github.com/beeker1121/goque" 13 | "github.com/pkg/errors" 14 | "log" 15 | "sort" 16 | "sync" 17 | "time" 18 | ) 19 | 20 | const ( 21 | JobSuccess = JobStatus("success") 22 | JobReady = JobStatus("ready") 23 | JobError = JobStatus("error") 24 | intervalSeconds = 5 25 | ) 26 | 27 | type JobStatus string 28 | 29 | // ========================================================== 30 | // Job, JobGetter and JobRunner are all implemented by the user of this library 31 | // ========================================================== 32 | 33 | // Job interface is implemented by the user to represent the job he wants us to run. 34 | type Job interface { 35 | GetId() uint64 36 | SetId(id uint64) 37 | GetStatus() JobStatus 38 | SetStatus(status JobStatus) 39 | GetRunAt() time.Time 40 | } 41 | 42 | // JobGetter is implemented by the user to retrieve the Job instance from a goque.Item. 43 | // This is less than idea becuase the user shouldn't know we are using goque. 44 | type JobGetter func(item *goque.Item) (Job, error) 45 | 46 | // JobRunner is implemented by the user to run their job 47 | type JobRunner func(job Job) (JobStatus, error) 48 | 49 | // 50 | // Jobs is a sortable slice of Job instances 51 | // 52 | type Jobs []Job 53 | 54 | func (slice Jobs) Len() int { 55 | return len(slice) 56 | } 57 | 58 | func (slice Jobs) Less(i, j int) bool { 59 | return slice[i].GetRunAt().Before(slice[j].GetRunAt()) 60 | } 61 | 62 | func (slice Jobs) Swap(i, j int) { 63 | slice[i], slice[j] = slice[j], slice[i] 64 | } 65 | 66 | // 67 | // JobScheduler manages the jobs that need to be run 68 | // 69 | type JobScheduler struct { 70 | Name string 71 | getter JobGetter 72 | runner JobRunner 73 | readyJobs Jobs 74 | mutex sync.Mutex // control access to readyJobs 75 | } 76 | 77 | func NewJobScheduler(name string, getter JobGetter, runner JobRunner) *JobScheduler { 78 | job := &JobScheduler{Name: name, getter: getter, runner: runner} 79 | err := job.initialize() 80 | if err != nil { 81 | log.Printf("Error initializing JobScheduler %s: %v", name, err) 82 | return nil 83 | } 84 | return job 85 | } 86 | 87 | // initialize adds any persistent jobs so they can be run 88 | func (js *JobScheduler) initialize() error { 89 | 90 | js.mutex.Lock() 91 | defer js.mutex.Unlock() 92 | 93 | // Collect and sort all persisted "ready" jobs 94 | q, err := goque.OpenQueue(js.Name) 95 | if err != nil { 96 | return errors.Wrap(err, fmt.Sprintf("Error opening queue %s", js.Name)) 97 | } 98 | defer q.Close() 99 | 100 | // Get each persisted job and add it to the readyJobs field 101 | for i := uint64(0); i < q.Length(); i++ { 102 | item, err := q.PeekByOffset(i) 103 | if err != nil { 104 | return errors.Wrap(err, "Error in inititialize calling PeekByOffset") 105 | } 106 | job, err := js.getter(item) 107 | if err != nil { 108 | return errors.Wrap(err, "Error in inititialize calling jobGetter") 109 | } 110 | if job.GetStatus() == JobReady { 111 | job.SetId(item.ID) 112 | js.readyJobs = append(js.readyJobs, job) 113 | } else { 114 | log.Println("Initialize is ignoring job:", job) 115 | } 116 | 117 | } 118 | 119 | // This goroutine regularly runs the available jobs one by one 120 | go func() { 121 | log.Println("Started goroutine") 122 | for { 123 | time.Sleep(time.Duration(intervalSeconds) * time.Second) 124 | log.Println("Woke up...") 125 | if len(js.readyJobs) > 0 { 126 | js.checkJobs() 127 | } 128 | } 129 | }() 130 | 131 | return nil 132 | } 133 | 134 | // AddJob persists a new job to be run 135 | func (js *JobScheduler) AddJob(job Job) error { 136 | 137 | js.mutex.Lock() 138 | defer js.mutex.Unlock() // Persist the job 139 | q, err := goque.OpenQueue(js.Name) 140 | if err != nil { 141 | return errors.Wrap(err, fmt.Sprintf("Error opening queue %s", js.Name)) 142 | } 143 | defer q.Close() 144 | 145 | // Set the initial state before we persist it 146 | job.SetStatus(JobReady) 147 | 148 | item, err := q.EnqueueObject(job) 149 | if err != nil { 150 | return errors.Wrap(err, "Error adding job to work queue") 151 | } 152 | job.SetId(item.ID) 153 | 154 | // Add job to our in-memory list and select the next job to run 155 | js.readyJobs = append(js.readyJobs, job) 156 | 157 | log.Println("job added: ", item.ID, job) 158 | 159 | return nil 160 | } 161 | 162 | // checkJobs is called every minute (or so) to see if there is any work to do 163 | func (js *JobScheduler) checkJobs() { 164 | 165 | // Lock for the duration of this method 166 | js.mutex.Lock() 167 | defer js.mutex.Unlock() 168 | 169 | log.Printf("Checking %d job(s)\n", len(js.readyJobs)) 170 | if len(js.readyJobs) == 0 { 171 | return 172 | } 173 | 174 | // Sort the jobs by ascending RunAt time 175 | sort.Sort(js.readyJobs) 176 | 177 | // Loop through each job in the list 178 | for _, job := range js.readyJobs { 179 | // Run the job only if it is time to run 180 | if job.GetRunAt().Before(time.Now()) { 181 | 182 | // Run the job 183 | jobStatus, err := js.runner(job) 184 | if err != nil { 185 | log.Printf("Error running jobId %d in queue %s %v\n", job.GetId(), js.Name, err) 186 | jobStatus = JobError 187 | } 188 | 189 | // Remove the first one (the one we just ran) 190 | js.readyJobs = js.readyJobs[1:] 191 | 192 | // Update persistent version of job with new status 193 | job.SetStatus(jobStatus) 194 | err = updateJob(js.Name, job) 195 | if err != nil { 196 | log.Println("Error updating jobId %d in queue %s %v\n", job.GetId(), js.Name, err) 197 | return 198 | } 199 | 200 | } else { 201 | log.Printf("JobId %d is not ready to run until %v\n", job.GetId(), job.GetRunAt()) 202 | } 203 | } 204 | } 205 | 206 | // updateJob puts the new job state back into the database 207 | func updateJob(queueName string, job Job) error { 208 | q, err := goque.OpenQueue(queueName) 209 | if err != nil { 210 | return errors.Wrap(err, "Error opening queue:") 211 | } 212 | defer q.Close() 213 | 214 | _, err = q.UpdateObject(job.GetId(), job) 215 | if err != nil { 216 | return errors.Wrap(err, "Error updating job in queue:") 217 | } 218 | 219 | log.Println("Updated job with new status:", job) 220 | return nil 221 | } 222 | 223 | // =========================================================================== 224 | // 225 | // Everything above is the "library" and not to be modified by the user 226 | // Everything below is the user implementation 227 | // 228 | // =========================================================================== 229 | 230 | func main() { 231 | js := NewJobScheduler("sleep", SleepJobGetter, SleepJobRunner) 232 | err := js.AddJob(&SleepJob{SleepSeconds: 3, Status: JobReady, RunAt: time.Now().Add(time.Duration(20) * time.Second)}) 233 | checkErr(err) 234 | time.Sleep(time.Duration(5) * time.Second) 235 | err = js.AddJob(&SleepJob{SleepSeconds: 7, Status: JobReady, RunAt: time.Now()}) 236 | checkErr(err) 237 | time.Sleep(time.Duration(2) * time.Second) 238 | err = js.AddJob(&SleepJob{SleepSeconds: 1}) 239 | checkErr(err) 240 | time.Sleep(time.Duration(30) * time.Second) 241 | log.Println("Done") 242 | } 243 | 244 | func checkErr(err error) { 245 | if err != nil { 246 | log.Fatalln("Error adding Job:", err) 247 | } 248 | } 249 | 250 | // SleepJob requires public fields and public getters/setters. 251 | // The methods on SleepJob implement the Job interface 252 | type SleepJob struct { 253 | Id uint64 254 | Status JobStatus 255 | RunAt time.Time 256 | SleepSeconds int64 257 | } 258 | 259 | func (my *SleepJob) GetId() uint64 { 260 | return my.Id 261 | } 262 | 263 | func (my *SleepJob) SetId(id uint64) { 264 | my.Id = id 265 | } 266 | 267 | func (my *SleepJob) GetStatus() JobStatus { 268 | return my.Status 269 | } 270 | 271 | func (my *SleepJob) SetStatus(status JobStatus) { 272 | my.Status = status 273 | } 274 | 275 | func (my *SleepJob) GetRunAt() time.Time { 276 | return my.RunAt 277 | } 278 | 279 | // SleepJobGetter decodes the SleepJob from the goque Item value. 280 | // This is less than idea becuase the user shouldn't know we are using goque. 281 | // It's really just boilerplate code to specify the type of job stored in the queue 282 | // The gob package requires the exact type when decoding 283 | func SleepJobGetter(item *goque.Item) (Job, error) { 284 | var job SleepJob 285 | err := item.ToObject(&job) 286 | if err != nil { 287 | return &job, errors.Wrap(err, "Error getting job from item") 288 | } 289 | return &job, nil 290 | } 291 | 292 | // SleepJobRunner runs the sleep job 293 | func SleepJobRunner(job Job) (JobStatus, error) { 294 | 295 | // Ensure it's really a SleepJob 296 | sleepJob, ok := job.(*SleepJob) 297 | if !ok { 298 | return JobError, errors.New("Not running a SleepJob") 299 | } 300 | 301 | log.Printf("SleepJob %d is sleeping for %d seconds", sleepJob.Id, sleepJob.SleepSeconds) 302 | time.Sleep(time.Duration(sleepJob.SleepSeconds) * time.Second) 303 | 304 | log.Printf("Job %d sleeping for %d seconds", sleepJob.Id, sleepJob.SleepSeconds) 305 | return JobSuccess, nil 306 | } 307 | -------------------------------------------------------------------------------- /smtp.go: -------------------------------------------------------------------------------- 1 | // 2 | // Thank you to William Kennedy who shared his code here: 3 | // http://www.goinggo.net/2013/06/send-email-in-go-with-smtpsendmail.html 4 | // 5 | // Example call: 6 | // SendEmail( 7 | // "smtp.gmail.com", 8 | // 587, 9 | // "username@gmail.com", 10 | // "password", 11 | // []string{"me@domain.com"}, 12 | // "testing subject", 13 | // "Exception 1Exception 1") 14 | // } 15 | // 16 | package main 17 | 18 | import ( 19 | "bytes" 20 | "fmt" 21 | "net/smtp" 22 | "runtime" 23 | "strings" 24 | "text/template" 25 | ) 26 | 27 | var _emailScript = `From: {{.From}} 28 | To: {{.To}} 29 | Subject: {{.Subject}} 30 | MIME-version: 1.0 31 | Content-Type: text/html; charset="UTF-8" 32 | 33 | {{.Message}}` 34 | 35 | // sendMail sends an email with values from the config file 36 | func sendMail(subject, message string) error { 37 | return _sendEmail(mailHost, mailPort, mailUsername, mailPassword, mailFrom, mailTo, subject, message) 38 | } 39 | 40 | // _sendEmail does the detailed-work for sending an email 41 | func _sendEmail(host string, port int, userName string, password string, from string, to []string, subject string, message string) (err error) { 42 | defer _catchPanic(&err, "_sendEmail") 43 | 44 | if len(host) == 0 { 45 | if verbose { 46 | fmt.Println("No smtp host. Skipping email.") 47 | } 48 | return nil 49 | } 50 | 51 | parameters := struct { 52 | From string 53 | To string 54 | Subject string 55 | Message string 56 | }{ 57 | userName, 58 | strings.Join([]string(to), ","), 59 | subject, 60 | message, 61 | } 62 | 63 | buffer := new(bytes.Buffer) 64 | 65 | template := template.Must(template.New("emailTemplate").Parse(_emailScript)) 66 | template.Execute(buffer, ¶meters) 67 | 68 | auth := smtp.PlainAuth("", userName, password, host) 69 | if len(userName) == 0 { 70 | auth = nil 71 | } 72 | 73 | err = smtp.SendMail( 74 | fmt.Sprintf("%s:%d", host, port), 75 | auth, 76 | from, 77 | to, 78 | buffer.Bytes()) 79 | 80 | return err 81 | } 82 | 83 | func _catchPanic(err *error, functionName string) { 84 | if r := recover(); r != nil { 85 | fmt.Printf("%s : PANIC Defered : %v\n", functionName, r) 86 | 87 | // Capture the stack trace 88 | buf := make([]byte, 10000) 89 | runtime.Stack(buf, false) 90 | 91 | fmt.Printf("%s : Stack Trace : %s", functionName, string(buf)) 92 | 93 | if err != nil { 94 | *err = fmt.Errorf("%v", r) 95 | } 96 | } else if err != nil && *err != nil { 97 | fmt.Printf("%s : ERROR : %v\n", functionName, *err) 98 | 99 | // Capture the stack trace 100 | buf := make([]byte, 10000) 101 | runtime.Stack(buf, false) 102 | 103 | fmt.Printf("%s : Stack Trace : %s", functionName, string(buf)) 104 | } 105 | } 106 | 107 | func main() { 108 | 109 | } 110 | -------------------------------------------------------------------------------- /soap.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | "net/http" 7 | "strings" 8 | ) 9 | 10 | func main() { 11 | soapuiorg() 12 | } 13 | 14 | func soapSecretServer() { 15 | 16 | } 17 | 18 | func soapuiorg() { 19 | 20 | resp, err := http.Post("http://www.soapui.org", " text/xml;charset=UTF-8", strings.NewReader("the body")) 21 | 22 | if err != nil { 23 | fmt.Println(err) 24 | return 25 | } 26 | 27 | b, err := ioutil.ReadAll(resp.Body) 28 | 29 | if err != nil { 30 | fmt.Println(err) 31 | return 32 | } 33 | 34 | fmt.Println(string(b)) 35 | } 36 | -------------------------------------------------------------------------------- /soap.txt: -------------------------------------------------------------------------------- 1 | import ( 2 | "./esb" // the full ESBCredentials type is defined elsewhere 3 | ... 4 | ) 5 | 6 | // Sample data 7 | var ESBUsername = "user@esb" 8 | var ESBPassword = "changeme" 9 | var ClientGUID = "CA55E77E-1962-0000-2014-DEC1A551F1ED" 10 | 11 | type Envelope struct { 12 | XMLName xml.Name `xml:"http://schemas.xmlsoap.org/soap/envelope/ Envelope"` 13 | EncodingStyle string `xml:"http://schemas.xmlsoap.org/soap/envelope/ encodingStyle,attr"` 14 | Header EnvelopeHeader `xml:"http://schemas.xmlsoap.org/soap/envelope/ Header"` 15 | Body EnvelopeBody `xml:"http://schemas.xmlsoap.org/soap/envelope/ Body"` 16 | } 17 | 18 | // This is fixed independently of whatever port we call 19 | type EnvelopeHeader struct { 20 | Credentials *esb.ESBCredentials `xml:"http://esb/definitions ESBCredentials"` 21 | } 22 | 23 | // This has to change depending on which service we invoke 24 | type EnvelopeBody struct { 25 | Payload interface{} // the empty interface lets us assign any other type 26 | } 27 | 28 | type QueryEntity struct { 29 | // having an XMLName field lets you re-tag (and rename) the Payload 30 | XMLName xml.Name `xml:"http://esb/queryservice QueryEntity"` 31 | ClientGUID *string 32 | QueryString *string 33 | } 34 | 35 | 36 | 37 | func CreateQuery(query string) *Envelope { 38 | // Build the envelope (this could be farmed out to another func) 39 | retval := &Envelope{} 40 | retval.EncodingStyle = "http://schemas.xmlsoap.org/soap/encoding/" 41 | retval.Header = EnvelopeHeader{} 42 | retval.Header.Credentials = &esb.ESBCredentials{} 43 | retval.Header.Credentials.ESBUsername = &ESBUsername 44 | retval.Header.Credentials.ESBPassword = &ESBPassword 45 | 46 | // Build the payload that matches our desired SOAP port 47 | payload := QueryEntity{} 48 | payload.ClientGUID = &HouseholdId 49 | payload.QueryString = query 50 | 51 | // ...and in the darkness bind it 52 | retval.Body.Payload = payload 53 | return retval 54 | } 55 | 56 | 57 | func InvokePitsOfDarkness() { 58 | ... 59 | buffer := &bytes.Buffer{} 60 | encoder := xml.NewEncoder(buffer) 61 | envelope := CreateQuery("wally") 62 | err := encoder.Encode(envelope) 63 | // this is just a test, so let's panic freely 64 | if err != nil { 65 | log.Panic("Could not encode request") 66 | } 67 | 68 | client := http.Client{} 69 | req, err := http.NewRequest("POST", "https://esb/service", buffer) 70 | if err != nil { 71 | log.Panic(err.Error()) 72 | } 73 | 74 | req.Header.Add("SOAPAction", "\"http://esb/service/QueryEntity\"") 75 | req.Header.Add("Content-Type", "text/xml") 76 | resp, err := client.Do(req) 77 | if err != nil { 78 | log.Panic(err.Error()) 79 | } 80 | if resp.StatusCode != 200 { 81 | log.Panic(resp.Status) 82 | } 83 | 84 | result, err := GoElsewhereToDecode(resp.Body) 85 | ... 86 | } 87 | -------------------------------------------------------------------------------- /sort.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | // http://nerdyworm.com/blog/2013/05/15/sorting-a-slice-of-structs-in-go/ 6 | 7 | type Interface interface { 8 | // Len is the number of elements in the collection. 9 | Len() int 10 | // Less reports whether the element with 11 | // index i should sort before the element with index j. 12 | Less(i, j int) bool 13 | // Swap swaps the elements with indexes i and j. 14 | Swap(i, j int) 15 | } 16 | 17 | func main() { 18 | fmt.Println("sort not implemented yet") 19 | } 20 | -------------------------------------------------------------------------------- /spawn.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | // 4 | // Example of spawning external shell commands 5 | // 6 | 7 | import "fmt" 8 | import "os/exec" 9 | import "io/ioutil" 10 | 11 | func main() { 12 | fmt.Println("--- spawn ---") 13 | 14 | // We'll start with a simple command that takes no 15 | // arguments or input and just prints something to 16 | // stdout. The `exec.Command` helper creates an object 17 | // to represent this external process. 18 | dateCmd := exec.Command("date") 19 | 20 | // `.Output` is another helper than handles the common 21 | // case of running a command, waiting for it to finish, 22 | // and collecting its output. If there were no errors, 23 | // `dateOut` will hold bytes with the date info. 24 | dateOut, err := dateCmd.Output() 25 | if err != nil { 26 | panic(err) 27 | } 28 | fmt.Println("> date") 29 | fmt.Println(string(dateOut)) 30 | 31 | // Next we'll look at a slightly more involved case 32 | // where we pipe data to the external process on its 33 | // `stdin` and collect the results from its `stdout`. 34 | grepCmd := exec.Command("grep", "hello") 35 | 36 | // Here we explicitly grab input/output pipes, start 37 | // the process, write some input to it, read the 38 | // resulting output, and finally wait for the process 39 | // to exit. 40 | grepIn, _ := grepCmd.StdinPipe() 41 | grepOut, _ := grepCmd.StdoutPipe() 42 | grepCmd.Start() 43 | grepIn.Write([]byte("hello grep\ngoodbye grep")) 44 | grepIn.Close() 45 | grepBytes, _ := ioutil.ReadAll(grepOut) 46 | grepCmd.Wait() 47 | 48 | // We ommited error checks in the above example, but 49 | // you could use the usual `if err != nil` pattern for 50 | // all of them. We also only collect the `StdoutPipe` 51 | // results, but you could collect the `StderrPipe` in 52 | // exactly the same way. 53 | fmt.Println("> grep hello") 54 | fmt.Println(string(grepBytes)) 55 | 56 | // Note that when spawning commands we need to 57 | // provide an explicitly delineated command and 58 | // argument array, vs. being able to just pass in one 59 | // command-line string. If you want to spawn a full 60 | // command with a string, you can use `bash`'s `-c` 61 | // option: 62 | lsCmd := exec.Command("bash", "-c", "ls -a -l -h") 63 | lsOut, err := lsCmd.Output() 64 | if err != nil { 65 | panic(err) 66 | } 67 | fmt.Println("> ls -a -l -h") 68 | fmt.Println(string(lsOut)) 69 | fmt.Println() 70 | } 71 | -------------------------------------------------------------------------------- /sqlite.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "crypto/rand" 5 | "database/sql" 6 | "encoding/hex" 7 | "fmt" 8 | _ "github.com/mattn/go-sqlite3" 9 | "log" 10 | "os" 11 | "path/filepath" 12 | ) 13 | 14 | type Article struct { 15 | Id int 16 | Title string 17 | Body string 18 | Date string 19 | } 20 | 21 | func main() { 22 | fmt.Println("===== sqlite ===== ") 23 | tempFilename := tempFileName() 24 | fmt.Printf("temp sqlite db: %s \n", tempFilename) 25 | db, err := sql.Open("sqlite3", tempFilename) 26 | check(err, "opening database") 27 | 28 | defer db.Close() 29 | defer os.Remove(tempFilename) 30 | 31 | _, err = db.Exec("DROP TABLE foo") 32 | _, err = db.Exec("CREATE TABLE foo (id integer)") 33 | check(err, "creating table") 34 | 35 | res, err := db.Exec("INSERT INTO foo(id) VALUES(?)", 123) 36 | check(err, "inserting record") 37 | 38 | affected, _ := res.RowsAffected() 39 | if affected != 1 { 40 | log.Fatalf("Expected %d for affected rows, but %d:", 1, affected) 41 | } 42 | 43 | rows, err := db.Query("SELECT id FROM foo") 44 | check(err, "selecting records") 45 | defer rows.Close() 46 | 47 | for rows.Next() { 48 | var result int 49 | rows.Scan(&result) 50 | if result != 123 { 51 | log.Fatalf("Fetched %q; expected %q", 123, result) 52 | } 53 | } 54 | fmt.Println("sqlite done!") 55 | } 56 | 57 | func tempFileName() string { 58 | randBytes := make([]byte, 16) 59 | rand.Read(randBytes) 60 | return filepath.Join(os.TempDir(), "foo-"+hex.EncodeToString(randBytes)+".db") 61 | } 62 | 63 | func check(err error, action string) { 64 | if err != nil { 65 | log.Fatalf("Error %s: %v\n", action, err) 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /sqlx-sqlite.go: -------------------------------------------------------------------------------- 1 | // 2 | // Provides an example of the jmoiron/sqlx data mapping library with sqlite 3 | // 4 | package main 5 | 6 | import ( 7 | "database/sql" 8 | "fmt" 9 | "github.com/jmoiron/sqlx" 10 | _ "github.com/mattn/go-sqlite3" 11 | "log" 12 | ) 13 | 14 | var schema = ` 15 | DROP TABLE IF EXISTS user; 16 | CREATE TABLE user ( 17 | user_id INTEGER PRIMARY KEY, 18 | first_name VARCHAR(80) DEFAULT '', 19 | last_name VARCHAR(80) DEFAULT '', 20 | email VARCHAR(250) DEFAULT '', 21 | password VARCHAR(250) DEFAULT NULL 22 | ); 23 | ` 24 | 25 | type User struct { 26 | UserId int `db:"user_id"` 27 | FirstName string `db:"first_name"` 28 | LastName string `db:"last_name"` 29 | Email string 30 | Password sql.NullString 31 | } 32 | 33 | func main() { 34 | // this connects & tries a simple 'SELECT 1', panics on error 35 | // use sqlx.Open() for sql.Open() semantics 36 | db, err := sqlx.Connect("sqlite3", "__deleteme.db") 37 | if err != nil { 38 | log.Fatalln(err) 39 | } 40 | 41 | // exec the schema or fail; multi-statement Exec behavior varies between 42 | // database drivers; pq will exec them all, sqlite3 won't, ymmv 43 | db.MustExec(schema) 44 | 45 | tx := db.MustBegin() 46 | tx.MustExec("INSERT INTO user (first_name, last_name, email) VALUES ($1, $2, $3)", "Jason", "Moiron", "jmoiron@jmoiron.net") 47 | tx.MustExec("INSERT INTO user (first_name, last_name, email, password) VALUES ($1, $2, $3, $4)", "John", "Doe", "johndoeDNE@gmail.net", "supersecret") 48 | // Named queries can use structs, so if you have an existing struct (i.e. person := &User{}) that you have populated, you can pass it in as &person 49 | tx.NamedExec("INSERT INTO user (first_name, last_name, email) VALUES (:first_name, :last_name, :email)", &User{FirstName: "Jane", LastName: "Citizen", Email: "jane.citzen@example.com"}) 50 | tx.Commit() 51 | 52 | // Query the database, storing results in a []User (wrapped in []interface{}) 53 | people := []User{} 54 | db.Select(&people, "SELECT * FROM user ORDER BY first_name ASC") 55 | jane, jason := people[0], people[1] 56 | 57 | fmt.Printf("Jane: %#v\nJason: %#v\n", jane, jason) 58 | // User{FirstName:"Jason", LastName:"Moiron", Email:"jmoiron@jmoiron.net"} 59 | // User{FirstName:"John", LastName:"Doe", Email:"johndoeDNE@gmail.net"} 60 | 61 | // You can also get a single result, a la QueryRow 62 | jason1 := User{} 63 | err = db.Get(&jason1, "SELECT * FROM user WHERE first_name=$1", "Jason") 64 | fmt.Printf("Jason: %#v\n", jason1) 65 | // User{FirstName:"Jason", LastName:"Moiron", Email:"jmoiron@jmoiron.net"} 66 | 67 | // if you have null fields and use SELECT *, you must use sql.Null* in your struct 68 | users := []User{} 69 | err = db.Select(&users, "SELECT * FROM user ORDER BY email ASC") 70 | if err != nil { 71 | fmt.Println(err) 72 | return 73 | } 74 | jane, jason, john := users[0], users[1], users[2] 75 | 76 | fmt.Printf("Jane: %#v\nJason: %#v\nJohn: %#v\n", jane, jason, john) 77 | 78 | // Loop through rows using only one struct 79 | user := User{} 80 | rows, err := db.Queryx("SELECT * FROM user WHERE first_name LIKE 'J%'") 81 | for rows.Next() { 82 | err := rows.StructScan(&user) 83 | if err != nil { 84 | log.Fatalln(err) 85 | } 86 | fmt.Printf("%#v\n", user) 87 | } 88 | 89 | // Named queries, using `:name` as the bindvar. Automatic bindvar support 90 | // which takes into account the dbtype based on the driverName on sqlx.Open/Connect 91 | _, err = db.NamedExec(`INSERT INTO user (first_name,last_name,email) VALUES (:first,:last,:email)`, 92 | map[string]interface{}{ 93 | "first": "Bin", 94 | "last": "Smuth", 95 | "email": "bensmith@allblacks.nz", 96 | }) 97 | 98 | _, err = db.NamedExec(`UPDATE user SET first_name=:first, last_name=:last WHERE first_name = 'Bin'`, 99 | map[string]interface{}{ 100 | "first": "Ben", 101 | "last": "Smith", 102 | "email": "bensmith@allblacks.nz", 103 | }) 104 | 105 | // Selects Mr. Smith from the database 106 | rows, err = db.NamedQuery(`SELECT * FROM user WHERE first_name=:fn`, map[string]interface{}{"fn": "Ben"}) 107 | for rows.Next() { 108 | err := rows.StructScan(&user) 109 | if err != nil { 110 | log.Fatalln(err) 111 | } 112 | fmt.Printf("Ben: %#v\n", user) 113 | } 114 | 115 | // Named queries can also use structs. Their bind names follow the same rules 116 | // as the name -> db mapping, so struct fields are lowercased and the `db` tag 117 | // is taken into consideration. 118 | rows, err = db.NamedQuery(`SELECT * FROM user WHERE first_name=:first_name`, jason) 119 | for rows.Next() { 120 | err := rows.StructScan(&user) 121 | if err != nil { 122 | log.Fatalln(err) 123 | } 124 | fmt.Printf("Jason: %#v\n", user) 125 | } 126 | 127 | // fetch one column from the db 128 | rows2, _ := db.Query("SELECT first_name FROM user WHERE last_name = 'Smith'") 129 | // iterate over each row 130 | for rows2.Next() { 131 | var firstName string // if nullable, use the NullString type 132 | err = rows2.Scan(&firstName) 133 | fmt.Printf("Ben: %s\n", firstName) 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /strings.go: -------------------------------------------------------------------------------- 1 | // The standard library's `strings` package provides many 2 | // useful string-related functions. Here are some examples 3 | // to give you a sense of the package. 4 | package main 5 | 6 | import "fmt" 7 | import "strings" 8 | 9 | // alias `fmt.Println` to a shorter name 10 | var p = fmt.Println 11 | 12 | func main() { 13 | fmt.Println("--- strings ---") 14 | 15 | p("Len: ", len("hello")) 16 | p("Char:", "hello"[1]) 17 | 18 | spacey := " x " 19 | 20 | fmt.Println(strings.Fields("Hello GoLang, my friend")) 21 | fmt.Println("Hello " + "GoLang, my friend") 22 | fmt.Print("Before TrimSpace: '" + spacey + "'") 23 | fmt.Println(" After TrimSpace: '" + strings.TrimSpace(spacey) + "'") 24 | 25 | // Here's a sample of the functions available in 26 | // `strings`. Note that these are all functions from 27 | // package, not methods on the string object itself. 28 | // This means that we need pass the string in question 29 | // as the first argument to the function. 30 | fmt.Println("Contains: ", strings.Contains("test", "es")) 31 | fmt.Println("Count: ", strings.Count("test", "t")) 32 | fmt.Println("HasPrefix: ", strings.HasPrefix("test", "te")) 33 | fmt.Println("HasSuffix: ", strings.HasSuffix("test", "st")) 34 | fmt.Println("Index: ", strings.Index("test", "e")) 35 | fmt.Println("Join: ", strings.Join([]string{"a", "b"}, "-")) 36 | fmt.Println("Repeat: ", strings.Repeat("a", 5)) 37 | fmt.Println("Replace: ", strings.Replace("foo", "o", "0", -1)) 38 | fmt.Println("Replace: ", strings.Replace("foo", "o", "0", 1)) 39 | fmt.Println("Split: ", strings.Split("a-b-c-d-e", "-")) 40 | fmt.Println("ToLower: ", strings.ToLower("TEST")) 41 | fmt.Println("ToUpper: ", strings.ToUpper("test")) 42 | fmt.Println() 43 | 44 | // You can find more functions in the [`strings`](http://golang.org/pkg/strings/) 45 | // package docs. 46 | 47 | convertByteArrayToString() 48 | convertStringToByteArray() 49 | stringBytesPrintingExample() 50 | } 51 | 52 | func convertByteArrayToString() { 53 | fmt.Println("======== convertByteArrayToString") 54 | src := []byte{'h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd'} 55 | 56 | str := string(src[:]) 57 | fmt.Println(str) 58 | 59 | str = string(src) 60 | fmt.Println(str) 61 | } 62 | 63 | func convertStringToByteArray() { 64 | fmt.Println("======== convertStringToByteArray") 65 | str := "hello world" 66 | byteArray := []byte(str) 67 | 68 | // that's it, all the rest is display 69 | fmt.Printf("String as is: %s\n", str) 70 | fmt.Printf("String as hex: % x\n", str) 71 | fmt.Printf("as hex: % x\n", str) 72 | for i := 0; i < len(byteArray); i++ { 73 | fmt.Printf("%x ", byteArray[i]) 74 | } 75 | fmt.Println() 76 | } 77 | 78 | // stringPrinting from https://blog.golang.org/strings 79 | func stringBytesPrintingExample() { 80 | fmt.Println("======== stringBytesPrintingExample") 81 | const sample = "\xbd\xb2\x3d\xbc\x20\xe2\x8c\x98" 82 | 83 | fmt.Println("Println:") 84 | fmt.Println(sample) 85 | 86 | fmt.Println("Byte loop:") 87 | for i := 0; i < len(sample); i++ { 88 | fmt.Printf("%x ", sample[i]) 89 | } 90 | fmt.Printf("\n") 91 | 92 | fmt.Println("Print with %%x:") 93 | fmt.Printf("%x\n", sample) 94 | 95 | fmt.Println("Printf with % x:") 96 | fmt.Printf("% x\n", sample) 97 | 98 | fmt.Println("Printf with %q:") 99 | fmt.Printf("%q\n", sample) 100 | 101 | fmt.Println("Printf with %+q:") 102 | fmt.Printf("%+q\n", sample) 103 | } 104 | 105 | // iterate over the runes in a string 106 | func iterateOverString() { 107 | str := "Hello" 108 | fmt.Println() 109 | for i, roon := range str { 110 | fmt.Println(i, roon, string(roon)) 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /switch.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | import "os" 5 | import "log" 6 | 7 | var port int 8 | 9 | func main() { 10 | fmt.Println("Hello!") 11 | if len(os.Args) > 1 { 12 | switch os.Args[1] { 13 | case "chooseone": 14 | fmt.Println("You chose one") 15 | case "concat": 16 | fmt.Println("You chose concat") 17 | case "diff": 18 | fmt.Println("You chose diff") 19 | default: 20 | fmt.Println("Invalid argument:", os.Args[1]) 21 | } 22 | } 23 | fmt.Println("Done!") 24 | } 25 | 26 | func check(err error, action string) { 27 | if err != nil { 28 | log.Fatalf("Error %s: %v\n", action, err) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /template.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "os" 5 | templ "text/template" 6 | ) 7 | 8 | // Context is used by the template 9 | type Context struct { 10 | People []Person 11 | } 12 | 13 | // Person represents, well, a person 14 | type Person struct { 15 | Name string //exported field since it begins with a capital letter 16 | Senior bool 17 | } 18 | 19 | func main() { 20 | 21 | // With example 1 22 | t := templ.New("'With' Example 1") 23 | templ.Must(t.Parse(` 24 | With inline Variable:{{with $3 := "hello"}} {{$3}} {{end}}, with an inline variable 25 | `)) 26 | t.Execute(os.Stdout, nil) 27 | 28 | // With example 2 29 | t2 := templ.New("'With' Example 2") 30 | templ.Must(t2.Parse(` 31 | Current Value:{{with "mom"}} {{.}} {{end}}, with the current value 32 | `)) 33 | t2.Execute(os.Stdout, nil) 34 | 35 | // If-Else example 36 | tIfElse := templ.New("If Else Example") 37 | ctx := Person{Name: "Mary", Senior: false} 38 | tIfElse = templ.Must( 39 | tIfElse.Parse(` 40 | If-Else example:{{if eq $.Senior true }} Yes, {{.Name}} is a senior {{else}} No, {{.Name}} is not a senior {{end}} 41 | `)) 42 | tIfElse.Execute(os.Stdout, ctx) 43 | 44 | // Range example 45 | tRange := templ.New("Range Example") 46 | ctx2 := Context{People: []Person{Person{Name: "Mary", Senior: false}, Person{Name: "Joseph", Senior: true}}} 47 | tRange = templ.Must( 48 | tRange.Parse(` 49 | Range example: 50 | {{range $i, $x := $.People}} Name={{$x.Name}} Senior={{$x.Senior}} 51 | {{end}} 52 | 53 | `)) 54 | tRange.Execute(os.Stdout, ctx2) 55 | 56 | } 57 | -------------------------------------------------------------------------------- /time.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | ) 7 | 8 | // layout shows by example how the reference time should be represented. 9 | const layout = "Jan 2, 2006 at 3:04pm -0700 (MST)" 10 | const layout2 = "2006-01-02 15:04:05" 11 | 12 | func main() { 13 | //t := time.Date(2009, time.November, 10, 15, 0, 0, 0, time.Local) 14 | t := time.Now() 15 | fmt.Println(t.Format(layout2)) 16 | fmt.Println(t.UTC().Format(layout2)) 17 | } 18 | -------------------------------------------------------------------------------- /trapctlc.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "os/signal" 7 | "syscall" 8 | "time" // or "runtime" 9 | ) 10 | 11 | func cleanup() { 12 | fmt.Println("cleanup") 13 | } 14 | 15 | // Trap a Ctrl-C while sleeping 16 | func main() { 17 | c := make(chan os.Signal, 1) 18 | signal.Notify(c, os.Interrupt) 19 | signal.Notify(c, syscall.SIGTERM) 20 | go func() { 21 | <-c 22 | cleanup() 23 | os.Exit(1) 24 | }() 25 | 26 | for { 27 | fmt.Println("sleeping...") 28 | time.Sleep(10 * time.Second) // or runtime.Gosched() or similar per @misterbee 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /web/.gitignore: -------------------------------------------------------------------------------- 1 | web 2 | web.db 3 | -------------------------------------------------------------------------------- /web/README.md: -------------------------------------------------------------------------------- 1 | # Jon's example web application 2 | 3 | This example web application is written in Go (GoLang). It will be used as a starting point for any secure web applications that I build. 4 | 5 | ### Take it for a spin: 6 | * Run `go build` 7 | * Run `./web` 8 | * Open browser to `http://localhost:8080/auth` 9 | * notice how it redirects you to https (you will have to set an exception to accept the certificate) 10 | * when it asks for credentials enter `joe@example.com` and `supersecret` 11 | 12 | ### It currently does these things: 13 | * Redirects to https if http is used 14 | * When started, it builds/rebuilds a sqlite3 user table with example users and passwords 15 | * Provides a couple of resources (/info and /auth) 16 | * go to http://localhost:8080/auth 17 | * enter username: joe@example.com password: supersecret 18 | * Request handlers can be wrapped with Basic authentication checks 19 | 20 | ### Do Next 21 | * Absorb this: http://blog.8thlight.com/uncle-bob/2011/09/30/Screaming-Architecture.html 22 | * And this: http://blog.8thlight.com/uncle-bob/2012/08/13/the-clean-architecture.html 23 | * And then this: http://manuel.kiessling.net/2012/09/28/applying-the-clean-architecture-to-go-applications/ 24 | 25 | ### It will eventually do these things 26 | * Provide REST resources that: 27 | * return user data as JSON (from the user table) 28 | * inserts and updates data in the user table 29 | * An HTML5 Login web page 30 | * An HTML5 "Forgot Password" web page that sends an email with a reset link 31 | * A login monitor that prevents brute force password attacks by blocking account for 20 minutes after 10 invalid passwords are tried 32 | * An HTML5 index page that provides links to REST resources 33 | 34 | ### Pretty important to have 35 | * Permission and role tables 36 | * A user resource (GET, PUT, POST, DELETE) 37 | * An HTML5 web page to display all users (if user has the right permission) 38 | * An HTML5 web page to edit a user (user can always edit his own user) 39 | * An HTML5 web page to create a user 40 | 41 | ### Libraries used: 42 | * net/http 43 | * http://godoc.org/golang.org/x/crypto/bcrypt - for hash/salting passwords 44 | * https://github.com/jmoiron/sqlx - maps rows to objects 45 | * https://github.com/mattes/migrate - handles database migration 46 | * https://github.com/stvp/assert - assertions for testing 47 | * Bootstrap for styling web pages 48 | * Angular JS for browser->server interactions 49 | 50 | ### I may use these libraries: 51 | * httprouter: go get -u github.com/julienschmidt/httprouter 52 | * codegansta/negroni: go get -u github.com/codegangsta/negroni 53 | * go-flags: go get -u github.com/jessevdk/go-flags 54 | 55 | ### How to recreate the DB using the command-line 56 | migrate -url sqlite3://web.db -path ./migrations reset 57 | migrate -url sqlite3://web.db -path ./migrations up 58 | 59 | ### Future ideas for startup 60 | * `./web --url 'sqlite3://database.db' --https-cert=https-cert.pem --https-key=https-key.pem` 61 | * create a web.sh script to start it up with the above command 62 | -------------------------------------------------------------------------------- /web/api/api.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | // "io/ioutil" 7 | "net/http" 8 | ) 9 | 10 | func TestApiPackage(w http.ResponseWriter, r *http.Request) { 11 | var ryanne map[string]string 12 | switch r.Method { 13 | case "GET": 14 | buf, _ := json.Marshal(&ryanne) 15 | w.Write(buf) 16 | case "PUT": 17 | // buf, _ := ioutil.ReadAll(r.Body) 18 | // json.Unmarshal(buf, &ryanne) 19 | default: 20 | w.WriteHeader(400) 21 | } 22 | fmt.Println("api package") 23 | } 24 | -------------------------------------------------------------------------------- /web/api/user-api.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "encoding/json" 5 | "net/http" 6 | ) 7 | 8 | // Map method to function 9 | //var userApis map[string](function(w http.ResponseWriter, r *http.Request) error) 10 | 11 | // UserHandler handles user requests 12 | func UserHandler(w http.ResponseWriter, r *http.Request) error { 13 | w.Header().Set("Content-Type", "text/json; charset=utf-8") 14 | 15 | switch r.Method { 16 | case "GET": 17 | return userApiGet(w, r) 18 | case "PUT": 19 | return userApiPut(w, r) 20 | default: 21 | // TODO: Return an error instead! 22 | w.WriteHeader(400) 23 | } 24 | 25 | return nil 26 | } 27 | 28 | func userApiGet(w http.ResponseWriter, r *http.Request) error { 29 | // buf, _ := ioutil.ReadAll(r.Body()) 30 | // var ryanne map[string]string 31 | // json.Unmarshal(buf, &ryanne) 32 | 33 | res1D := &Response1{ 34 | Page: 101, 35 | Fruits: []string{"apple", "peach", "pear"}, 36 | } 37 | res1B, _ := json.Marshal(res1D) 38 | w.Write(res1B) 39 | return nil 40 | } 41 | 42 | func userApiPut(w http.ResponseWriter, r *http.Request) error { 43 | http.Error(w, "Method not allowed", 405) 44 | return nil 45 | } 46 | 47 | func userApiPost(w http.ResponseWriter, r *http.Request) error { 48 | http.Error(w, "Method not allowed", 405) 49 | return nil 50 | } 51 | 52 | func userApiDelete(w http.ResponseWriter, r *http.Request) error { 53 | http.Error(w, "Method not allowed", 405) 54 | return nil 55 | } 56 | 57 | // 58 | // 59 | // 60 | type Response1 struct { 61 | Page int 62 | Fruits []string 63 | } 64 | -------------------------------------------------------------------------------- /web/config.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) 2015 Jon Carlson. All rights reserved. 3 | // Use of this source code is governed by the MIT 4 | // license that can be found in the LICENSE file. 5 | // 6 | package main 7 | 8 | import ( 9 | "bufio" 10 | "fmt" 11 | "os" 12 | "regexp" 13 | "strconv" 14 | "strings" 15 | ) 16 | 17 | var propertySplittingRegex = regexp.MustCompile(`\s*=\s*`) 18 | var commaSplittingRegex = regexp.MustCompile(`\s*,\s*`) 19 | 20 | // processConfigFile reads the properties in the given file and assigns them to global variables 21 | func processConfigFile(fileName string) { 22 | if verbose { 23 | fmt.Println("Processing config file:", fileName) 24 | } 25 | props, err := _readConfigFile(fileName) 26 | if err != nil { 27 | fmt.Fprintf(os.Stderr, "Error reading config file "+fileName+":", err) 28 | os.Exit(1) 29 | } 30 | _processConfig(props) 31 | } 32 | 33 | func _processConfig(props map[string]string) { 34 | 35 | var intVal int 36 | var strVal string 37 | var boolVal bool 38 | var ok bool 39 | 40 | if boolVal, ok = _boolValue(props, "verbose"); ok { 41 | verbose = boolVal 42 | if verbose { 43 | fmt.Println("verbose:", verbose) 44 | } 45 | } 46 | 47 | // Configurable variables 48 | //var ( 49 | // version = "0.1" 50 | // verbose = false 51 | // dbType = "sqlite3" 52 | // dbUrl = "web.db" 53 | // migrateSqlPath = "./migrations" 54 | // httpPort = 8080 55 | // httpsPort = 8443 56 | // certFile = "https-cert.pem" 57 | // keyFile = "https-key.pem" 58 | // //certFile = "" 59 | // //keyFile = "" 60 | //) 61 | if strVal, ok = props["dbType"]; ok { 62 | dbType = strVal 63 | fmt.Println("dbType:", dbType) 64 | } 65 | 66 | if strVal, ok = props["dbUrl"]; ok { 67 | dbUrl = strVal 68 | fmt.Println("dbUrl:", dbUrl) 69 | } 70 | 71 | if strVal, ok = props["migrateSqlPath"]; ok { 72 | migrateSqlPath = strVal 73 | fmt.Println("migrateSqlPath:", migrateSqlPath) 74 | } 75 | 76 | if intVal, ok = _intValue(props, "httpPort"); ok { 77 | httpPort = intVal 78 | fmt.Println("httpPort:", httpPort) 79 | } 80 | 81 | if intVal, ok = _intValue(props, "httpsPort"); ok { 82 | httpsPort = intVal 83 | fmt.Println("httpsPort:", httpsPort) 84 | } 85 | 86 | if strVal, ok = props["certFile"]; ok { 87 | certFile = strVal 88 | fmt.Println("certFile:", certFile) 89 | } 90 | 91 | if strVal, ok = props["keyFile"]; ok { 92 | keyFile = strVal 93 | fmt.Println("keyFile:", keyFile) 94 | } 95 | 96 | if strVal, ok = props["mailHost"]; ok { 97 | mailHost = strVal 98 | fmt.Println("mailHost:", mailHost) 99 | } 100 | 101 | if intVal, ok = _intValue(props, "mailPort"); ok { 102 | mailPort = intVal 103 | fmt.Println("mailPort:", mailPort) 104 | } 105 | 106 | if strVal, ok = props["mailUsername"]; ok { 107 | mailUsername = strVal 108 | fmt.Println("mailUsername:", mailUsername) 109 | } 110 | if strVal, ok = props["mailPassword"]; ok { 111 | mailPassword = strVal 112 | fmt.Println("mailPassword: *******") 113 | } 114 | if strVal, ok = props["mailFrom"]; ok { 115 | mailFrom = strVal 116 | fmt.Println("mailFrom:", mailFrom) 117 | } 118 | if strVal, ok = props["mailTo"]; ok { 119 | mailTo = commaSplittingRegex.Split(strVal, -1) 120 | fmt.Println("mailTo:", mailTo) 121 | } 122 | 123 | } 124 | 125 | // generateConfigurationFile prints an example configuration file to standard output 126 | func generateConfigurationFile() { 127 | fmt.Println(`# web configuration file. Uncomment the values you change: 128 | # ====================== 129 | # Web app configuration 130 | # ====================== 131 | 132 | # Just sqlite3 is tested, but others could be supported too 133 | dbType = sqlite3 134 | 135 | # The name of the sqlite3 file 136 | dbUrl = myapp.db 137 | 138 | # Directory that holds the database migration SQL 139 | migrateSqlPath = ./migrations 140 | 141 | # Ports 142 | httpPort = 8080 143 | httpsPort = 8443 144 | 145 | # SSL files 146 | certFile = https-cert.pem 147 | keyFile = https-key.pem 148 | 149 | # =================== 150 | # Mail configuration 151 | # =================== 152 | 153 | # mailHost = localhost 154 | # mailPort = 25 155 | # mailUsername = 156 | # mailPassword = 157 | 158 | # An email address to be used as the "from" address in emails 159 | # mailFrom = 160 | 161 | # A comma-separated list of email addresses that will receive alert emails 162 | # mailTo = 163 | `) 164 | } 165 | 166 | // readLinesChannel reads a text file line by line into a channel. 167 | func _readLinesChannel(filePath string) (<-chan string, error) { 168 | c := make(chan string) 169 | file, err := os.Open(filePath) 170 | if err != nil { 171 | return nil, err 172 | } 173 | go func() { 174 | defer file.Close() 175 | scanner := bufio.NewScanner(file) 176 | for scanner.Scan() { 177 | c <- scanner.Text() 178 | } 179 | close(c) 180 | }() 181 | return c, nil 182 | } 183 | 184 | // readConfigFile reads name-value pairs from a properties file 185 | func _readConfigFile(fileName string) (map[string]string, error) { 186 | c, err := _readLinesChannel(fileName) 187 | if err != nil { 188 | return nil, err 189 | } 190 | 191 | properties := make(map[string]string) 192 | for line := range c { 193 | line = strings.TrimSpace(line) 194 | if strings.HasPrefix(line, "#") { 195 | // Ignore this line 196 | } else if len(line) == 0 { 197 | // Ignore this line 198 | } else { 199 | parts := propertySplittingRegex.Split(line, 2) 200 | properties[parts[0]] = parts[1] 201 | } 202 | } 203 | 204 | return properties, nil 205 | } 206 | 207 | func _intValue(props map[string]string, name string) (int, bool) { 208 | if value, ok := props[name]; ok { 209 | _intValue, err := strconv.Atoi(value) 210 | if err != nil { 211 | fmt.Fprintf(os.Stderr, "Invalid integer config value for %s: %s \n", name, value) 212 | return 0, false 213 | } 214 | return _intValue, true 215 | } 216 | 217 | return 0, false 218 | } 219 | 220 | func _boolValue(props map[string]string, name string) (bool, bool) { 221 | if value, ok := props[name]; ok { 222 | _boolValue, err := strconv.ParseBool(value) 223 | if err != nil { 224 | fmt.Fprintf(os.Stderr, "Invalid bool config value for %s: %s \n", name, value) 225 | return false, false 226 | } 227 | return _boolValue, true 228 | } 229 | return false, false 230 | } 231 | -------------------------------------------------------------------------------- /web/data/.gitignore: -------------------------------------------------------------------------------- 1 | __deleteme.db 2 | -------------------------------------------------------------------------------- /web/data/auth.go: -------------------------------------------------------------------------------- 1 | // 2 | // Provides authentication methods 3 | // 4 | package data 5 | 6 | import ( 7 | "database/sql" 8 | "errors" 9 | "github.com/jmoiron/sqlx" 10 | "github.com/joncrlsn/misc" 11 | "log" 12 | ) 13 | 14 | const ( 15 | findUserIdAndPasswordByEmail = "SELECT user_id, password FROM user WHERE email = $1" 16 | findUserIdByEmail = "SELECT user_id FROM user WHERE email = $1" 17 | updatePasswordByUserId = "UPDATE user SET password = $1 WHERE user_id = $2" 18 | ) 19 | 20 | func SavePassword(db *sqlx.DB, email string, password string) (err error) { 21 | // Find the userId for the given email 22 | var userId int 23 | err = db.Get(&userId, findUserIdByEmail, email) 24 | if err != nil { 25 | return err 26 | } 27 | if userId == 0 { 28 | err = errors.New("email not found") 29 | } else { 30 | var hashedPassword string 31 | hashedPassword, err = misc.HashPassword(password) 32 | if err == nil { 33 | _, err = db.Exec(updatePasswordByUserId, hashedPassword, userId) 34 | } 35 | } 36 | 37 | return err // err may be nil 38 | } 39 | 40 | // When successful, the returned user will have a non-negative UserId. 41 | func Authenticate(db *sqlx.DB, email string, testPassword string) (user User, err error) { 42 | // Find the hashed password for the given email 43 | var rows *sql.Rows 44 | rows, err = db.Query(findUserIdAndPasswordByEmail, email) 45 | if err != nil { 46 | return 47 | } 48 | for rows.Next() { 49 | var userId int 50 | var hashedPassword sql.NullString 51 | err = rows.Scan(&userId, &hashedPassword) 52 | if err != nil { 53 | log.Println("Error in Scan", err) 54 | } else if userId == 0 { 55 | //fmt.Println("userId == 0. user not found") 56 | } else if hashedPassword.Valid { 57 | if misc.ComparePassword(testPassword, hashedPassword.String) { 58 | // We found a match so find the user 59 | user, err = UserFindById(db, userId) 60 | } else { 61 | log.Println("Password mismatched for email", email) 62 | } 63 | } 64 | rows.Close() 65 | } 66 | 67 | // return the user and error 68 | return 69 | } 70 | -------------------------------------------------------------------------------- /web/data/auth_test.go: -------------------------------------------------------------------------------- 1 | package data 2 | 3 | import ( 4 | "github.com/jmoiron/sqlx" 5 | "github.com/mattes/migrate/migrate" 6 | "github.com/stvp/assert" 7 | "testing" 8 | ) 9 | 10 | func Test_SavePassword(t *testing.T) { 11 | db := resetDb(t) 12 | 13 | // Save a new password 14 | err := SavePassword(db, "joncrlsn@gmail.com", "new-secret") 15 | assert.Nil(t, err, "error saving password for email %v", err) 16 | 17 | // See if we can authenticate using the password we just saved 18 | user, err2 := Authenticate(db, "joncrlsn@gmail.com", "new-secret") 19 | assert.Nil(t, err2, "error authenticating valid email address %v", err) 20 | assert.Equal(t, "joncrlsn@gmail.com", user.Email) 21 | 22 | // try an invalid email 23 | err = SavePassword(db, "xxxx@yyyy.com", "whatever") 24 | assert.NotNil(t, err, "Did not receive error when saving password for invalid email ") 25 | } 26 | 27 | func Test_Authenticate(t *testing.T) { 28 | db := resetDb(t) 29 | 30 | user, err := Authenticate(db, "xxxx@yyyy.com", "super-duper-secret") 31 | assert.Nil(t, err, "error authenticating bogus email address %v", err) 32 | assert.Equal(t, 0, user.UserId) 33 | 34 | user, err = Authenticate(db, "joncrlsn@gmail.com", "super-duper-secret") 35 | assert.Nil(t, err, "error authenticating valid email") 36 | assert.NotEqual(t, 0, user.UserId) 37 | 38 | user, err = Authenticate(db, "joncrlsn@gmail.com", "wrong-password") 39 | assert.Nil(t, err, "error authenticating valid email and wrong password") 40 | assert.Equal(t, 0, user.UserId) 41 | } 42 | 43 | func resetDb(t *testing.T) (db *sqlx.DB) { 44 | // Synchronously setup the DB or migrate it to the latest 45 | allErrors, ok := migrate.ResetSync("sqlite3://"+dbName, "../migrations") 46 | if !ok { 47 | t.Fatal("Error resetting test db", allErrors) 48 | } 49 | 50 | db = sqlx.MustConnect("sqlite3", dbName) 51 | 52 | return db 53 | } 54 | -------------------------------------------------------------------------------- /web/data/user.go: -------------------------------------------------------------------------------- 1 | // 2 | // 3 | // 4 | package data 5 | 6 | import ( 7 | //"database/sql" 8 | "github.com/jmoiron/sqlx" 9 | //_ "github.com/mattn/go-sqlite3" 10 | //"github.com/joncrlsn/misc" 11 | ) 12 | 13 | type User struct { 14 | UserId int `db:"user_id"` 15 | FirstName string `db:"first_name"` 16 | LastName string `db:"last_name"` 17 | Email string 18 | } 19 | 20 | const ( 21 | userInsertSql = `INSERT INTO user (first_name, last_name, email) VALUES (:FirstName, :LastName, :Email);` 22 | userUpdateSql = `UPDATE user SET first_name = :FirstName, last_name = :LastName, email = :Email) WHERE user_id = :UserId;` 23 | userFindByEmailSql = `SELECT user_id, first_name, last_name, email FROM user WHERE email = $1;` 24 | userFindByIdSql = `SELECT user_id, first_name, last_name, email FROM user WHERE user_id = $1;` 25 | ) 26 | 27 | // UserSave either inserts or updates the given User object to the database. 28 | // If there is a userId, an update is done. Otherwise, an insert. 29 | func UserSave(db *sqlx.DB, user User) error { 30 | var err error 31 | if user.UserId == 0 { 32 | _, err = db.NamedExec(userInsertSql, user) 33 | } else if user.UserId > 0 { 34 | _, err = db.NamedExec(userUpdateSql, user) 35 | } 36 | return err 37 | } 38 | 39 | // UserFindByEmail returns an array User instances 40 | func UserFindByEmail(db *sqlx.DB, email string) ([]User, error) { 41 | users := []User{} 42 | err := db.Select(&users, userFindByEmailSql, email) 43 | return users, err 44 | } 45 | 46 | // UserFindById returns one User for the given id. UserId will be 0 if none found. 47 | func UserFindById(db *sqlx.DB, userId int) (User, error) { 48 | var user User 49 | err := db.Get(&user, userFindByIdSql, userId) 50 | return user, err 51 | } 52 | -------------------------------------------------------------------------------- /web/data/user_test.go: -------------------------------------------------------------------------------- 1 | package data 2 | 3 | import ( 4 | "github.com/jmoiron/sqlx" 5 | "github.com/mattes/migrate/migrate" 6 | "github.com/stvp/assert" 7 | "testing" 8 | ) 9 | 10 | const ( 11 | dbName = "__deleteme.db" 12 | ) 13 | 14 | func Test_UserFindByEmail(t *testing.T) { 15 | // Synchronously setup the DB or migrate it to the latest 16 | allErrors, ok := migrate.ResetSync("sqlite3://"+dbName, "../migrations") 17 | if !ok { 18 | t.Fatal("Error resetting test db", allErrors) 19 | } 20 | 21 | // 22 | db, err := sqlx.Connect("sqlite3", dbName) 23 | if err != nil { 24 | t.Fatal("Error connectin to db", err) 25 | } 26 | 27 | users, err := UserFindByEmail(db, "joncrlsn@gmail.com") 28 | assert.Nil(t, err, "error finding by email") 29 | assert.Equal(t, 1, len(users), "Expected 1 user returned") 30 | assert.Equal(t, "joncrlsn@gmail.com", users[0].Email, "Wrong email returned from db") 31 | } 32 | -------------------------------------------------------------------------------- /web/db.go: -------------------------------------------------------------------------------- 1 | // 2 | // Sets up the database connection and ensure the schema is migrated. 3 | // 4 | // Copyright (c) 2015 Jon Carlson. All rights reserved. 5 | // Use of this source code is governed by the MIT 6 | // license that can be found in the LICENSE file. 7 | // 8 | package main 9 | 10 | import ( 11 | "github.com/jmoiron/sqlx" 12 | "github.com/mattes/migrate/migrate" 13 | "log" 14 | ) 15 | 16 | func setupDb(dbType string, dbName string, migrateSqlPath string) (db *sqlx.DB, err error) { 17 | log.Println("=== Checking schema version ===") 18 | dbUrl := dbType + "://" + dbName 19 | 20 | // Synchronously migrate the database up if needed. 21 | allErrors, ok := migrate.ResetSync(dbUrl, migrateSqlPath) 22 | if !ok { 23 | log.Println("Error migrating schema", allErrors) 24 | return nil, nil // Program should stop 25 | } 26 | 27 | // Get database connection to return 28 | db, err = sqlx.Connect(dbType, dbName) 29 | if err != nil { 30 | log.Println("Error connecting to db", err) 31 | return nil, err 32 | } 33 | 34 | // success 35 | return db, nil 36 | } 37 | -------------------------------------------------------------------------------- /web/http.go: -------------------------------------------------------------------------------- 1 | // 2 | // Sets up the URL routing and starts the web server 3 | // 4 | // Copyright (c) 2015 Jon Carlson. All rights reserved. 5 | // Use of this source code is governed by the MIT 6 | // license that can be found in the LICENSE file. 7 | // 8 | package main 9 | 10 | import ( 11 | "fmt" 12 | "github.com/joncrlsn/go-examples/web/api" 13 | "github.com/joncrlsn/go-examples/web/data" 14 | "log" 15 | "net/http" 16 | "strconv" 17 | "strings" 18 | ) 19 | 20 | // Taken from http://blog.golang.org/error-handling-and-go 21 | // errorHandler adds a ServeHttp method to every errorHandler function 22 | type errorHandler func(http.ResponseWriter, *http.Request) error 23 | 24 | // Adds a ServeHttp method to every errorHandler function 25 | func (fn errorHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { 26 | if err := fn(w, r); err != nil { 27 | log.Println("Error handling request", err) 28 | http.Error(w, "Internal Server Error. Check logs for actual error", 500) 29 | } 30 | } 31 | 32 | // startWebServer does as the name implies. If a certFile and keyFile are given then 33 | // the HTTPS port is also used and HTTP is redirected to HTTPS. 34 | func startWebServer(httpPort int, httpsPort int, certFile string, keyFile string) { 35 | 36 | // Any static files in the www dir will be served as-is 37 | http.Handle("/", http.FileServer(http.Dir("./www"))) 38 | fmt.Println(" /login.html is self-explanatory\n (try https://localhost:8080)") 39 | 40 | // REST/HTTP API handlers 41 | http.Handle("/api/user", errorHandler(_authBasic(api.UserHandler))) 42 | fmt.Println(" /api/user returns JSON about users in the system. requires authentication\n (use joe@example.com/supersecret for credentials)") 43 | 44 | // Setup business logic handlers 45 | http.Handle("/info", errorHandler(_viewInfo)) 46 | http.Handle("/auth", errorHandler(_authBasic(_viewInfo))) 47 | fmt.Println(" /info returns information about the request\n (try https://localhost:8443/info?x=123&y=456 )") 48 | fmt.Println(" /auth same as above, but requires authentication\n (use joe@example.com/supersecret for credentials)") 49 | 50 | // 51 | // Start listening 52 | // 53 | log.Println("=== Starting web server ===") 54 | 55 | // If we were given a certificate file, listen for HTTP but redirect to HTTPS 56 | if len(certFile) > 0 && httpsPort > 0 { 57 | // Handle everything on HTTPS 58 | go func() { 59 | log.Printf("Redirecting HTTP to HTTPS (port %v redirects to %v)", httpPort, httpsPort) 60 | log.Fatal(http.ListenAndServe(":"+strconv.Itoa(httpPort), http.HandlerFunc(_redirectToHttps(httpPort, httpsPort)))) 61 | }() 62 | 63 | log.Println("Listening for HTTPS on port", httpsPort) 64 | log.Fatal(http.ListenAndServeTLS(":"+strconv.Itoa(httpsPort), certFile, keyFile, nil)) 65 | return 66 | } 67 | 68 | // In this case, we'll go insecure (no HTTPS certificate was given) 69 | log.Println("Listening for HTTP on port", httpPort) 70 | log.Fatal(http.ListenAndServe(":"+strconv.Itoa(httpPort), nil)) 71 | } 72 | 73 | // _authBasic wraps a request handler with another one that requires BASIC HTTP authentication 74 | func _authBasic(handler func(http.ResponseWriter, *http.Request) error) func(w http.ResponseWriter, req *http.Request) error { 75 | return func(w http.ResponseWriter, req *http.Request) error { 76 | // 77 | // Ensure request has an "Authorization" header (needed for "Basic" authentication) 78 | // (That header is misnamed. It should be Authentication, but I don't make the specs) 79 | // (Authorization makes sure the authenticated user is permitted to do the action) 80 | // 81 | username, password, ok := req.BasicAuth() 82 | if !ok { 83 | // Request the "Authorization" header 84 | w.Header().Set("WWW-Authenticate", `Basic realm="go-example-web"`) 85 | http.Error(w, "Access Denied", http.StatusUnauthorized) 86 | return nil 87 | } 88 | 89 | // 90 | // Ensure the given credentials match one of the users in our database 91 | // 92 | user, err := data.Authenticate(db, username, password) 93 | if err != nil { 94 | log.Println("Error authenticating user", username, err) 95 | return err 96 | } 97 | if user.UserId == 0 { 98 | // User authentication failed 99 | w.Header().Set("WWW-Authenticate", `Basic realm="go-example-web"`) 100 | http.Error(w, "Access Denied", http.StatusUnauthorized) 101 | return nil 102 | } 103 | 104 | // 105 | // The credentials match, so run the wrapped handler 106 | // 107 | return handler(w, req) 108 | } 109 | } 110 | 111 | // _redirectToHttps returns a function that redirects anything on the http port over to the https port. 112 | // We have to wrap the function in a function so we can dynamically provide the http and https ports. 113 | func _redirectToHttps(httpPort int, httpsPort int) func(w http.ResponseWriter, req *http.Request) { 114 | return func(w http.ResponseWriter, req *http.Request) { 115 | newHost := strings.Replace(req.Host, strconv.Itoa(httpPort), strconv.Itoa(httpsPort), 1) 116 | newUrl := fmt.Sprintf("https://%s/%s", newHost, req.RequestURI) 117 | http.Redirect(w, req, newUrl, http.StatusMovedPermanently) 118 | } 119 | } 120 | 121 | // _viewInfo is an example handler that returns info about the request in HTML format 122 | func _viewInfo(w http.ResponseWriter, r *http.Request) error { 123 | if r.Method == "GET" { 124 | //fmt.Fprintf(w, "Hello, %q", html.EscapeString(r.URL.Path)) 125 | fmt.Fprintf(w, ` 126 | r.Method = %s
127 | r.RequestURI = %s
128 | r.Host = %s
129 | r.Header = %v
130 | r.RemoteAddr = %s
131 | r.MultipartForm = %v
132 | r.PostForm = %v
133 | r.Form = %v
134 |
135 | r.URL = %s
136 | r.URL.Host = %s
137 | r.URL.Path = %s
138 | r.URL.RawQuery = %s
139 | r.URL.Scheme = %s
140 | r.URL.Opaque = %s
141 | r.URL.User = %s
142 | r.URL.Fragment = %s
143 |
144 | r.Header.Get("Authorization") = %s
145 | `, r.Method, r.RequestURI, r.Host, r.Header, r.RemoteAddr, r.MultipartForm, r.PostForm, r.Form, r.URL, r.URL.Host, r.URL.Path, r.URL.RawQuery, r.URL.Scheme, r.URL.Opaque, r.URL.User, r.URL.Fragment, r.Header.Get("Authorization")) 146 | } 147 | return nil 148 | } 149 | -------------------------------------------------------------------------------- /web/https-cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIC+zCCAeWgAwIBAgIQVNoXWWSi8LciDrbiFWwp8DALBgkqhkiG9w0BAQswEjEQ 3 | MA4GA1UEChMHQWNtZSBDbzAeFw0xNTAzMDcxNzMyMjhaFw0xNjAzMDYxNzMyMjha 4 | MBIxEDAOBgNVBAoTB0FjbWUgQ28wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK 5 | AoIBAQC4vrC4fkp0ufl0Fb3TA16A+vfoqkDkcFMWI4kV4JhRANVg6sQAFyteVVSV 6 | 7oYzKo/ia7voYrL+ycXEm1jckaYN6HXTm2xZHFCMSaV8oU8kokyWJTDNSET1WNEE 7 | lPM9quwB+P59WJiUpwj/FbI4bmxn8ULDo1LzwXBQ+7WQSvL0OUs2up2hfqR93lcF 8 | 2t4oLQCqZ7ETsf47ic+jj6DSHzaFC654rmXYTVkX1aZdRUjfxSeFSHMjvRsKOCFC 9 | N/shorbVLZa2wqlSTDogJ4+AjF4xeGS7lmc6ZSa9yOfrtWRawzDsrcqeaWtA7PUz 10 | /BNWNEPX18Cp9Z/yor+VnqJTGl95AgMBAAGjUTBPMA4GA1UdDwEB/wQEAwIAoDAT 11 | BgNVHSUEDDAKBggrBgEFBQcDATAMBgNVHRMBAf8EAjAAMBoGA1UdEQQTMBGCCWxv 12 | Y2FsaG9zdIcEfwAAATALBgkqhkiG9w0BAQsDggEBAEl7Z/7OgyjJtu4uDPk1aLDR 13 | B7iUft4cQt7b0v/9taWNGcorQFw9YLS+jPsmCWcK0zPW6X71U11LTQBJrYW3SQMP 14 | 5DJI+HNlly9j6TLewj8pkEL3/BD78/XX+oEjj6R5ft+shDcX3pkt9B8fDJ+2sreR 15 | jNhoPeSTY1X4ckoSNY8LIMHjrTJFN07ym91obCfqsg7maLp2bj+qGvJ3aQmRbxWd 16 | p3hzL7zIwnj2NWJtsrkHkzJ20Ty3wGtzesrgM09DS4MK8HxzKroTEiaVBUHemXpA 17 | kBqyO97s2OFZFieaxdz89nNLCIOXXD3peH6U02EjbOysiWrabFwtcAmCgdmF8RI= 18 | -----END CERTIFICATE----- 19 | -------------------------------------------------------------------------------- /web/https-key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEowIBAAKCAQEAuL6wuH5KdLn5dBW90wNegPr36KpA5HBTFiOJFeCYUQDVYOrE 3 | ABcrXlVUle6GMyqP4mu76GKy/snFxJtY3JGmDeh105tsWRxQjEmlfKFPJKJMliUw 4 | zUhE9VjRBJTzParsAfj+fViYlKcI/xWyOG5sZ/FCw6NS88FwUPu1kEry9DlLNrqd 5 | oX6kfd5XBdreKC0AqmexE7H+O4nPo4+g0h82hQuueK5l2E1ZF9WmXUVI38UnhUhz 6 | I70bCjghQjf7IaK21S2WtsKpUkw6ICePgIxeMXhku5ZnOmUmvcjn67VkWsMw7K3K 7 | nmlrQOz1M/wTVjRD19fAqfWf8qK/lZ6iUxpfeQIDAQABAoIBADA6r/3qElv4tRPG 8 | HUE6LvCzFAcsczZv0HEGI+KPOJRlCE992l8/rTW6RxPBKk2vPdLZVzvqkFoNqNCT 9 | 0ZX7fANDfYcZmyaESs7k5wvrPLMOn7nOybe9tyrp3d85V2rw9R3qt91XRLYCCUo2 10 | islKoohcJpbWS3CRPlYV8CdUOarh3FviDEiSMQdBxi0HbUyok7i1Q7e7DghSaOcY 11 | 3C0zYaU+QXsCS+jCM1uN73YlQo7ZRHgGEZ0IRyFjZBgD4VL/pufxFo2wh6AZTqeX 12 | oeyOSN3BeMCEWiZ/7O1ZYB3HN7a6A7+Jp2s5mp2nasB5ZCrcTFgJfNaXQIepNL2C 13 | ycDpZXUCgYEA6Qxf4iRZbWQK5SEjpd6ySyHSFoy4silIyUJFa2v6gKRxqWdPJoGG 14 | BNkKQ9hQLNno5tWGc3XeSEd+LcqhdtrPYDMr8huNYmyRqhm6EQzqMeRRjzvwvq+h 15 | SmU94rUPJ2LxivkMlDEjAnpDTd/92BO8Pq+aHkPjFPdotfXsW2+TOLMCgYEAyvB8 16 | osLKXdkZdvgCQPqr8j+nzpz1K8LDuOywCrlUJSWgfObSKeGh72JXhuhrgBAoTeT5 17 | Bc/8xsubRRk7fg3NZkpDnzU/8NYfnFN7dijQ4YBVrNg5z5Uacc5XYGFJ4T8ThdFu 18 | +5wMdd1cZOjTpwiV3jf/y2z/t8BpW/QvFBB5ZSMCgYB0+kEmwhghUJYnbPr3x1C3 19 | ZcKOjxQmWZKvcxRlV03mRKTI45JXQayDwm3koC+eU8MUGxnh2sg4f6pWUd+6SRrO 20 | ruDgJfRR8y0qtL8wz8q+QFywCOZO0Nmk/iySH0/79S5JE7m8qO1p0PT3ofdI/p7+ 21 | 2CYECw9w9r8YltiNDPRZAQKBgBKvRapuqNcvL+sGNp7o9X/eYILjbKdUzVRvZl32 22 | ZkrtRfN6BQuaHKC/uzNOnucxlaRmWo8wcSOUh307L4ERoEG0wLkd2/8+l7Z68TkV 23 | 1PWOLVU2q0vzfHXR+7WB/51fg1qkx8Uevv7zHJil3ybX4YSOP1zpcjnm20x+FP+R 24 | 7LtlAoGBAM+DB4t87yKipJGBUBRQ5UohH81IySO+4fdsIq0wzpTB3LjTnWiDvPlf 25 | AREc8uI4Cn4nINRW0rXyz8C0uPSjvtUHiy0EBbaOHr/zYWs1ojugCalfdNwyH+PV 26 | uAM6j0OFHuO2ECGWRNXIRAftmiZWBZYFGieCk5FKPctDkR/3qRcm 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /web/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/jmoiron/sqlx" 6 | flag "github.com/ogier/pflag" 7 | "log" 8 | "os" 9 | ) 10 | 11 | // Configurable variables 12 | var ( 13 | version = "0.1" 14 | verbose = false 15 | dbType = "sqlite3" 16 | dbUrl = "web.db" 17 | migrateSqlPath = "./migrations" 18 | httpPort = 8080 19 | httpsPort = 8443 20 | certFile = "https-cert.pem" 21 | keyFile = "https-key.pem" 22 | mailHost = "" 23 | mailPort = 25 24 | mailUsername = "" 25 | mailPassword = "" 26 | mailFrom = "" // an email address 27 | mailTo = []string{} // a slice of email addresses 28 | ) 29 | 30 | // this variable is accessed in http.go 31 | var db *sqlx.DB 32 | 33 | func main() { 34 | 35 | if !processFlags() { // no need to proceed 36 | return 37 | } 38 | 39 | fmt.Println("==================================\n== Jon's Example Web App\n==================================") 40 | 41 | // 42 | // Setting up the database 43 | // 44 | var err error 45 | db, err = setupDb(dbType, dbUrl, migrateSqlPath) 46 | if err != nil { 47 | log.Println("Error setting up the db", err) 48 | return 49 | } 50 | 51 | // 52 | // Starting Web Server 53 | // 54 | startWebServer(httpPort, httpsPort, certFile, keyFile) 55 | } 56 | 57 | // processFlags returns true if processing should continue, false otherwise 58 | func processFlags() bool { 59 | var configFileName string 60 | var versionFlag bool 61 | var helpFlag bool 62 | var generateConfig bool 63 | var testMail bool 64 | 65 | flag.StringVarP(&configFileName, "config", "c", "", "path and name of the config file") 66 | flag.BoolVarP(&versionFlag, "version", "V", false, "displays version information") 67 | flag.BoolVarP(&verbose, "verbose", "v", false, "outputs extra information") 68 | flag.BoolVarP(&helpFlag, "help", "?", false, "displays usage help") 69 | flag.BoolVarP(&generateConfig, "generate-config", "g", false, "prints a default config file to standard output") 70 | flag.BoolVarP(&testMail, "test-mail", "m", false, "sends a test email to the configured mail server") 71 | flag.Parse() 72 | 73 | if versionFlag { 74 | fmt.Fprintf(os.Stderr, "%s version %s\n", os.Args[0], version) 75 | fmt.Fprintln(os.Stderr, "\nCopyright (c) 2015 Jon Carlson. All rights reserved.") 76 | fmt.Fprintln(os.Stderr, "Use of this source code is governed by the MIT license") 77 | fmt.Fprintln(os.Stderr, "that can be found here: http://opensource.org/licenses/MIT") 78 | return false 79 | } 80 | 81 | if helpFlag { 82 | usage() 83 | return false 84 | } 85 | 86 | if generateConfig { 87 | generateConfigurationFile() 88 | return false 89 | } 90 | 91 | if len(configFileName) > 0 { 92 | processConfigFile(configFileName) 93 | } 94 | 95 | if testMail { 96 | testMailConfig() 97 | return false 98 | } 99 | 100 | return true 101 | } 102 | 103 | func usage() { 104 | fmt.Fprintf(os.Stderr, "usage: %s --config \n", os.Args[0]) 105 | fmt.Fprintf(os.Stderr, " %s --generate-config=true > web.config \n", os.Args[0]) 106 | fmt.Fprintln(os.Stderr, ` 107 | Program flags are: 108 | -?, --help : prints a summary of the arguments 109 | -V, --version : prints the version of this program 110 | -v, --verbose : prints additional lines to standard output 111 | -c, --config : name and path of config file (required) 112 | -g, --generate-config : prints an example config file to standard output 113 | -m, --test-mail : sends a test alert email using the configured settings 114 | `) 115 | } 116 | 117 | // testMailConfig sends a test email to the configured addresses 118 | func testMailConfig() { 119 | if len(mailHost) == 0 { 120 | fmt.Fprintln(os.Stderr, "Error, there is no mail host configured to send a test email to.") 121 | return 122 | } 123 | if len(mailTo) == 0 { 124 | fmt.Fprintln(os.Stderr, "Error, there is no 'mailTo' address to send a test email to.") 125 | return 126 | } 127 | 128 | // Send the test email 129 | err := sendMail("Test email from web-mon", "Receiving this email means your mail configuration is working") 130 | if err != nil { 131 | fmt.Fprintln(os.Stderr, "Test email error:", err) 132 | return 133 | } 134 | 135 | fmt.Println("Test email sent") 136 | } 137 | -------------------------------------------------------------------------------- /web/migrations/001_tables.down.sql: -------------------------------------------------------------------------------- 1 | 2 | DROP TABLE IF EXISTS user; 3 | -------------------------------------------------------------------------------- /web/migrations/001_tables.up.sql: -------------------------------------------------------------------------------- 1 | -- 2 | -- sqlite3 3 | -- 4 | 5 | CREATE TABLE user ( 6 | user_id INTEGER PRIMARY KEY, 7 | first_name VARCHAR(80) DEFAULT '', 8 | last_name VARCHAR(80) DEFAULT '', 9 | email VARCHAR(250) DEFAULT '', 10 | password VARCHAR(250) DEFAULT NULL 11 | ); 12 | -------------------------------------------------------------------------------- /web/migrations/002_initialData.down.sql: -------------------------------------------------------------------------------- 1 | DELETE FROM user WHERE email = 'jane.citizen@example.com'; 2 | -------------------------------------------------------------------------------- /web/migrations/002_initialData.up.sql: -------------------------------------------------------------------------------- 1 | 2 | -- password is 'supersecret' 3 | INSERT INTO user (first_name, last_name, email, password) VALUES ('Joe', 'Carter', 'joe@example.com', 'c44tqq+T5R57KwVuAJKfNiriIzmxR+uUcyJNuvAA7PvI84OE7xUpASLiGyMp4/vgYDVJu49u99ugxujEH4g7hw=='); 4 | INSERT INTO user (first_name, last_name, email, password) VALUES ('Jon', 'Carlson', 'joncrlsn@gmail.com', 'c44tqq+T5R57KwVuAJKfNiriIzmxR+uUcyJNuvAA7PvI84OE7xUpASLiGyMp4/vgYDVJu49u99ugxujEH4g7hw=='); 5 | INSERT INTO user (first_name, last_name, email) VALUES ('Jane', 'Citizen', 'jane@example.com'); 6 | -------------------------------------------------------------------------------- /web/smtp.go: -------------------------------------------------------------------------------- 1 | // 2 | // Thank you to William Kennedy who shared his code here: 3 | // http://www.goinggo.net/2013/06/send-email-in-go-with-smtpsendmail.html 4 | // 5 | // Example call: 6 | // SendEmail( 7 | // "smtp.gmail.com", 8 | // 587, 9 | // "username@gmail.com", 10 | // "password", 11 | // []string{"me@domain.com"}, 12 | // "testing subject", 13 | // "Exception 1Exception 1") 14 | // } 15 | // 16 | package main 17 | 18 | import ( 19 | "bytes" 20 | "fmt" 21 | "net/smtp" 22 | "runtime" 23 | "strings" 24 | "text/template" 25 | ) 26 | 27 | var _emailScript = `From: {{.From}} 28 | To: {{.To}} 29 | Subject: {{.Subject}} 30 | MIME-version: 1.0 31 | Content-Type: text/html; charset="UTF-8" 32 | 33 | {{.Message}}` 34 | 35 | // sendMail sends an email with values from the config file 36 | func sendMail(subject, message string) error { 37 | return _sendEmail(mailHost, mailPort, mailUsername, mailPassword, mailFrom, mailTo, subject, message) 38 | } 39 | 40 | // _sendEmail does the detailed-work for sending an email 41 | func _sendEmail(host string, port int, userName string, password string, from string, to []string, subject string, message string) (err error) { 42 | defer _catchPanic(&err, "_sendEmail") 43 | 44 | if len(host) == 0 { 45 | if verbose { 46 | fmt.Println("No smtp host. Skipping email.") 47 | } 48 | return nil 49 | } 50 | 51 | parameters := struct { 52 | From string 53 | To string 54 | Subject string 55 | Message string 56 | }{ 57 | userName, 58 | strings.Join([]string(to), ","), 59 | subject, 60 | message, 61 | } 62 | 63 | buffer := new(bytes.Buffer) 64 | 65 | template := template.Must(template.New("emailTemplate").Parse(_emailScript)) 66 | template.Execute(buffer, ¶meters) 67 | 68 | auth := smtp.PlainAuth("", userName, password, host) 69 | if len(userName) == 0 { 70 | auth = nil 71 | } 72 | 73 | err = smtp.SendMail( 74 | fmt.Sprintf("%s:%d", host, port), 75 | auth, 76 | from, 77 | to, 78 | buffer.Bytes()) 79 | 80 | return err 81 | } 82 | 83 | func _catchPanic(err *error, functionName string) { 84 | if r := recover(); r != nil { 85 | fmt.Printf("%s : PANIC Defered : %v\n", functionName, r) 86 | 87 | // Capture the stack trace 88 | buf := make([]byte, 10000) 89 | runtime.Stack(buf, false) 90 | 91 | fmt.Printf("%s : Stack Trace : %s", functionName, string(buf)) 92 | 93 | if err != nil { 94 | *err = fmt.Errorf("%v", r) 95 | } 96 | } else if err != nil && *err != nil { 97 | fmt.Printf("%s : ERROR : %v\n", functionName, *err) 98 | 99 | // Capture the stack trace 100 | buf := make([]byte, 10000) 101 | runtime.Stack(buf, false) 102 | 103 | fmt.Printf("%s : Stack Trace : %s", functionName, string(buf)) 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /web/web.config: -------------------------------------------------------------------------------- 1 | # ====================== 2 | # Web app configuration 3 | # ====================== 4 | 5 | # Just sqlite3 is tested, but others could be supported too 6 | dbType = sqlite3 7 | 8 | # The name of the sqlite3 file 9 | dbUrl = web.db 10 | 11 | # Directory that holds the database migration SQL 12 | migrateSqlPath = ./migrations 13 | 14 | # Ports 15 | httpPort = 8080 16 | httpsPort = 8443 17 | 18 | # SSL files 19 | certFile = https-cert.pem 20 | keyFile = https-key.pem 21 | 22 | # =================== 23 | # Mail configuration 24 | # =================== 25 | 26 | # mailHost = localhost 27 | # mailPort = 25 28 | # mailUsername = 29 | # mailPassword = 30 | 31 | # An email address to be used as the "from" address in emails 32 | # mailFrom = 33 | 34 | # A comma-separated list of email addresses that will receive alert emails 35 | # mailTo = 36 | 37 | -------------------------------------------------------------------------------- /web/www/login.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | HTML5 Web Form With No JavaScript in Sight 6 | 7 | 8 | 238 | 239 | 250 | 251 | 252 | 267 | 268 | 269 | 270 | 271 | 272 |
273 | 274 |

Jon's Sample Web Server

275 | 276 |
277 | Login 278 | 279 |
    280 | 281 |
  • 282 | 283 | 284 |
  • 285 | 286 |
  • 287 | 288 | 289 |
  • 290 | 291 |
292 | 293 |
294 | 295 | 296 | 297 |
298 | 299 | 300 | 301 | 302 | 303 | -------------------------------------------------------------------------------- /web/www/main.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Bootstrap 101 Template 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 20 | 29 | 30 | 31 | 32 | 52 |
53 |
54 |

Bootstrap starter template

55 |

Use this document as a way to quickly start any new project.
All you get is this text and a mostly barebones HTML document.

56 |
57 |
58 | 59 | 60 | 61 | 62 | 63 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | -------------------------------------------------------------------------------- /web/www/sample-nojs.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | HTML5 Web Form With No JavaScript in Sight | Elated.com 6 | 7 | 8 | 238 | 239 | 250 | 251 | 252 | 267 | 268 | 269 | 270 | 271 | 272 |
273 |

HTML5 Web Form With No JavaScript in Sight

274 |
275 | 276 |
277 | 278 |

Software Order Form

279 | 280 |
281 | License details 282 | 283 |
    284 | 285 |
  • 286 | 287 | 288 |
  • 289 | 290 |
  • 291 | 292 | 293 |
  • 294 | 295 |
  • 296 | 297 | 298 |
  • 299 | 300 |
301 | 302 |
303 | 304 |
305 | Billing details 306 | 307 |
    308 | 309 |
  • 310 | 311 | 312 |
  • 313 | 314 |
  • 315 | 316 | 317 |
  • 318 | 319 |
  • 320 | 321 | 322 |
  • 323 | 324 |
  • 325 | 326 | 327 |
  • 328 | 329 |
  • 330 | 331 | 332 |
  • 333 | 334 |
335 | 336 |
337 | 338 |
339 | Payment details 340 | 341 |
    342 | 343 |
  • 344 | 345 | 346 |
  • 347 | 348 |
  • 349 | 350 | 351 |
  • 352 | 353 |
  • 354 | 355 | 356 |
  • 357 | 358 |
359 | 360 |
361 | 362 | 363 | 364 |
365 | 366 |
367 |

© Elated.com | Back to Tutorial

368 |

Creative Commons License
This work by http://www.elated.com/ is licensed under a Creative Commons Attribution 3.0 Unported License.

369 |
370 | 371 | 372 | 373 | 374 | 375 | -------------------------------------------------------------------------------- /yaml.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "github.com/spf13/viper" 6 | "log" 7 | ) 8 | 9 | func main() { 10 | // 11 | // Initialize from our config file 12 | // 13 | //viper.SetConfigType("yaml") 14 | //viper.SetConfigName("yaml-config") // name of config file (without extension) 15 | //viper.AddConfigPath(".") 16 | //viper.WatchConfig() 17 | //err := viper.ReadInConfig() // Find and read the config file 18 | //if err != nil { 19 | //log.Fatalln(err) 20 | //} 21 | 22 | // Parse bytes 23 | var yamlExample = []byte(` 24 | Hacker: true 25 | name: steve 26 | hobbies: 27 | - skateboarding 28 | - snowboarding 29 | - go 30 | clothing: 31 | jacket: leather 32 | trousers: denim 33 | age: 35 34 | eyes : brown 35 | beard: true 36 | Hacker: false 37 | `) 38 | yaml := viper.New() 39 | yaml.SetConfigType("yaml") 40 | yaml.ReadConfig(bytes.NewBuffer(yamlExample)) 41 | 42 | log.Println("clothing.jacket", yaml.Get("clothing.jacket")) 43 | log.Println("age", yaml.Get("age")) 44 | log.Println("hobbies", yaml.Get("hobbies")) 45 | log.Println("hacker", yaml.Get("Hacker")) 46 | } 47 | --------------------------------------------------------------------------------