A config.go => config.go +43 -0
@@ 0,0 1,43 @@
+package main
+
+import (
+ "log"
+ "os"
+
+ "github.com/BurntSushi/toml"
+)
+
+type ConfigFile struct {
+ AwsConfig awsConfig
+}
+
+func loadConfiguration(filePath string) ConfigFile {
+
+ var config ConfigFile
+ if _, err := toml.DecodeFile(filePath, &config); err != nil {
+ log.Fatal(err)
+ }
+
+ return config
+}
+
+type awsConfig struct {
+ IamAccessKeyId string
+ IamAccessKeySecret string
+ Region string
+}
+
+func (c *awsConfig) setAwsEnvironmentVariables() {
+ if err := os.Setenv("AWS_ACCESS_KEY_ID", c.IamAccessKeyId); err != nil {
+ log.Fatal(err)
+ }
+ if err := os.Setenv("AWS_SECRET_ACCESS_KEY", c.IamAccessKeySecret); err != nil {
+ log.Fatal(err)
+ }
+ if err := os.Setenv("AWS_DEFAULT_REGION", c.Region); err != nil {
+ log.Fatal(err)
+ }
+ if err := os.Setenv("AWS_REGION", c.Region); err != nil {
+ log.Fatal(err)
+ }
+}
A dictionary.go => dictionary.go +112 -0
@@ 0,0 1,112 @@
+package main
+
+import (
+ "bytes"
+ "compress/bzip2"
+ "encoding/json"
+ "fmt"
+ "log"
+ "net/http"
+ "os"
+ "strings"
+
+ "github.com/aws/aws-sdk-go/aws"
+ "github.com/aws/aws-sdk-go/aws/session"
+ "github.com/aws/aws-sdk-go/service/s3"
+ "github.com/aws/aws-sdk-go/service/s3/s3manager"
+)
+
+func downloadFromS3(filePath string, bucket string, key string) {
+ file, err := os.Create(filePath)
+ if err != nil {
+ log.Fatal(err)
+ }
+ defer file.Close()
+
+ sess := session.Must(session.NewSession())
+ downloader := s3manager.NewDownloader(sess)
+
+ _, err = downloader.Download(file,
+ &s3.GetObjectInput{
+ Bucket: aws.String(bucket),
+ Key: aws.String(key),
+ })
+
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ log.Printf("Successfully fetched '%s' item from '%s' bucket in S3. "+
+ "Content written to '%s'\n", key, bucket, filePath)
+}
+
+type DictionaryHandler struct {
+ dictionary map[string]string
+}
+
+func NewDictionaryHandler() *DictionaryHandler {
+ dictHandler := new(DictionaryHandler)
+ dictHandler.loadDictionary()
+ return dictHandler
+}
+
+func (d *DictionaryHandler) loadDictionary() {
+ const dictionaryFileName string = "dictionary.json.bz2"
+ const dictionaryFileS3Bucket string = "pub-static-files"
+ const dictionaryFileS3ObjectKey string = "dictionary.json.bz2"
+
+ if _, err := os.Stat(dictionaryFileName); os.IsNotExist(err) {
+ log.Printf("Didn't find %s file locally, fetching from S3.\n", dictionaryFileName)
+ downloadFromS3(dictionaryFileName, dictionaryFileS3Bucket, dictionaryFileS3ObjectKey)
+ } else {
+ log.Printf("Found the %s file locally. Using that.\n", dictionaryFileName)
+ }
+
+ file, err := os.Open(dictionaryFileName)
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ decompressedStream := bzip2.NewReader(file)
+ buf := new(bytes.Buffer)
+ if _, err := buf.ReadFrom(decompressedStream); err != nil {
+ log.Fatal(err)
+ }
+
+ if err := json.Unmarshal([]byte(buf.String()), &d.dictionary); err != nil {
+ log.Fatal(err)
+ }
+}
+
+func (d *DictionaryHandler) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
+ if request.Method == "GET" {
+ lookupWord := request.URL.Path[len("/dictionary/"):]
+ lookupWord = strings.ToLower(lookupWord)
+
+ if lookupWord == "" {
+ // They just asked for /dictionary so let's print some help.
+ formatted := fmt.Sprintf("Hello! You've reached the /dictionary resource. " +
+ "You can get a definition by adding the word you're looking for to the end of the URL.\n\n" +
+ "For example: /dictionary/banana")
+ writer.Write([]byte(formatted))
+ } else {
+ if found, ok := d.dictionary[lookupWord]; ok {
+ // They asked for a valid word. e.g. /dictionary/potato
+ formatted := fmt.Sprintf("Hello! You've just requested the definition of '%s'.\n\n'%s' "+
+ " is defined as: %s\n", lookupWord, lookupWord, found)
+ writer.Write([]byte(formatted))
+ return
+ } else {
+ // They asked for a word that we didn't know. e.g. /dictionary/taters
+ message := fmt.Sprintf("Hello! You've just requested the definition of '%s'. "+
+ "Unfortunately, I don't know that word :(.", lookupWord)
+ writer.Write([]byte(message))
+ }
+ }
+ } else {
+ message := fmt.Sprintf("Hello! You've made a '%s' request to the '%s' resource. "+
+ "Unfortunately, that REST method is not supported at this time.", request.Method, request.URL.Path)
+ writer.Write([]byte(message))
+ }
+
+}
M go.mod => go.mod +5 -0
@@ 1,3 1,8 @@
module git.sr.ht/~zacbrown/api.zacbrown.org
go 1.13
+
+require (
+ github.com/BurntSushi/toml v0.3.1
+ github.com/aws/aws-sdk-go v1.25.43
+)
A go.sum => go.sum +6 -0
@@ 0,0 1,6 @@
+github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
+github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
+github.com/aws/aws-sdk-go v1.25.43 h1:R5YqHQFIulYVfgRySz9hvBRTWBjudISa+r0C8XQ1ufg=
+github.com/aws/aws-sdk-go v1.25.43/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
+github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM=
+github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
M main.go => main.go +23 -0
@@ 4,12 4,35 @@ import (
"fmt"
"log"
"net/http"
+ "os"
)
+func getConfigFilePathFromProgramArgs() string {
+ if len(os.Args) >= 2 {
+ path := os.Args[1]
+ if _, err := os.Stat(path); os.IsExist(err) {
+ return path
+ }
+ }
+
+ panic("Not enough arguments provided. Panic time.")
+}
+
func main() {
+ configFilePath := os.Args[1]
+ config := loadConfiguration(configFilePath)
+ log.Printf("Loaded config file from path '%s'.\n", configFilePath)
+
+ config.AwsConfig.setAwsEnvironmentVariables()
+ log.Println("Setup AWS variables.")
+
http.HandleFunc("/", rootFunc)
log.Println("Setup root '/' handler.")
+ dictionaryHandler := NewDictionaryHandler()
+ http.HandleFunc("/dictionary/", dictionaryHandler.ServeHTTP)
+ log.Println("Setup dictionary '/dictionary' handler.")
+
log.Println("Starting server on port 80...")
log.Fatal(http.ListenAndServe(":80", nil))
}