├── .gitignore ├── bin └── bbpd │ ├── .gitignore │ ├── bbpd_daemon │ ├── README │ ├── bbpd.conf │ └── bbpd_ctl ├── tests ├── describe-livetest.sh ├── update_table-livetest.sh ├── scan-livetest.sh ├── README ├── batch_write_item-livetest6.sh ├── batch_get_item-livetest4.sh ├── query-livetest.sh ├── get_item-livetest.sh ├── put_item-livestest.sh ├── create_table-livetest.sh ├── item_operations-livetest.sh ├── batch_write_item-livetest1.sh ├── batch_write_item-livetest2.sh ├── batch_write_item-livetest3.sh ├── batch_write_item-livetest4.sh ├── batch_get_item-livetest1.sh ├── batch_get_item-livetest2.sh ├── batch_write_item-livetest5.sh └── batch_get_item-livetest3.sh ├── lib ├── bbpd_const │ └── bbpd_const.go ├── bbpd_msg │ └── bbpd_msg.go ├── bbpd_runinfo │ └── bbpd_runinfo.go ├── bbpd_stats │ └── bbpd_stats.go ├── scan_route │ └── scan_route.go ├── query_route │ └── query_route.go ├── update_item_route │ └── update_item_route.go ├── update_table_route │ └── update_table_route.go ├── delete_item_route │ └── delete_item_route.go ├── raw_post_route │ └── raw_post_route.go ├── create_table_route │ └── create_table_route.go ├── delete_table_route │ └── delete_table_route.go ├── route_response │ └── route_response.go ├── get_item_route │ └── get_item_route.go ├── put_item_route │ └── put_item_route.go ├── list_tables_route │ └── list_tables_route.go ├── batch_write_item_route │ └── batch_write_item_route.go ├── batch_get_item_route │ └── batch_get_item_route.go ├── describe_table_route │ └── describe_table_route.go └── bbpd_route │ └── bbpd_route.go ├── CHANGELOG.txt ├── LICENSE.txt ├── bbpd.go └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | bbpd 2 | -------------------------------------------------------------------------------- /bin/bbpd/.gitignore: -------------------------------------------------------------------------------- 1 | bbpd 2 | nt 3 | -------------------------------------------------------------------------------- /bin/bbpd/bbpd_daemon: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env zsh 2 | /usr/bin/bbpd & disown 3 | -------------------------------------------------------------------------------- /bin/bbpd/README: -------------------------------------------------------------------------------- 1 | bbpd.conf is used to configure Ubuntu upstart. 2 | 3 | bbpd_ctl and bbpd daemon are sample wrappers for launching bbpd. 4 | 5 | -------------------------------------------------------------------------------- /tests/describe-livetest.sh: -------------------------------------------------------------------------------- 1 | curl -H "X-Amz-Target: DynamoDB_20120810.DescribeTable" -X POST -d '{"TableName":"test-godynamo-livetest"}' http://localhost:12333/ 2 | -------------------------------------------------------------------------------- /tests/update_table-livetest.sh: -------------------------------------------------------------------------------- 1 | curl -H "X-Amz-Target: DynamoDB_20120810.UpdateTable" -X POST -d '{"TableName":"test-godynamo-livetest","ProvisionedThroughput":{"ReadCapacityUnits":200,"WriteCapacityUnits":200}}' http://localhost:12333/ 2 | -------------------------------------------------------------------------------- /tests/scan-livetest.sh: -------------------------------------------------------------------------------- 1 | curl -H "X-Amz-Target: DynamoDB_20120810.Scan" -X POST -d '{"AttributesToGet":null,"ExclusiveStartKey":null,"ReturnConsumedCapacity":"NONE","Limit":null,"ScanFilter":null,"Select":null,"Segment":null,"TableName":"test-godynamo-livetest","TotalSegments":null}' http://localhost:12333/ 2 | -------------------------------------------------------------------------------- /tests/README: -------------------------------------------------------------------------------- 1 | These tests must be run in the correct order to be useful. 2 | 3 | First run the create-table test, then some tests to insert items (item-operations, batch_write*). 4 | 5 | Then you can try some of the reading tests (batch_get*,scan,query) 6 | 7 | Remember to delete the table when you are done! 8 | -------------------------------------------------------------------------------- /bin/bbpd/bbpd.conf: -------------------------------------------------------------------------------- 1 | description "BigBirdProxyDaemon (bbpd)" 2 | author "brad clawsie " 3 | 4 | start on (local-filesystems and net-device-up IFACE!=lo) 5 | stop on runlevel [!2345] 6 | respawn 7 | console log 8 | 9 | script 10 | exec su bbpd -s /bin/bash -c "/usr/bin/bbpd" 11 | end script 12 | -------------------------------------------------------------------------------- /tests/batch_write_item-livetest6.sh: -------------------------------------------------------------------------------- 1 | curl -X POST -d '{"RequestItems":{"test-godynamo-livetest":[{"PutRequest":{"Item":{"SomeValue":1,"TheHashKey":"AHashKeyJSONBatch1","TheRangeKey":3}}},{"PutRequest":{"Item":{"SomeValue":2,"TheHashKey":"AHashKeyJSONBatch2","TheRangeKey":4}}}]},"ReturnConsumedCapacity":"NONE","ReturnItemCollectionMetrics":"NONE"}' http://localhost:12333/BatchWriteItemJSON 2 | -------------------------------------------------------------------------------- /tests/batch_get_item-livetest4.sh: -------------------------------------------------------------------------------- 1 | curl -X POST -d '{"RequestItems":{"test-godynamo-livetest":{"Keys":[{"TheHashKey":{"S":"AHashKey1"},"TheRangeKey":{"N":"1"}},{"TheHashKey":{"S":"AHashKey2"},"TheRangeKey":{"N":"2"}},{"TheHashKey":{"S":"AHashKey3"},"TheRangeKey":{"N":"3"}},{"TheHashKey":{"S":"AHashKey4"},"TheRangeKey":{"N":"4"}},{"TheHashKey":{"S":"AHashKey5"},"TheRangeKey":{"N":"5"}}]}}}' "http://localhost:12333/BatchGetItemJSON" 2 | -------------------------------------------------------------------------------- /tests/query-livetest.sh: -------------------------------------------------------------------------------- 1 | curl -H "X-Amz-Target: DynamoDB_20120810.Query" -X POST -d '{"AttributesToGet":null,"ConsistentRead":false,"ExclusiveStartKey":null,"IndexName":null,"KeyConditions":{"TheHashKey":{"AttributeValueList":[{"S":"AHashKey100"}],"ComparisonOperator":"EQ"}},"Limit":10000,"ReturnConsumedCapacity":"NONE","ScanIndexForward":true,"TableName":"test-godynamo-livetest","Select":"ALL_ATTRIBUTES"}' http://localhost:12333/ 2 | -------------------------------------------------------------------------------- /tests/get_item-livetest.sh: -------------------------------------------------------------------------------- 1 | curl -H "X-Bbpd-Indent: true" -H "X-Amz-Target: DynamoDB_20120810.GetItem" -X POST -d '{"TableName":"test-godynamo-livetest","Key":{"TheHashKey":{"S":"a-hash-key-json1"},"TheRangeKey":{"N":"1"}}}' "http://localhost:12333/"; 2 | echo ""; 3 | curl -H "X-Bbpd-Verbose: True" -X POST -d '{"TableName":"test-godynamo-livetest","Key":{"TheHashKey":{"S":"a-hash-key-json1"},"TheRangeKey":{"N":"1"}}}' "http://localhost:12333/GetItemJSON"; 4 | -------------------------------------------------------------------------------- /lib/bbpd_const/bbpd_const.go: -------------------------------------------------------------------------------- 1 | // Collection of global constant values. 2 | package bbpd_const 3 | 4 | const ( 5 | INDENT = "indent" 6 | COMPACT = "compact" 7 | KEYS = "keys" 8 | ATTRS = "attrs" 9 | CONTENTTYPE = "Content-Type" 10 | CONTENTLENGTH = "Content-Length" 11 | JSONMIME = "application/json" 12 | PORT = 12333 // primary port 13 | PORT2 = 12334 // secondary 14 | LOCALHOST = "localhost" 15 | 16 | // request headers specific to bbpd 17 | X_BBPD_VERBOSE = "X-Bbpd-Verbose" 18 | X_BBPD_INDENT = "X-Bbpd-Indent" 19 | ) 20 | -------------------------------------------------------------------------------- /CHANGELOG.txt: -------------------------------------------------------------------------------- 1 | December 9, 2014 2 | ---------------- 3 | 4 | - Remove support for query parameters "compact" and "indent" in favor 5 | of http headers X-Bbpd-Verbose and X-Bbpd-Inent. Default behavior now 6 | is compact and unindented output by default. To override, set the 7 | aforementioned headers with any value. 8 | 9 | December 3, 2014 10 | ---------------- 11 | 12 | - Deploy support for some type changes in GoDynamo. 13 | 14 | 15 | October 27, 2014 16 | ---------------- 17 | 18 | - Bring bbpd into conformance with DynamoDB changes made by AWS in October 2014. 19 | 20 | - Support graceful shutdown. 21 | -------------------------------------------------------------------------------- /tests/put_item-livestest.sh: -------------------------------------------------------------------------------- 1 | curl -H "X-Amz-Target: DynamoDB_20120810.PutItem" -X POST -d '{"TableName":"test-godynamo-livetest","Item":{"TheHashKey":{"S":"a-hash-key"},"TheRangeKey":{"N":"1"},"byte":{"B":"aGVsbG8="},"bytelist":{"BS":["aGVsbG8=","dGhlcmU="]},"num":{"N":"1"},"numlist":{"NS":["1","2","3","-7234234234.234234"]},"stringlist":{"SS":["pk1_a","pk1_b","pk1_c"]}},"ReturnValues":"NONE"}' http://localhost:12333/; 2 | # PutItemJSON is not a compatible aws endpoint, so it cannot go through / 3 | curl -X POST -d '{"TableName":"test-godynamo-livetest","Item":{"TheHashKey":"a-hash-key-json1","TheRangeKey":1,"num":1,"numlist":[7,7,1,2,3,9,-7234234234.234234],"stringlist":["pk1_a","pk1_b","pk1_c"]}}' http://localhost:12333/PutItemJSON; 4 | -------------------------------------------------------------------------------- /tests/create_table-livetest.sh: -------------------------------------------------------------------------------- 1 | curl -H "X-Amz-Target: DynamoDB_20120810.CreateTable" -X POST -d '{"TableName":"test-godynamo-livetest","AttributeDefinitions":[{"AttributeName":"TheHashKey","AttributeType":"S"},{"AttributeName":"TheRangeKey","AttributeType":"N"},{"AttributeName":"AnAttrName","AttributeType":"S"}],"KeySchema":[{"AttributeName":"TheHashKey","KeyType":"HASH"},{"AttributeName":"TheRangeKey","KeyType":"RANGE"}],"LocalSecondaryIndexes":[{"IndexName":"AnAttrIndex","KeySchema":[{"AttributeName":"TheHashKey","KeyType":"HASH"},{"AttributeName":"AnAttrName","KeyType":"RANGE"}],"Projection":{"NonKeyAttributes":null,"ProjectionType":"KEYS_ONLY"}}],"ProvisionedThroughput":{"ReadCapacityUnits":100,"WriteCapacityUnits":100}}' http://localhost:12333/ 2 | -------------------------------------------------------------------------------- /lib/bbpd_msg/bbpd_msg.go: -------------------------------------------------------------------------------- 1 | // Some core types for managing proxied requests. 2 | package bbpd_msg 3 | 4 | import ( 5 | "encoding/json" 6 | "time" 7 | ) 8 | 9 | // RunInfo provides duration information. 10 | type RunInfo struct { 11 | Method string 12 | Host string 13 | Start time.Time 14 | End time.Time 15 | Duration string 16 | } 17 | 18 | type Status struct { 19 | Status string 20 | Run RunInfo 21 | } 22 | 23 | type Response struct { 24 | Name string 25 | StatusCode int 26 | Body []byte 27 | Run RunInfo 28 | } 29 | 30 | type response struct { 31 | Name string 32 | StatusCode int 33 | Body string 34 | Run RunInfo 35 | } 36 | 37 | // Body is a []byte above so it needs to be converted here or it will be encoded as a bytestream 38 | func (r Response) MarshalJSON() ([]byte, error) { 39 | return json.Marshal(response{Name: r.Name, StatusCode: r.StatusCode, Run: r.Run, Body: string(r.Body)}) 40 | } 41 | -------------------------------------------------------------------------------- /bin/bbpd/bbpd_ctl: -------------------------------------------------------------------------------- 1 | #!/bin/zsh 2 | 3 | PROG=bbpd 4 | DAEMON=bbpd_daemon 5 | INSTALL_PATH=/usr/bin 6 | 7 | test -x $INSTALL_PATH/$PROG || exit 0 8 | test -x $INSTALL_PATH/$DAEMON || exit 0 9 | 10 | case "$1" in 11 | start) 12 | echo -n "**** starting bbpd as daemon\n" 13 | $INSTALL_PATH/$DAEMON || true 14 | $INSTALL_PATH/$DAEMON || true 15 | echo "bbpd - started\n" 16 | ;; 17 | stop) 18 | echo -n "**** stopping bbpd\n" 19 | killall $PROG || true 20 | echo "bbpd - stopped\n" 21 | ;; 22 | status) 23 | curl "http://localhost:12333/Status?indent=1&compact=1" 24 | ;; 25 | restart) 26 | echo -n "**** stopping bbpd\n" 27 | killall $PROG || true 28 | echo " - stopped" 29 | echo -n "**** starting bbpd as daemon\n" 30 | $INSTALL_PATH/$DAEMON || true 31 | $INSTALL_PATH/$DAEMON || true 32 | echo "bbpd - started\n" 33 | ;; 34 | 35 | esac 36 | 37 | exit 0 38 | -------------------------------------------------------------------------------- /tests/item_operations-livetest.sh: -------------------------------------------------------------------------------- 1 | curl -H "X-Amz-Target: DynamoDB_20120810.PutItem" -X POST -d '{"TableName":"test-godynamo-livetest","Item":{"TheHashKey":{"S":"a-hash-key"},"TheRangeKey":{"N":"1"},"byte":{"B":"aGVsbG8="},"bytelist":{"BS":["aGVsbG8=","dGhlcmU="]},"num":{"N":"1"},"numlist":{"NS":["1","2","3","-7234234234.234234"]},"stringlist":{"SS":["pk1_a","pk1_b","pk1_c"]}}}' http://localhost:12333/; 2 | curl -H "X-Amz-Target: DynamoDB_20120810.GetItem" -X POST -d '{"TableName":"test-godynamo-livetest","Key":{"TheHashKey":{"S":"a-hash-key"},"TheRangeKey":{"N":"1"}}}' http://localhost:12333/; 3 | curl -H "X-Amz-Target: DynamoDB_20120810.UpdateItem" -X POST -d '{"TableName":"test-godynamo-livetest","Key":{"TheHashKey":{"S":"a-hash-key"},"TheRangeKey":{"N":"1"}},"AttributeUpdates":{"byte":{"Action":"DELETE"},"new_string":{"Value":{"S":"new string here"},"Action":"PUT"},"num":{"Value":{"N":"4"},"Action":"ADD"},"stringlist":{"Value":{"SS":["pk1_a"]},"Action":"DELETE"}}}' http://localhost:12333/; 4 | curl -H "X-Amz-Target: DynamoDB_20120810.DeleteItem" -X POST -d '{"TableName":"test-godynamo-livetest","Key":{"TheHashKey":{"S":"a-hash-key"},"TheRangeKey":{"N":"1"}}}' http://localhost:12333/; 5 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013,2014 SmugMug, Inc. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | * Redistributions of source code must retain the above copyright 7 | notice, this list of conditions and the following disclaimer. 8 | * Redistributions in binary form must reproduce the above 9 | copyright notice, this list of conditions and the following 10 | disclaimer in the documentation and/or other materials provided 11 | with the distribution. 12 | 13 | THIS SOFTWARE IS PROVIDED BY SMUGMUG, INC. ``AS IS'' AND ANY 14 | EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 15 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 16 | PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL SMUGMUG, INC. BE LIABLE FOR 17 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 18 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE 19 | GOODS OR SERVICES;LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 20 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER 21 | IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 22 | OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 23 | ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | -------------------------------------------------------------------------------- /lib/bbpd_runinfo/bbpd_runinfo.go: -------------------------------------------------------------------------------- 1 | // Control routines for starting and stopping bbpd safely 2 | package bbpd_runinfo 3 | 4 | import ( 5 | "errors" 6 | "log" 7 | "net/http" 8 | "sync" 9 | "time" 10 | ) 11 | 12 | var ( 13 | accepting bool 14 | accept_mut *sync.RWMutex 15 | conns_wg *sync.WaitGroup 16 | ) 17 | 18 | func init() { 19 | accepting = false 20 | accept_mut = new(sync.RWMutex) 21 | conns_wg = new(sync.WaitGroup) 22 | } 23 | 24 | // SetBBPDAccept should be called when the server is started. 25 | func SetBBPDAccept() { 26 | accept_mut.Lock() 27 | accepting = true 28 | accept_mut.Unlock() 29 | } 30 | 31 | // StopBBPD executes any shutdown tasks. 32 | func StopBBPD() error { 33 | accept_mut.Lock() 34 | accepting = false 35 | accept_mut.Unlock() 36 | wait_chan := make(chan bool, 1) 37 | go func() { 38 | conns_wg.Wait() 39 | wait_chan <- true 40 | }() 41 | select { 42 | case <-wait_chan: 43 | log.Printf("conns completed, graceful exit possible") 44 | return nil 45 | case <-time.After(1000 * time.Millisecond): 46 | return errors.New("shutdown timed out") 47 | } 48 | return nil 49 | } 50 | 51 | // IsAccepting returns the value of server accepting state that can be set when bbpd should 52 | // stop accepting connections. 53 | func IsAccepting() bool { 54 | accept_mut.RLock() 55 | a := accepting 56 | accept_mut.RUnlock() 57 | return a 58 | } 59 | 60 | // BBPDAbortIfClosed returns a 503 if the server accepting state has been set to false. 61 | func BBPDAbortIfClosed(w http.ResponseWriter) bool { 62 | closed := !IsAccepting() 63 | if closed { 64 | e := "bbpd is in a closed state and is no longer accepting connections" 65 | http.Error(w, e, http.StatusServiceUnavailable) 66 | } 67 | return closed 68 | } 69 | 70 | // RecordConnState keeps track of the current connection count via a waitgroup. 71 | func RecordConnState(new_state http.ConnState) { 72 | switch new_state { 73 | case http.StateNew: 74 | conns_wg.Add(1) 75 | case http.StateClosed, http.StateHijacked: 76 | conns_wg.Done() 77 | } 78 | return 79 | } 80 | -------------------------------------------------------------------------------- /lib/bbpd_stats/bbpd_stats.go: -------------------------------------------------------------------------------- 1 | // bbpd_stats will collect information about the running bbpd process 2 | package bbpd_stats 3 | 4 | import ( 5 | "fmt" 6 | "sync" 7 | "time" 8 | ) 9 | 10 | type Summary struct { 11 | StartTime string 12 | RunningTime string 13 | LongestResponse string 14 | AverageResponse string 15 | LastResponse string 16 | ResponseCount string 17 | } 18 | 19 | var ( 20 | bbpd_start time.Time 21 | 22 | response_count uint64 23 | 24 | longest_response uint64 25 | shortest_response uint64 26 | average_response float64 27 | 28 | last_response time.Time 29 | 30 | stat_lock sync.RWMutex 31 | ) 32 | 33 | func init() { 34 | shortest_response = 9999999999 35 | average_response = 0.0 36 | bbpd_start = time.Now() 37 | } 38 | 39 | // AddResponse will add the stat information for a response to the totals. 40 | func AddResponse(start time.Time) { 41 | duration := time.Since(start) 42 | duration_ns := uint64(duration.Nanoseconds()) 43 | 44 | stat_lock.Lock() 45 | response_count++ 46 | last_response = time.Now() 47 | new_average_response := average_response*(float64((response_count-1))/float64(response_count)) + 48 | float64(duration_ns/response_count) 49 | average_response = new_average_response 50 | if duration_ns > longest_response { 51 | longest_response = duration_ns 52 | } 53 | if duration_ns < shortest_response && duration_ns != 0 { 54 | shortest_response = duration_ns 55 | } 56 | stat_lock.Unlock() 57 | } 58 | 59 | // GetSummary returns a struct of formatted strings that provide human-readable run stats. 60 | func GetSummary() Summary { 61 | n := time.Since(bbpd_start) 62 | stat_lock.RLock() 63 | longest_response_ms := float64(longest_response) / 1000000 64 | average_response_ms := float64(average_response) / 1000000 65 | l := "no requests made yet" 66 | if response_count > 0 { 67 | l = fmt.Sprintf("%v, (%v ago)", last_response, time.Since(last_response)) 68 | } 69 | stat_lock.RUnlock() 70 | return Summary{ 71 | StartTime: fmt.Sprintf("%v", bbpd_start), 72 | RunningTime: fmt.Sprintf("%s", n.String()), 73 | LongestResponse: fmt.Sprintf("%.2fms", longest_response_ms), 74 | AverageResponse: fmt.Sprintf("%.2fms", average_response_ms), 75 | LastResponse: fmt.Sprintf("%v", l), 76 | ResponseCount: fmt.Sprintf("%d", response_count), 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /lib/scan_route/scan_route.go: -------------------------------------------------------------------------------- 1 | // Supports proxying the Scan endpoint. 2 | package scan_route 3 | 4 | import ( 5 | "encoding/json" 6 | "fmt" 7 | "github.com/smugmug/bbpd/lib/bbpd_runinfo" 8 | raw "github.com/smugmug/bbpd/lib/raw_post_route" 9 | "github.com/smugmug/bbpd/lib/route_response" 10 | ep "github.com/smugmug/godynamo/endpoint" 11 | scan "github.com/smugmug/godynamo/endpoints/scan" 12 | "io" 13 | "io/ioutil" 14 | "log" 15 | "net/http" 16 | "strings" 17 | "time" 18 | ) 19 | 20 | // RawPostHandler relays the Scan request to Dynamo directly. 21 | func RawPostHandler(w http.ResponseWriter, req *http.Request) { 22 | raw.RawPostReq(w, req, scan.SCAN_ENDPOINT) 23 | } 24 | 25 | // ScanHandler relays the Scan request to Dynamo but first validates it through a local type. 26 | func ScanHandler(w http.ResponseWriter, req *http.Request) { 27 | if bbpd_runinfo.BBPDAbortIfClosed(w) { 28 | return 29 | } 30 | start := time.Now() 31 | if req.Method != "POST" { 32 | e := "scan_route.ScanHandler:method only supports POST" 33 | log.Printf(e) 34 | http.Error(w, e, http.StatusBadRequest) 35 | return 36 | } 37 | pathElts := strings.Split(req.URL.Path, "/") 38 | if len(pathElts) != 2 { 39 | e := "scan_route.ScanHandler:cannot parse path. try /create, call as POST" 40 | log.Printf(e) 41 | http.Error(w, e, http.StatusBadRequest) 42 | return 43 | } 44 | 45 | bodybytes, read_err := ioutil.ReadAll(req.Body) 46 | req.Body.Close() 47 | if read_err != nil && read_err != io.EOF { 48 | e := fmt.Sprintf("scan_route.ScanHandler err reading req body: %s", read_err.Error()) 49 | log.Printf(e) 50 | http.Error(w, e, http.StatusInternalServerError) 51 | return 52 | } 53 | 54 | s := scan.NewScan() 55 | um_err := json.Unmarshal(bodybytes, s) 56 | 57 | if um_err != nil { 58 | e := fmt.Sprintf("scan_route.ScanHandler unmarshal err on %s to Create: %s", string(bodybytes), um_err.Error()) 59 | log.Printf(e) 60 | http.Error(w, e, http.StatusInternalServerError) 61 | return 62 | } 63 | 64 | resp_body, code, resp_err := s.EndpointReq() 65 | 66 | if resp_err != nil { 67 | e := fmt.Sprintf("scan_route.ScanHandler:err %s", 68 | resp_err.Error()) 69 | log.Printf(e) 70 | http.Error(w, e, http.StatusInternalServerError) 71 | return 72 | } 73 | 74 | if ep.HttpErr(code) { 75 | route_response.WriteError(w, code, "scan_route.ScanHandler", resp_body) 76 | return 77 | } 78 | 79 | mr_err := route_response.MakeRouteResponse( 80 | w, 81 | req, 82 | resp_body, 83 | code, 84 | start, 85 | scan.ENDPOINT_NAME) 86 | if mr_err != nil { 87 | e := fmt.Sprintf("scan_route.ScanHandler %s", mr_err.Error()) 88 | log.Printf(e) 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /lib/query_route/query_route.go: -------------------------------------------------------------------------------- 1 | // Supports proxying the Query endpoint. 2 | package query_route 3 | 4 | import ( 5 | "encoding/json" 6 | "fmt" 7 | "github.com/smugmug/bbpd/lib/bbpd_runinfo" 8 | raw "github.com/smugmug/bbpd/lib/raw_post_route" 9 | "github.com/smugmug/bbpd/lib/route_response" 10 | ep "github.com/smugmug/godynamo/endpoint" 11 | query "github.com/smugmug/godynamo/endpoints/query" 12 | "io" 13 | "io/ioutil" 14 | "log" 15 | "net/http" 16 | "strings" 17 | "time" 18 | ) 19 | 20 | // RawPostHandler relays the Query request to Dynamo directly. 21 | func RawPostHandler(w http.ResponseWriter, req *http.Request) { 22 | raw.RawPostReq(w, req, query.QUERY_ENDPOINT) 23 | } 24 | 25 | // QueryHandler relays the Query request to Dynamo but first validates it through a local type. 26 | func QueryHandler(w http.ResponseWriter, req *http.Request) { 27 | if bbpd_runinfo.BBPDAbortIfClosed(w) { 28 | return 29 | } 30 | start := time.Now() 31 | if req.Method != "POST" { 32 | e := "query_route.QueryHandler:method only supports POST" 33 | log.Printf(e) 34 | http.Error(w, e, http.StatusBadRequest) 35 | return 36 | } 37 | pathElts := strings.Split(req.URL.Path, "/") 38 | if len(pathElts) != 2 { 39 | e := "query_route.QueryHandler:cannot parse path. try /create, call as POST" 40 | log.Printf(e) 41 | http.Error(w, e, http.StatusBadRequest) 42 | return 43 | } 44 | 45 | bodybytes, read_err := ioutil.ReadAll(req.Body) 46 | req.Body.Close() 47 | if read_err != nil && read_err != io.EOF { 48 | e := fmt.Sprintf("query_route.QueryHandler err reading req body: %s", read_err.Error()) 49 | log.Printf(e) 50 | http.Error(w, e, http.StatusInternalServerError) 51 | return 52 | } 53 | 54 | q := query.NewQuery() 55 | um_err := json.Unmarshal(bodybytes, q) 56 | 57 | if um_err != nil { 58 | e := fmt.Sprintf("query_route.QueryHandler unmarshal err on %s to Create: %s", string(bodybytes), um_err.Error()) 59 | log.Printf(e) 60 | http.Error(w, e, http.StatusInternalServerError) 61 | return 62 | } 63 | 64 | resp_body, code, resp_err := q.EndpointReq() 65 | 66 | if resp_err != nil { 67 | e := fmt.Sprintf("query_route.QueryHandler:err %s", 68 | resp_err.Error()) 69 | log.Printf(e) 70 | http.Error(w, e, http.StatusInternalServerError) 71 | return 72 | } 73 | 74 | if ep.HttpErr(code) { 75 | route_response.WriteError(w, code, "query_route.QueryHandler", resp_body) 76 | return 77 | } 78 | 79 | mr_err := route_response.MakeRouteResponse( 80 | w, 81 | req, 82 | resp_body, 83 | code, 84 | start, 85 | query.ENDPOINT_NAME) 86 | if mr_err != nil { 87 | e := fmt.Sprintf("query_route.QueryHandler %s", mr_err.Error()) 88 | log.Printf(e) 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /tests/batch_write_item-livetest1.sh: -------------------------------------------------------------------------------- 1 | curl -H "X-Amz-Target: DynamoDB_20120810.BatchWriteItem" -X POST -d '{"RequestItems":{"test-godynamo-livetest":[{"PutRequest":{"Item":{"SomeValue":{"N":"1"},"TheHashKey":{"S":"AHashKey1"},"TheRangeKey":{"N":"1"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"2"},"TheHashKey":{"S":"AHashKey2"},"TheRangeKey":{"N":"2"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"3"},"TheHashKey":{"S":"AHashKey3"},"TheRangeKey":{"N":"3"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"4"},"TheHashKey":{"S":"AHashKey4"},"TheRangeKey":{"N":"4"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"5"},"TheHashKey":{"S":"AHashKey5"},"TheRangeKey":{"N":"5"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"6"},"TheHashKey":{"S":"AHashKey6"},"TheRangeKey":{"N":"6"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"7"},"TheHashKey":{"S":"AHashKey7"},"TheRangeKey":{"N":"7"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"8"},"TheHashKey":{"S":"AHashKey8"},"TheRangeKey":{"N":"8"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"9"},"TheHashKey":{"S":"AHashKey9"},"TheRangeKey":{"N":"9"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"10"},"TheHashKey":{"S":"AHashKey10"},"TheRangeKey":{"N":"10"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"11"},"TheHashKey":{"S":"AHashKey11"},"TheRangeKey":{"N":"11"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"12"},"TheHashKey":{"S":"AHashKey12"},"TheRangeKey":{"N":"12"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"13"},"TheHashKey":{"S":"AHashKey13"},"TheRangeKey":{"N":"13"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"14"},"TheHashKey":{"S":"AHashKey14"},"TheRangeKey":{"N":"14"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"15"},"TheHashKey":{"S":"AHashKey15"},"TheRangeKey":{"N":"15"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"16"},"TheHashKey":{"S":"AHashKey16"},"TheRangeKey":{"N":"16"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"17"},"TheHashKey":{"S":"AHashKey17"},"TheRangeKey":{"N":"17"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"18"},"TheHashKey":{"S":"AHashKey18"},"TheRangeKey":{"N":"18"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"19"},"TheHashKey":{"S":"AHashKey19"},"TheRangeKey":{"N":"19"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"20"},"TheHashKey":{"S":"AHashKey20"},"TheRangeKey":{"N":"20"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"21"},"TheHashKey":{"S":"AHashKey21"},"TheRangeKey":{"N":"21"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"22"},"TheHashKey":{"S":"AHashKey22"},"TheRangeKey":{"N":"22"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"23"},"TheHashKey":{"S":"AHashKey23"},"TheRangeKey":{"N":"23"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"24"},"TheHashKey":{"S":"AHashKey24"},"TheRangeKey":{"N":"24"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"25"},"TheHashKey":{"S":"AHashKey25"},"TheRangeKey":{"N":"25"}}}}]},"ReturnConsumedCapacity":"NONE","ReturnItemCollectionMetrics":"NONE"}' http://localhost:12333/ 2 | -------------------------------------------------------------------------------- /tests/batch_write_item-livetest2.sh: -------------------------------------------------------------------------------- 1 | curl -H "X-Amz-Target: DynamoDB_20120810.BatchWriteItem" -X POST -d '{"RequestItems":{"test-godynamo-livetest":[{"PutRequest":{"Item":{"SomeValue":{"N":"26"},"TheHashKey":{"S":"AHashKey26"},"TheRangeKey":{"N":"26"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"27"},"TheHashKey":{"S":"AHashKey27"},"TheRangeKey":{"N":"27"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"28"},"TheHashKey":{"S":"AHashKey28"},"TheRangeKey":{"N":"28"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"29"},"TheHashKey":{"S":"AHashKey29"},"TheRangeKey":{"N":"29"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"30"},"TheHashKey":{"S":"AHashKey30"},"TheRangeKey":{"N":"30"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"31"},"TheHashKey":{"S":"AHashKey31"},"TheRangeKey":{"N":"31"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"32"},"TheHashKey":{"S":"AHashKey32"},"TheRangeKey":{"N":"32"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"33"},"TheHashKey":{"S":"AHashKey33"},"TheRangeKey":{"N":"33"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"34"},"TheHashKey":{"S":"AHashKey34"},"TheRangeKey":{"N":"34"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"35"},"TheHashKey":{"S":"AHashKey35"},"TheRangeKey":{"N":"35"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"36"},"TheHashKey":{"S":"AHashKey36"},"TheRangeKey":{"N":"36"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"37"},"TheHashKey":{"S":"AHashKey37"},"TheRangeKey":{"N":"37"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"38"},"TheHashKey":{"S":"AHashKey38"},"TheRangeKey":{"N":"38"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"39"},"TheHashKey":{"S":"AHashKey39"},"TheRangeKey":{"N":"39"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"40"},"TheHashKey":{"S":"AHashKey40"},"TheRangeKey":{"N":"40"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"41"},"TheHashKey":{"S":"AHashKey41"},"TheRangeKey":{"N":"41"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"42"},"TheHashKey":{"S":"AHashKey42"},"TheRangeKey":{"N":"42"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"43"},"TheHashKey":{"S":"AHashKey43"},"TheRangeKey":{"N":"43"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"44"},"TheHashKey":{"S":"AHashKey44"},"TheRangeKey":{"N":"44"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"45"},"TheHashKey":{"S":"AHashKey45"},"TheRangeKey":{"N":"45"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"46"},"TheHashKey":{"S":"AHashKey46"},"TheRangeKey":{"N":"46"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"47"},"TheHashKey":{"S":"AHashKey47"},"TheRangeKey":{"N":"47"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"48"},"TheHashKey":{"S":"AHashKey48"},"TheRangeKey":{"N":"48"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"49"},"TheHashKey":{"S":"AHashKey49"},"TheRangeKey":{"N":"49"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"50"},"TheHashKey":{"S":"AHashKey50"},"TheRangeKey":{"N":"50"}}}}]},"ReturnConsumedCapacity":"NONE","ReturnItemCollectionMetrics":"NONE"}' http://localhost:12333/ 2 | -------------------------------------------------------------------------------- /tests/batch_write_item-livetest3.sh: -------------------------------------------------------------------------------- 1 | curl -H "X-Amz-Target: DynamoDB_20120810.BatchWriteItem" -X POST -d '{"RequestItems":{"test-godynamo-livetest":[{"PutRequest":{"Item":{"SomeValue":{"N":"51"},"TheHashKey":{"S":"AHashKey51"},"TheRangeKey":{"N":"51"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"52"},"TheHashKey":{"S":"AHashKey52"},"TheRangeKey":{"N":"52"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"53"},"TheHashKey":{"S":"AHashKey53"},"TheRangeKey":{"N":"53"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"54"},"TheHashKey":{"S":"AHashKey54"},"TheRangeKey":{"N":"54"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"55"},"TheHashKey":{"S":"AHashKey55"},"TheRangeKey":{"N":"55"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"56"},"TheHashKey":{"S":"AHashKey56"},"TheRangeKey":{"N":"56"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"57"},"TheHashKey":{"S":"AHashKey57"},"TheRangeKey":{"N":"57"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"58"},"TheHashKey":{"S":"AHashKey58"},"TheRangeKey":{"N":"58"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"59"},"TheHashKey":{"S":"AHashKey59"},"TheRangeKey":{"N":"59"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"60"},"TheHashKey":{"S":"AHashKey60"},"TheRangeKey":{"N":"60"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"61"},"TheHashKey":{"S":"AHashKey61"},"TheRangeKey":{"N":"61"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"62"},"TheHashKey":{"S":"AHashKey62"},"TheRangeKey":{"N":"62"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"63"},"TheHashKey":{"S":"AHashKey63"},"TheRangeKey":{"N":"63"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"64"},"TheHashKey":{"S":"AHashKey64"},"TheRangeKey":{"N":"64"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"65"},"TheHashKey":{"S":"AHashKey65"},"TheRangeKey":{"N":"65"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"66"},"TheHashKey":{"S":"AHashKey66"},"TheRangeKey":{"N":"66"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"67"},"TheHashKey":{"S":"AHashKey67"},"TheRangeKey":{"N":"67"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"68"},"TheHashKey":{"S":"AHashKey68"},"TheRangeKey":{"N":"68"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"69"},"TheHashKey":{"S":"AHashKey69"},"TheRangeKey":{"N":"69"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"70"},"TheHashKey":{"S":"AHashKey70"},"TheRangeKey":{"N":"70"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"71"},"TheHashKey":{"S":"AHashKey71"},"TheRangeKey":{"N":"71"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"72"},"TheHashKey":{"S":"AHashKey72"},"TheRangeKey":{"N":"72"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"73"},"TheHashKey":{"S":"AHashKey73"},"TheRangeKey":{"N":"73"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"74"},"TheHashKey":{"S":"AHashKey74"},"TheRangeKey":{"N":"74"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"75"},"TheHashKey":{"S":"AHashKey75"},"TheRangeKey":{"N":"75"}}}}]},"ReturnConsumedCapacity":"NONE","ReturnItemCollectionMetrics":"NONE"}' http://localhost:12333/ 2 | -------------------------------------------------------------------------------- /tests/batch_write_item-livetest4.sh: -------------------------------------------------------------------------------- 1 | curl -H "X-Amz-Target: DynamoDB_20120810.BatchWriteItem" -X POST -d '{"RequestItems":{"test-godynamo-livetest":[{"PutRequest":{"Item":{"SomeValue":{"N":"76"},"TheHashKey":{"S":"AHashKey76"},"TheRangeKey":{"N":"76"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"77"},"TheHashKey":{"S":"AHashKey77"},"TheRangeKey":{"N":"77"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"78"},"TheHashKey":{"S":"AHashKey78"},"TheRangeKey":{"N":"78"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"79"},"TheHashKey":{"S":"AHashKey79"},"TheRangeKey":{"N":"79"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"80"},"TheHashKey":{"S":"AHashKey80"},"TheRangeKey":{"N":"80"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"81"},"TheHashKey":{"S":"AHashKey81"},"TheRangeKey":{"N":"81"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"82"},"TheHashKey":{"S":"AHashKey82"},"TheRangeKey":{"N":"82"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"83"},"TheHashKey":{"S":"AHashKey83"},"TheRangeKey":{"N":"83"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"84"},"TheHashKey":{"S":"AHashKey84"},"TheRangeKey":{"N":"84"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"85"},"TheHashKey":{"S":"AHashKey85"},"TheRangeKey":{"N":"85"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"86"},"TheHashKey":{"S":"AHashKey86"},"TheRangeKey":{"N":"86"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"87"},"TheHashKey":{"S":"AHashKey87"},"TheRangeKey":{"N":"87"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"88"},"TheHashKey":{"S":"AHashKey88"},"TheRangeKey":{"N":"88"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"89"},"TheHashKey":{"S":"AHashKey89"},"TheRangeKey":{"N":"89"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"90"},"TheHashKey":{"S":"AHashKey90"},"TheRangeKey":{"N":"90"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"91"},"TheHashKey":{"S":"AHashKey91"},"TheRangeKey":{"N":"91"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"92"},"TheHashKey":{"S":"AHashKey92"},"TheRangeKey":{"N":"92"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"93"},"TheHashKey":{"S":"AHashKey93"},"TheRangeKey":{"N":"93"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"94"},"TheHashKey":{"S":"AHashKey94"},"TheRangeKey":{"N":"94"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"95"},"TheHashKey":{"S":"AHashKey95"},"TheRangeKey":{"N":"95"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"96"},"TheHashKey":{"S":"AHashKey96"},"TheRangeKey":{"N":"96"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"97"},"TheHashKey":{"S":"AHashKey97"},"TheRangeKey":{"N":"97"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"98"},"TheHashKey":{"S":"AHashKey98"},"TheRangeKey":{"N":"98"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"99"},"TheHashKey":{"S":"AHashKey99"},"TheRangeKey":{"N":"99"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"100"},"TheHashKey":{"S":"AHashKey100"},"TheRangeKey":{"N":"100"}}}}]},"ReturnConsumedCapacity":"NONE","ReturnItemCollectionMetrics":"NONE"}' http://localhost:12333/ 2 | -------------------------------------------------------------------------------- /lib/update_item_route/update_item_route.go: -------------------------------------------------------------------------------- 1 | // Supports proxying the UpdateItem endpoint. 2 | package update_item_route 3 | 4 | import ( 5 | "encoding/json" 6 | "fmt" 7 | "github.com/smugmug/bbpd/lib/bbpd_runinfo" 8 | raw "github.com/smugmug/bbpd/lib/raw_post_route" 9 | "github.com/smugmug/bbpd/lib/route_response" 10 | ep "github.com/smugmug/godynamo/endpoint" 11 | update_item "github.com/smugmug/godynamo/endpoints/update_item" 12 | "io" 13 | "io/ioutil" 14 | "log" 15 | "net/http" 16 | "strings" 17 | "time" 18 | ) 19 | 20 | // RawPostHandler relays the UpdateItem request to Dynamo directly. 21 | func RawPostHandler(w http.ResponseWriter, req *http.Request) { 22 | raw.RawPostReq(w, req, update_item.UPDATEITEM_ENDPOINT) 23 | } 24 | 25 | // UpdateItemHandler relays the UpdateItem request to Dynamo but first validates it through a local type. 26 | func UpdateItemHandler(w http.ResponseWriter, req *http.Request) { 27 | if bbpd_runinfo.BBPDAbortIfClosed(w) { 28 | return 29 | } 30 | start := time.Now() 31 | if req.Method != "POST" { 32 | e := "update_item_route.UpdateItemHandler:method only supports POST" 33 | log.Printf(e) 34 | http.Error(w, e, http.StatusBadRequest) 35 | return 36 | } 37 | pathElts := strings.Split(req.URL.Path, "/") 38 | if len(pathElts) != 2 { 39 | e := "update_item_route.UpdateItemHandler:cannot parse path. try /update-item" 40 | log.Printf(e) 41 | http.Error(w, e, http.StatusBadRequest) 42 | return 43 | } 44 | 45 | bodybytes, read_err := ioutil.ReadAll(req.Body) 46 | req.Body.Close() 47 | if read_err != nil && read_err != io.EOF { 48 | e := fmt.Sprintf("update_item_route.UpdateItemHandler err reading req body: %s", read_err.Error()) 49 | log.Printf(e) 50 | http.Error(w, e, http.StatusInternalServerError) 51 | return 52 | } 53 | 54 | u := update_item.NewUpdateItem() 55 | um_err := json.Unmarshal(bodybytes, u) 56 | 57 | if um_err != nil { 58 | e := fmt.Sprintf("update_item_route.UpdateItemHandler unmarshal err on %s to Update: %s", string(bodybytes), um_err.Error()) 59 | log.Printf(e) 60 | http.Error(w, e, http.StatusInternalServerError) 61 | return 62 | } 63 | 64 | resp_body, code, resp_err := u.EndpointReq() 65 | 66 | if resp_err != nil { 67 | e := fmt.Sprintf("update_item_route.UpdateItemHandler:err %s", 68 | resp_err.Error()) 69 | log.Printf(e) 70 | http.Error(w, e, http.StatusInternalServerError) 71 | return 72 | } 73 | 74 | if ep.HttpErr(code) { 75 | route_response.WriteError(w, code, "update_item_route.UpdateItemHandler", resp_body) 76 | return 77 | } 78 | 79 | mr_err := route_response.MakeRouteResponse( 80 | w, 81 | req, 82 | resp_body, 83 | code, 84 | start, 85 | update_item.ENDPOINT_NAME) 86 | if mr_err != nil { 87 | e := fmt.Sprintf("update_item_route.UpdateItemHandler %s", mr_err.Error()) 88 | log.Printf(e) 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /lib/update_table_route/update_table_route.go: -------------------------------------------------------------------------------- 1 | // Supports proxying the UpdateItem endpoint. 2 | package update_table_route 3 | 4 | import ( 5 | "encoding/json" 6 | "fmt" 7 | "github.com/smugmug/bbpd/lib/bbpd_runinfo" 8 | raw "github.com/smugmug/bbpd/lib/raw_post_route" 9 | "github.com/smugmug/bbpd/lib/route_response" 10 | ep "github.com/smugmug/godynamo/endpoint" 11 | update_table "github.com/smugmug/godynamo/endpoints/update_table" 12 | "io" 13 | "io/ioutil" 14 | "log" 15 | "net/http" 16 | "strings" 17 | "time" 18 | ) 19 | 20 | // RawPostHandler relays the UpdateTable request to Dynamo directly. 21 | func RawPostHandler(w http.ResponseWriter, req *http.Request) { 22 | raw.RawPostReq(w, req, update_table.UPDATETABLE_ENDPOINT) 23 | } 24 | 25 | // UpdateTableHandler relays the UpdateTable request to Dynamo but first validates it through a local type. 26 | func UpdateTableHandler(w http.ResponseWriter, req *http.Request) { 27 | if bbpd_runinfo.BBPDAbortIfClosed(w) { 28 | return 29 | } 30 | start := time.Now() 31 | if req.Method != "POST" { 32 | e := "update_table_route.UpdateTableHandler:method only supports POST" 33 | log.Printf(e) 34 | http.Error(w, e, http.StatusBadRequest) 35 | return 36 | } 37 | pathElts := strings.Split(req.URL.Path, "/") 38 | if len(pathElts) != 2 { 39 | e := "update_table_route.UpdateTableHandler:cannot parse path. try /update-table" 40 | log.Printf(e) 41 | http.Error(w, e, http.StatusBadRequest) 42 | return 43 | } 44 | 45 | bodybytes, read_err := ioutil.ReadAll(req.Body) 46 | req.Body.Close() 47 | if read_err != nil && read_err != io.EOF { 48 | e := fmt.Sprintf("update_table_route.UpdateTableHandler err reading req body: %s", read_err.Error()) 49 | log.Printf(e) 50 | http.Error(w, e, http.StatusInternalServerError) 51 | return 52 | } 53 | 54 | u := update_table.NewUpdateTable() 55 | um_err := json.Unmarshal(bodybytes, u) 56 | 57 | if um_err != nil { 58 | e := fmt.Sprintf("update_table_route.UpdateTableHandler unmarshal err on %s to Update: %s", string(bodybytes), um_err.Error()) 59 | log.Printf(e) 60 | http.Error(w, e, http.StatusInternalServerError) 61 | return 62 | } 63 | 64 | resp_body, code, resp_err := u.EndpointReq() 65 | 66 | if resp_err != nil { 67 | e := fmt.Sprintf("update_item_route.UpdateTableHandler:err %s", 68 | resp_err.Error()) 69 | log.Printf(e) 70 | http.Error(w, e, http.StatusInternalServerError) 71 | return 72 | } 73 | 74 | if ep.HttpErr(code) { 75 | route_response.WriteError(w, code, "update_item_route.UpdateTableHandler", resp_body) 76 | return 77 | } 78 | 79 | mr_err := route_response.MakeRouteResponse( 80 | w, 81 | req, 82 | resp_body, 83 | code, 84 | start, 85 | update_table.ENDPOINT_NAME) 86 | if mr_err != nil { 87 | e := fmt.Sprintf("update_table_route.UpdateTableHandler %s", mr_err.Error()) 88 | log.Printf(e) 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /lib/delete_item_route/delete_item_route.go: -------------------------------------------------------------------------------- 1 | // Supports proxying the DeleteItem endpoint. 2 | package delete_item_route 3 | 4 | import ( 5 | "encoding/json" 6 | "fmt" 7 | "github.com/smugmug/bbpd/lib/bbpd_runinfo" 8 | raw "github.com/smugmug/bbpd/lib/raw_post_route" 9 | "github.com/smugmug/bbpd/lib/route_response" 10 | ep "github.com/smugmug/godynamo/endpoint" 11 | delete_item "github.com/smugmug/godynamo/endpoints/delete_item" 12 | "io" 13 | "io/ioutil" 14 | "log" 15 | "net/http" 16 | "strings" 17 | "time" 18 | ) 19 | 20 | // RawPostHandler relays the DeleteItem request to Dynamo directly. 21 | func RawPostHandler(w http.ResponseWriter, req *http.Request) { 22 | raw.RawPostReq(w, req, delete_item.DELETEITEM_ENDPOINT) 23 | } 24 | 25 | // DeleteItemHandler relays the DeleteItem request to Dynamo but first validates it through a local type. 26 | func DeleteItemHandler(w http.ResponseWriter, req *http.Request) { 27 | if bbpd_runinfo.BBPDAbortIfClosed(w) { 28 | return 29 | } 30 | start := time.Now() 31 | if req.Method != "POST" { 32 | e := fmt.Sprintf("delete_item_route.DeleteItemHandler: method only supports POST") 33 | log.Printf(e) 34 | http.Error(w, e, http.StatusBadRequest) 35 | return 36 | } 37 | pathElts := strings.Split(req.URL.Path, "/") 38 | if len(pathElts) != 2 { 39 | e := fmt.Sprintf("delete_item_route.DeleteItemHandler:cannot parse path. try /delete-item, call as POST") 40 | log.Printf(e) 41 | http.Error(w, e, http.StatusBadRequest) 42 | return 43 | } 44 | 45 | bodybytes, read_err := ioutil.ReadAll(req.Body) 46 | req.Body.Close() 47 | if read_err != nil && read_err != io.EOF { 48 | e := fmt.Sprintf("delete_item_route.DeleteItemHandler err reading req body: %s", read_err.Error()) 49 | log.Printf(e) 50 | http.Error(w, e, http.StatusInternalServerError) 51 | return 52 | } 53 | 54 | d := delete_item.NewDelete() 55 | um_err := json.Unmarshal(bodybytes, d) 56 | 57 | if um_err != nil { 58 | e := fmt.Sprintf("delete_item_route.DeleteItemHandler unmarshal err on %s to PutExpected: %s", string(bodybytes), um_err.Error()) 59 | log.Printf(e) 60 | http.Error(w, e, http.StatusInternalServerError) 61 | return 62 | } 63 | 64 | resp_body, code, resp_err := d.EndpointReq() 65 | 66 | if resp_err != nil { 67 | e := fmt.Sprintf("delete_item_route.DeleteItemHandler:err %s", 68 | resp_err.Error()) 69 | log.Printf(e) 70 | http.Error(w, e, http.StatusInternalServerError) 71 | return 72 | } 73 | 74 | if ep.HttpErr(code) { 75 | route_response.WriteError(w, code, "delete_item_route.DeleteItemHandler", resp_body) 76 | return 77 | } 78 | 79 | mr_err := route_response.MakeRouteResponse( 80 | w, 81 | req, 82 | resp_body, 83 | code, 84 | start, 85 | delete_item.ENDPOINT_NAME) 86 | if mr_err != nil { 87 | e := fmt.Sprintf("delete_item_route.DeleteItemHandler %s", mr_err.Error()) 88 | log.Printf(e) 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /lib/raw_post_route/raw_post_route.go: -------------------------------------------------------------------------------- 1 | package raw_post_route 2 | 3 | import ( 4 | "fmt" 5 | "github.com/smugmug/bbpd/lib/bbpd_runinfo" 6 | "github.com/smugmug/bbpd/lib/route_response" 7 | "github.com/smugmug/godynamo/authreq" 8 | ep "github.com/smugmug/godynamo/endpoint" 9 | "io" 10 | "io/ioutil" 11 | "log" 12 | "net/http" 13 | "net/url" 14 | "strings" 15 | "time" 16 | ) 17 | 18 | // RawPostHandler relays POST data directly to Dynamo, typically called by other endpoint proxy packages 19 | // that are recognized by the string in the request path. 20 | func RawPostHandler(w http.ResponseWriter, req *http.Request) { 21 | if bbpd_runinfo.BBPDAbortIfClosed(w) { 22 | return 23 | } 24 | if req.Method != "POST" { 25 | e := fmt.Sprintf("raw_post_route.RawPostHandler: method only supports POST") 26 | log.Printf(e) 27 | http.Error(w, e, http.StatusBadRequest) 28 | return 29 | } 30 | pathElts := strings.Split(req.URL.Path, "/") 31 | if len(pathElts) != 3 { 32 | e := "raw_post_route.RawPostHandler:cannot parse path" 33 | log.Printf(e) 34 | http.Error(w, e, http.StatusBadRequest) 35 | return 36 | } 37 | 38 | ue_ep, ue_err := url.QueryUnescape(string(pathElts[2])) 39 | if ue_err != nil { 40 | e := fmt.Sprintf("raw_table_route.RawPostHandler:cannot unescape %s, %s", string(pathElts[2]), ue_err.Error()) 41 | log.Printf(e) 42 | http.Error(w, e, http.StatusInternalServerError) 43 | return 44 | } 45 | 46 | RawPostReq(w, req, ue_ep) 47 | } 48 | 49 | // RawPostReq obtains the POST payload from the request and forwards it on to the endpoint amzTarget. 50 | func RawPostReq(w http.ResponseWriter, req *http.Request, amzTarget string) { 51 | if bbpd_runinfo.BBPDAbortIfClosed(w) { 52 | return 53 | } 54 | start := time.Now() 55 | bodybytes, read_err := ioutil.ReadAll(req.Body) 56 | req.Body.Close() 57 | if read_err != nil && read_err != io.EOF { 58 | e := fmt.Sprintf("raw_post_route.RawPostReq err reading req body: %s", read_err.Error()) 59 | log.Printf(e) 60 | http.Error(w, e, http.StatusInternalServerError) 61 | return 62 | } 63 | 64 | resp_body, code, resp_err := authreq.RetryReqJSON_V4(bodybytes, amzTarget) 65 | 66 | if resp_err != nil { 67 | e := fmt.Sprintf("raw_post_route.RawPostReq: resp err calling %s err %s (input json: %s)", 68 | amzTarget, resp_err.Error(), string(bodybytes)) 69 | log.Printf(e) 70 | http.Error(w, e, http.StatusInternalServerError) 71 | return 72 | } 73 | 74 | if ep.HttpErr(code) { 75 | e := fmt.Sprintf("raw_post_route.RawPostReq: http err %d calling %s (input json: %s)", 76 | code, amzTarget, string(bodybytes)) 77 | route_response.WriteError(w, code, e, resp_body) 78 | return 79 | } 80 | 81 | mr_err := route_response.MakeRouteResponse( 82 | w, 83 | req, 84 | resp_body, 85 | code, 86 | start, 87 | amzTarget) 88 | if mr_err != nil { 89 | e := fmt.Sprintf("raw_post_route.RawPostReq %s", mr_err.Error()) 90 | log.Printf(e) 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /lib/create_table_route/create_table_route.go: -------------------------------------------------------------------------------- 1 | // Supports proxying the CreateTable endpoint. 2 | package create_table_route 3 | 4 | import ( 5 | "encoding/json" 6 | "fmt" 7 | "github.com/smugmug/bbpd/lib/bbpd_runinfo" 8 | raw "github.com/smugmug/bbpd/lib/raw_post_route" 9 | "github.com/smugmug/bbpd/lib/route_response" 10 | ep "github.com/smugmug/godynamo/endpoint" 11 | create "github.com/smugmug/godynamo/endpoints/create_table" 12 | "io" 13 | "io/ioutil" 14 | "log" 15 | "net/http" 16 | "strings" 17 | "time" 18 | ) 19 | 20 | // RawPostHandler relays the CreateTable request to Dynamo directly. 21 | func RawPostHandler(w http.ResponseWriter, req *http.Request) { 22 | raw.RawPostReq(w, req, create.CREATETABLE_ENDPOINT) 23 | } 24 | 25 | // CreateTableHandler relays the CreateTable request to Dynamo but first validates it through a local type. 26 | func CreateTableHandler(w http.ResponseWriter, req *http.Request) { 27 | if bbpd_runinfo.BBPDAbortIfClosed(w) { 28 | return 29 | } 30 | start := time.Now() 31 | if req.Method != "POST" { 32 | e := "create_table_route.CreateTableHandler:method only supports POST" 33 | log.Printf(e) 34 | http.Error(w, e, http.StatusBadRequest) 35 | return 36 | } 37 | pathElts := strings.Split(req.URL.Path, "/") 38 | if len(pathElts) != 2 { 39 | e := "create_table_route.CreateTableHandler:cannot parse path. try /create, call as POST" 40 | log.Printf(e) 41 | http.Error(w, e, http.StatusBadRequest) 42 | return 43 | } 44 | 45 | bodybytes, read_err := ioutil.ReadAll(req.Body) 46 | req.Body.Close() 47 | if read_err != nil && read_err != io.EOF { 48 | e := fmt.Sprintf("create_table_route.CreateTableHandler err reading req body: %s", read_err.Error()) 49 | log.Printf(e) 50 | http.Error(w, e, http.StatusInternalServerError) 51 | return 52 | } 53 | 54 | c := create.NewCreate() 55 | um_err := json.Unmarshal(bodybytes, c) 56 | 57 | if um_err != nil { 58 | e := fmt.Sprintf("create_table_route.CreateTableHandler unmarshal err on %s to Create: %s", string(bodybytes), um_err.Error()) 59 | log.Printf(e) 60 | http.Error(w, e, http.StatusInternalServerError) 61 | return 62 | } 63 | 64 | // the table name can't be too long, 256 bytes binary utf8 65 | if !create.ValidTableName(c.TableName) { 66 | e := fmt.Sprintf("create_table_route.CreateTableHandler: tablename over 256 bytes") 67 | log.Printf(e) 68 | http.Error(w, e, http.StatusBadRequest) 69 | return 70 | } 71 | 72 | resp_body, code, resp_err := c.EndpointReq() 73 | 74 | if resp_err != nil { 75 | e := fmt.Sprintf("create_table_route.CreateTableHandler:err %s", 76 | resp_err.Error()) 77 | log.Printf(e) 78 | http.Error(w, e, http.StatusInternalServerError) 79 | return 80 | } 81 | 82 | if ep.HttpErr(code) { 83 | route_response.WriteError(w, code, "create_table_route.CreateTableHandler", resp_body) 84 | return 85 | } 86 | 87 | mr_err := route_response.MakeRouteResponse( 88 | w, 89 | req, 90 | resp_body, 91 | code, 92 | start, 93 | create.ENDPOINT_NAME) 94 | if mr_err != nil { 95 | e := fmt.Sprintf("create_table_route.CreateTableHandler %s", mr_err.Error()) 96 | log.Printf(e) 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /lib/delete_table_route/delete_table_route.go: -------------------------------------------------------------------------------- 1 | // Supports proxying the DeleteTable endpoint. 2 | // It is recommended that you do NOT bind this to a route, its too dangerous. 3 | package delete_table_route 4 | 5 | import ( 6 | "encoding/json" 7 | "fmt" 8 | "github.com/smugmug/bbpd/lib/bbpd_runinfo" 9 | raw "github.com/smugmug/bbpd/lib/raw_post_route" 10 | "github.com/smugmug/bbpd/lib/route_response" 11 | ep "github.com/smugmug/godynamo/endpoint" 12 | delete_table "github.com/smugmug/godynamo/endpoints/delete_table" 13 | "io" 14 | "io/ioutil" 15 | "log" 16 | "net/http" 17 | "strings" 18 | "time" 19 | ) 20 | 21 | // RawPostHandler relays the DeleteTable request to Dynamo directly. 22 | func RawPostHandler(w http.ResponseWriter, req *http.Request) { 23 | raw.RawPostReq(w, req, delete_table.DELETETABLE_ENDPOINT) 24 | } 25 | 26 | // DeleteTableHandler can be used via POST (passing in JSON) or GET (as /DeleteTable/TableName). 27 | func DeleteTableHandler(w http.ResponseWriter, req *http.Request) { 28 | if bbpd_runinfo.BBPDAbortIfClosed(w) { 29 | return 30 | } 31 | if req.Method == "POST" { 32 | deleteTable_POST_Handler(w, req) 33 | } else { 34 | e := fmt.Sprintf("delete_table_route.DeleteTablesHandler:bad method %s", req.Method) 35 | log.Printf(e) 36 | http.Error(w, e, http.StatusInternalServerError) 37 | } 38 | } 39 | 40 | // Executes DeleteTable assuming it were requested with the POST method. 41 | func deleteTable_POST_Handler(w http.ResponseWriter, req *http.Request) { 42 | start := time.Now() 43 | pathElts := strings.Split(req.URL.Path, "/") 44 | if len(pathElts) != 2 { 45 | e := "delete_table_route.deleteTable_POST_Handler:cannot parse path. try /desc-table" 46 | log.Printf(e) 47 | http.Error(w, e, http.StatusBadRequest) 48 | return 49 | } 50 | 51 | bodybytes, read_err := ioutil.ReadAll(req.Body) 52 | req.Body.Close() 53 | if read_err != nil && read_err != io.EOF { 54 | e := fmt.Sprintf("delete_table_route.deleteTable_POST_Handler err reading req body: %s", read_err.Error()) 55 | log.Printf(e) 56 | http.Error(w, e, http.StatusInternalServerError) 57 | return 58 | } 59 | 60 | d := delete_table.NewDeleteTable() 61 | 62 | um_err := json.Unmarshal(bodybytes, d) 63 | if um_err != nil { 64 | e := fmt.Sprintf("delete_table_route.deleteTable_POST_Handler unmarshal err on %s to Get %s", string(bodybytes), um_err.Error()) 65 | log.Printf(e) 66 | http.Error(w, e, http.StatusInternalServerError) 67 | return 68 | } 69 | 70 | resp_body, code, resp_err := d.EndpointReq() 71 | 72 | if resp_err != nil { 73 | e := fmt.Sprintf("delete_table_route.deleteTable_POST_Handler:err %s", 74 | resp_err.Error()) 75 | log.Printf(e) 76 | http.Error(w, e, http.StatusInternalServerError) 77 | return 78 | } 79 | 80 | if ep.HttpErr(code) { 81 | route_response.WriteError(w, code, "delete_table_route.deleteTable_POST_Handler", resp_body) 82 | return 83 | } 84 | 85 | mr_err := route_response.MakeRouteResponse( 86 | w, 87 | req, 88 | resp_body, 89 | code, 90 | start, 91 | delete_table.ENDPOINT_NAME) 92 | if mr_err != nil { 93 | e := fmt.Sprintf("delete_table_route.deleteTable_POST_Handler %s", 94 | mr_err.Error()) 95 | log.Printf(e) 96 | http.Error(w, e, http.StatusInternalServerError) 97 | return 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /bbpd.go: -------------------------------------------------------------------------------- 1 | // bbpd is a proxy daemon for Amazon's DynamoDB. See ../../README.md 2 | package main 3 | 4 | import ( 5 | "fmt" 6 | "github.com/smugmug/bbpd/lib/bbpd_const" 7 | "github.com/smugmug/bbpd/lib/bbpd_route" 8 | conf "github.com/smugmug/godynamo/conf" 9 | conf_file "github.com/smugmug/godynamo/conf_file" 10 | conf_iam "github.com/smugmug/godynamo/conf_iam" 11 | keepalive "github.com/smugmug/godynamo/keepalive" 12 | "log" 13 | "os" 14 | "os/signal" 15 | "runtime" 16 | "syscall" 17 | ) 18 | 19 | // handle signals. we prefer 1,3,15 and will panic on 2 20 | func sigHandle(c <-chan os.Signal) { 21 | for sig := range c { 22 | if sig == syscall.SIGTERM || sig == syscall.SIGQUIT || sig == syscall.SIGHUP { 23 | log.Printf("*** caught signal %v, stop\n", sig) 24 | log.Printf("bbpd is in a closed state and is no longer accepting connections") 25 | stop_err := bbpd_route.StopBBPD() 26 | if stop_err != nil { 27 | log.Printf("graceful shutdown not possible:%s", stop_err.Error()) 28 | } 29 | log.Printf("bbpd exit\n") 30 | os.Exit(0) 31 | } else if sig == syscall.SIGINT { 32 | log.Printf("*** caught signal %v, PANIC stop\n", sig) 33 | panic("bbpd panic") 34 | os.Exit(1) 35 | } else { 36 | log.Printf("**** caught unchecked signal %v\n", sig) 37 | } 38 | } 39 | } 40 | 41 | func main() { 42 | runtime.GOMAXPROCS(runtime.NumCPU()) 43 | 44 | sigchan := make(chan os.Signal, 1) 45 | signal.Notify(sigchan) 46 | go sigHandle(sigchan) 47 | 48 | // conf file must be read in before anything else, to initialize permissions etc 49 | conf_file.Read() 50 | conf.Vals.ConfLock.RLock() 51 | if conf.Vals.Initialized == false { 52 | panic("the conf.Vals global conf struct has not been initialized, " + 53 | "invoke with conf_file.Read()") 54 | } else { 55 | log.Printf("global conf.Vals initialized") 56 | } 57 | 58 | // launch a background poller to keep conns to aws alive 59 | if conf.Vals.Network.DynamoDB.KeepAlive { 60 | log.Printf("launching background keepalive") 61 | go keepalive.KeepAlive([]string{conf.Vals.Network.DynamoDB.URL}) 62 | } 63 | 64 | // we must give up the lock on the conf before calling GoIAM below, or it 65 | // will not be able to mutate the auth params 66 | using_iam := (conf.Vals.UseIAM == true) 67 | conf.Vals.ConfLock.RUnlock() 68 | 69 | // the naive "fire and forget" IAM roles initializer and watcher. 70 | if using_iam { 71 | iam_ready_chan := make(chan bool) 72 | go conf_iam.GoIAM(iam_ready_chan) 73 | iam_ready := <-iam_ready_chan 74 | if !iam_ready { 75 | panic("iam is not ready? auth problem") 76 | } 77 | } else { 78 | log.Printf("not using iam, assume credentials hardcoded in conf file") 79 | } 80 | 81 | log.Printf("starting bbpd...") 82 | pid := syscall.Getpid() 83 | e := fmt.Sprintf("induce panic with ctrl-c (kill -2 %v) or graceful termination with kill -[1,3,15] %v", pid, pid) 84 | log.Printf(e) 85 | ports := []int{bbpd_const.PORT, bbpd_const.PORT2} 86 | start_bbpd_err := bbpd_route.StartBBPD(ports) 87 | if start_bbpd_err == nil { 88 | // all ports are in use. exit with 0 so our rc system does not 89 | // respawn the program 90 | log.Printf("all bbpd ports appear to be in use: exit with code 0") 91 | os.Exit(0) 92 | } else { 93 | // abnormal exit - allow the rc system to try to respawn by returning 94 | // exit code 1 95 | log.Printf("bbpd invocation error") 96 | log.Fatal(start_bbpd_err.Error()) 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /lib/route_response/route_response.go: -------------------------------------------------------------------------------- 1 | package route_response 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "errors" 7 | "fmt" 8 | "github.com/smugmug/bbpd/lib/bbpd_const" 9 | "github.com/smugmug/bbpd/lib/bbpd_msg" 10 | "github.com/smugmug/bbpd/lib/bbpd_stats" 11 | ep "github.com/smugmug/godynamo/endpoint" 12 | "io" 13 | "log" 14 | "net/http" 15 | "strconv" 16 | "time" 17 | ) 18 | 19 | // WriteError is a convenience wrapper for emitting an error. 20 | func WriteError(w http.ResponseWriter, code int, origin string, resp_body []byte) { 21 | if ep.ReqErr(code) { // 4xx err 22 | e := fmt.Sprintf("%s:(%d) %s", origin, code, string(resp_body)) 23 | log.Printf(e) 24 | http.Error(w, e, http.StatusBadRequest) 25 | } else { // 5xx err 26 | e := fmt.Sprintf("%s:(%d) Server Error", origin, code) 27 | log.Printf(e) 28 | http.Error(w, e, http.StatusInternalServerError) 29 | } 30 | } 31 | 32 | // MakeRouteResponse wraps a dynamo response with some debugging information related to http codes and request duration. 33 | func MakeRouteResponse(w http.ResponseWriter, req *http.Request, resp_body []byte, code int, start time.Time, endpoint_name string) error { 34 | end := time.Now() 35 | duration := fmt.Sprintf("%v", end.Sub(start)) 36 | if resp_body != nil && code == http.StatusOK { 37 | // add the response to the stats 38 | bbpd_stats.AddResponse(start) 39 | 40 | var b []byte 41 | var json_err error 42 | w.Header().Set(bbpd_const.CONTENTTYPE, bbpd_const.JSONMIME) 43 | 44 | // look for the X-BBPD-Verbose and X-BBPD-Indent headers 45 | verbose_output := false // should output include timing info? 46 | indent_output := false // should output be indented/pretty-printed? 47 | _, verbose_output = req.Header[bbpd_const.X_BBPD_VERBOSE] 48 | _, indent_output = req.Header[bbpd_const.X_BBPD_INDENT] 49 | 50 | if !verbose_output { 51 | b = resp_body 52 | } else { 53 | b, json_err = json.Marshal(bbpd_msg.Response{ 54 | Name: endpoint_name, 55 | StatusCode: code, 56 | Body: resp_body, 57 | Run: bbpd_msg.RunInfo{Method: req.Method, 58 | Host: bbpd_const.LOCALHOST, 59 | Duration: duration, 60 | Start: start, 61 | End: end}}) 62 | if json_err != nil { 63 | e := fmt.Sprintf("route_response.MakeRouteResponse:marshal failure %s", 64 | json_err.Error()) 65 | log.Printf(e) 66 | http.Error(w, e, http.StatusInternalServerError) 67 | return json_err 68 | } 69 | } 70 | 71 | // we support pretty-printing (indent) 72 | // just pass indent=1 (the 1 can be anything) in the url 73 | if indent_output { 74 | var buf bytes.Buffer 75 | if i_err := json.Indent(&buf, b, "", "\t"); i_err != nil { 76 | // could not pretty print! 77 | e := fmt.Sprintf("route_response.MakeRouteResponse cannot indent %s", string(b)) 78 | log.Printf(e) 79 | unindented_str := string(b) 80 | w.Header().Set(bbpd_const.CONTENTLENGTH, 81 | strconv.Itoa(len(unindented_str))) 82 | io.WriteString(w, unindented_str) 83 | } else { 84 | // do the pretty print 85 | indented_str := buf.String() 86 | w.Header().Set(bbpd_const.CONTENTLENGTH, 87 | strconv.Itoa(len(indented_str))) 88 | io.WriteString(w, indented_str) 89 | } 90 | } else { 91 | // no pretty print requested 92 | unindented_str := string(b) 93 | w.Header().Set(bbpd_const.CONTENTLENGTH, 94 | strconv.Itoa(len(unindented_str))) 95 | io.WriteString(w, unindented_str) 96 | } 97 | return nil 98 | } else { 99 | s := "" 100 | if resp_body != nil { 101 | s = string(resp_body) 102 | } 103 | e := fmt.Sprintf("route_response.MakeRouteResponse %s", s) 104 | log.Printf(e) 105 | http.Error(w, e, http.StatusBadRequest) 106 | return errors.New(e) 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /lib/get_item_route/get_item_route.go: -------------------------------------------------------------------------------- 1 | // Supports proxying the GetItem endpoint. 2 | package get_item_route 3 | 4 | import ( 5 | "encoding/json" 6 | "fmt" 7 | "github.com/smugmug/bbpd/lib/bbpd_runinfo" 8 | raw "github.com/smugmug/bbpd/lib/raw_post_route" 9 | "github.com/smugmug/bbpd/lib/route_response" 10 | "github.com/smugmug/godynamo/authreq" 11 | ep "github.com/smugmug/godynamo/endpoint" 12 | get "github.com/smugmug/godynamo/endpoints/get_item" 13 | "io" 14 | "io/ioutil" 15 | "log" 16 | "net/http" 17 | "strings" 18 | "time" 19 | ) 20 | 21 | // RawPostHandler relays the GetItem request to Dynamo directly. 22 | func RawPostHandler(w http.ResponseWriter, req *http.Request) { 23 | raw.RawPostReq(w, req, get.GETITEM_ENDPOINT) 24 | } 25 | 26 | // GetItemHandler relays the GetItem request to Dynamo but first validates it through a local type. 27 | func GetItemHandler(w http.ResponseWriter, req *http.Request) { 28 | if bbpd_runinfo.BBPDAbortIfClosed(w) { 29 | return 30 | } 31 | start := time.Now() 32 | if req.Method != "POST" { 33 | e := "get_item_route.GetItemHandler:method only supports POST" 34 | log.Printf(e) 35 | http.Error(w, e, http.StatusBadRequest) 36 | return 37 | } 38 | pathElts := strings.Split(req.URL.Path, "/") 39 | if len(pathElts) != 2 { 40 | e := "get_item_route.GetItemHandler:cannot parse path." 41 | log.Printf(e) 42 | http.Error(w, e, http.StatusBadRequest) 43 | return 44 | } 45 | 46 | bodybytes, read_err := ioutil.ReadAll(req.Body) 47 | req.Body.Close() 48 | if read_err != nil && read_err != io.EOF { 49 | e := fmt.Sprintf("get_item_route.GetItemHandler err reading req body: %s", read_err.Error()) 50 | log.Printf(e) 51 | http.Error(w, e, http.StatusInternalServerError) 52 | return 53 | } 54 | 55 | g := get.NewGetItem() 56 | 57 | um_err := json.Unmarshal(bodybytes, g) 58 | if um_err != nil { 59 | e := fmt.Sprintf("get_item_route.GetItemHandler unmarshal err on %s to Get %s", string(bodybytes), um_err.Error()) 60 | log.Printf(e) 61 | http.Error(w, e, http.StatusInternalServerError) 62 | return 63 | } 64 | 65 | resp_body, code, resp_err := g.EndpointReq() 66 | 67 | if resp_err != nil { 68 | e := fmt.Sprintf("get_item_route.GetItemHandler:err %s", 69 | resp_err.Error()) 70 | log.Printf(e) 71 | http.Error(w, e, http.StatusInternalServerError) 72 | return 73 | } 74 | 75 | if ep.HttpErr(code) { 76 | route_response.WriteError(w, code, "get_item_route.GetItemHandler", resp_body) 77 | return 78 | } 79 | 80 | mr_err := route_response.MakeRouteResponse( 81 | w, 82 | req, 83 | resp_body, 84 | code, 85 | start, 86 | get.ENDPOINT_NAME) 87 | if mr_err != nil { 88 | e := fmt.Sprintf("get_item_route.GetItemHandler %s", 89 | mr_err.Error()) 90 | log.Printf(e) 91 | http.Error(w, e, http.StatusInternalServerError) 92 | return 93 | } 94 | } 95 | 96 | // BBPD-only endpoint. 97 | // GetItemJSONHandler issues a GetItem request to aws and then transforms the Response into 98 | // a ResponseItemJSON. 99 | func GetItemJSONHandler(w http.ResponseWriter, req *http.Request) { 100 | if bbpd_runinfo.BBPDAbortIfClosed(w) { 101 | return 102 | } 103 | start := time.Now() 104 | bodybytes, read_err := ioutil.ReadAll(req.Body) 105 | req.Body.Close() 106 | if read_err != nil && read_err != io.EOF { 107 | e := fmt.Sprintf("get_item_route.GetItemJSONHandler err reading req body: %s", read_err.Error()) 108 | log.Printf(e) 109 | http.Error(w, e, http.StatusInternalServerError) 110 | return 111 | } 112 | 113 | resp_body, code, resp_err := authreq.RetryReqJSON_V4(bodybytes, get.GETITEM_ENDPOINT) 114 | 115 | if resp_err != nil { 116 | e := fmt.Sprintf("get_item_route.GetItemJSONHandler: resp err calling %s err %s (input json: %s)", 117 | get.GETITEM_ENDPOINT, resp_err.Error(), string(bodybytes)) 118 | log.Printf(e) 119 | http.Error(w, e, http.StatusInternalServerError) 120 | return 121 | } 122 | 123 | if ep.HttpErr(code) { 124 | e := fmt.Sprintf("get_item_route.GetItemJSONHandler: http err %d calling %s (input json: %s)", 125 | code, get.GETITEM_ENDPOINT, string(bodybytes)) 126 | route_response.WriteError(w, code, e, resp_body) 127 | return 128 | } 129 | 130 | // translate the Response to a ResponseItemJSON 131 | resp := get.NewResponse() 132 | um_err := json.Unmarshal([]byte(resp_body), resp) 133 | if um_err != nil { 134 | e := fmt.Sprintf("get_item_route.GetItemJSONHandler:err %s", 135 | um_err.Error()) 136 | log.Printf(e) 137 | http.Error(w, e, http.StatusInternalServerError) 138 | return 139 | } 140 | resp_json, rerr := resp.ToResponseItemJSON() 141 | if rerr != nil { 142 | e := fmt.Sprintf("get_item_route.GetItemJSONHandler:err %s", 143 | rerr.Error()) 144 | log.Printf(e) 145 | http.Error(w, e, http.StatusInternalServerError) 146 | return 147 | } 148 | json_body, jerr := json.Marshal(resp_json) 149 | if jerr != nil { 150 | e := fmt.Sprintf("get_item_route.GetItemJSONHandler:err %s", 151 | jerr.Error()) 152 | log.Printf(e) 153 | http.Error(w, e, http.StatusInternalServerError) 154 | return 155 | } 156 | mr_err := route_response.MakeRouteResponse( 157 | w, 158 | req, 159 | json_body, 160 | http.StatusOK, 161 | start, 162 | get.ENDPOINT_NAME) 163 | if mr_err != nil { 164 | e := fmt.Sprintf("get_item_route.GetItemJSONHandler %s", 165 | mr_err.Error()) 166 | log.Printf(e) 167 | http.Error(w, e, http.StatusInternalServerError) 168 | return 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /lib/put_item_route/put_item_route.go: -------------------------------------------------------------------------------- 1 | // Supports proxying the PutItem endpoint. 2 | package put_item_route 3 | 4 | import ( 5 | "encoding/json" 6 | "fmt" 7 | "github.com/smugmug/bbpd/lib/bbpd_runinfo" 8 | raw "github.com/smugmug/bbpd/lib/raw_post_route" 9 | "github.com/smugmug/bbpd/lib/route_response" 10 | ep "github.com/smugmug/godynamo/endpoint" 11 | put "github.com/smugmug/godynamo/endpoints/put_item" 12 | "io" 13 | "io/ioutil" 14 | "log" 15 | "net/http" 16 | "strings" 17 | "time" 18 | ) 19 | 20 | // RawPostHandler relays the PutItem request to Dynamo directly. 21 | func RawPostHandler(w http.ResponseWriter, req *http.Request) { 22 | raw.RawPostReq(w, req, put.PUTITEM_ENDPOINT) 23 | } 24 | 25 | // PutItemHandler relays the PutItem request to Dynamo but first validates it through a local type. 26 | func PutItemHandler(w http.ResponseWriter, req *http.Request) { 27 | if bbpd_runinfo.BBPDAbortIfClosed(w) { 28 | return 29 | } 30 | start := time.Now() 31 | if req.Method != "POST" { 32 | e := "put_item_route.PutItemHandler:method only supports POST" 33 | log.Printf(e) 34 | http.Error(w, e, http.StatusBadRequest) 35 | return 36 | } 37 | pathElts := strings.Split(req.URL.Path, "/") 38 | if len(pathElts) != 2 { 39 | e := "put_item_route.PutItemHandler:cannot parse path. try /put-item, call as POST" 40 | log.Printf(e) 41 | http.Error(w, e, http.StatusBadRequest) 42 | return 43 | } 44 | 45 | bodybytes, read_err := ioutil.ReadAll(req.Body) 46 | req.Body.Close() 47 | if read_err != nil && read_err != io.EOF { 48 | e := fmt.Sprintf("put_item_route.PutItemHandler err reading req body: %s", read_err.Error()) 49 | log.Printf(e) 50 | http.Error(w, e, http.StatusInternalServerError) 51 | return 52 | } 53 | 54 | p := put.NewPutItem() 55 | um_err := json.Unmarshal(bodybytes, p) 56 | 57 | if um_err != nil { 58 | e := fmt.Sprintf("put_item_route.PutItemHandler unmarshal err on %s to PutExpected: %s", string(bodybytes), um_err.Error()) 59 | log.Printf(e) 60 | http.Error(w, e, http.StatusInternalServerError) 61 | return 62 | } 63 | 64 | resp_body, code, resp_err := p.EndpointReq() 65 | 66 | if resp_err != nil { 67 | e := fmt.Sprintf("put_item_route.PutItemHandler:err %s", 68 | resp_err.Error()) 69 | log.Printf(e) 70 | http.Error(w, e, http.StatusInternalServerError) 71 | return 72 | } 73 | 74 | if ep.HttpErr(code) { 75 | route_response.WriteError(w, code, "put_item_route.PutItemHandler", resp_body) 76 | return 77 | } 78 | 79 | mr_err := route_response.MakeRouteResponse( 80 | w, 81 | req, 82 | resp_body, 83 | code, 84 | start, 85 | put.ENDPOINT_NAME) 86 | if mr_err != nil { 87 | e := fmt.Sprintf("put_item_route.PutItemHandler %s", mr_err.Error()) 88 | log.Printf(e) 89 | } 90 | } 91 | 92 | // BBPD-only endpoint. 93 | // PutItemJSONHandler relays the PutItem request to Dynamo but first validates it through a local type. 94 | // This variant allows the Item to be encoded as basic JSON. As there is always a conversion that 95 | // needs to be performed from a PutIemJSON struct to a PutItem, this endpoint cannot utilize 96 | // RawPost. 97 | func PutItemJSONHandler(w http.ResponseWriter, req *http.Request) { 98 | if bbpd_runinfo.BBPDAbortIfClosed(w) { 99 | return 100 | } 101 | start := time.Now() 102 | if req.Method != "POST" { 103 | e := "put_item_route.PutItemJSONHandler:method only supports POST" 104 | log.Printf(e) 105 | http.Error(w, e, http.StatusBadRequest) 106 | return 107 | } 108 | pathElts := strings.Split(req.URL.Path, "/") 109 | if len(pathElts) != 2 { 110 | e := "put_item_route.PutItemJSONHandler:cannot parse path. try /put-item, call as POST" 111 | log.Printf(e) 112 | http.Error(w, e, http.StatusBadRequest) 113 | return 114 | } 115 | 116 | bodybytes, read_err := ioutil.ReadAll(req.Body) 117 | req.Body.Close() 118 | if read_err != nil && read_err != io.EOF { 119 | e := fmt.Sprintf("put_item_route.PutItemJSONHandler err reading req body: %s", read_err.Error()) 120 | log.Printf(e) 121 | http.Error(w, e, http.StatusInternalServerError) 122 | return 123 | } 124 | 125 | p_json := put.NewPutItemJSON() 126 | um_err := json.Unmarshal(bodybytes, p_json) 127 | 128 | if um_err != nil { 129 | e := fmt.Sprintf("put_item_route.PutItemJSONHandler unmarshal err on %s to PutExpected: %s", string(bodybytes), um_err.Error()) 130 | log.Printf(e) 131 | http.Error(w, e, http.StatusInternalServerError) 132 | return 133 | } 134 | 135 | p, perr := p_json.ToPutItem() 136 | if perr != nil { 137 | e := fmt.Sprintf("put_item_route.PutItemJSONHandler cannot convert PutItemJSON to PutItem:%s", perr.Error()) 138 | log.Printf(e) 139 | http.Error(w, e, http.StatusInternalServerError) 140 | return 141 | } 142 | 143 | resp_body, code, resp_err := p.EndpointReq() 144 | 145 | if resp_err != nil { 146 | e := fmt.Sprintf("put_item_route.PutItemJSONHandler:err %s", 147 | resp_err.Error()) 148 | log.Printf(e) 149 | http.Error(w, e, http.StatusInternalServerError) 150 | return 151 | } 152 | 153 | if ep.HttpErr(code) { 154 | route_response.WriteError(w, code, "put_item_route.PutItemJSONHandler", resp_body) 155 | return 156 | } 157 | 158 | mr_err := route_response.MakeRouteResponse( 159 | w, 160 | req, 161 | resp_body, 162 | code, 163 | start, 164 | put.ENDPOINT_NAME) 165 | if mr_err != nil { 166 | e := fmt.Sprintf("put_item_route.PutItemJSONHandler %s", mr_err.Error()) 167 | log.Printf(e) 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /lib/list_tables_route/list_tables_route.go: -------------------------------------------------------------------------------- 1 | // Supports proxying the ListTables endpoint. 2 | package list_tables_route 3 | 4 | import ( 5 | "encoding/json" 6 | "fmt" 7 | "github.com/smugmug/bbpd/lib/bbpd_runinfo" 8 | raw "github.com/smugmug/bbpd/lib/raw_post_route" 9 | "github.com/smugmug/bbpd/lib/route_response" 10 | ep "github.com/smugmug/godynamo/endpoint" 11 | list "github.com/smugmug/godynamo/endpoints/list_tables" 12 | "io" 13 | "io/ioutil" 14 | "log" 15 | "net/http" 16 | "strconv" 17 | "strings" 18 | "time" 19 | ) 20 | 21 | const ( 22 | DEFAULT_LIMIT = 99 23 | ) 24 | 25 | // RawPostHandler relays the ListTables request to Dynamo directly. 26 | func RawPostHandler(w http.ResponseWriter, req *http.Request) { 27 | raw.RawPostReq(w, req, list.LISTTABLE_ENDPOINT) 28 | } 29 | 30 | // ListTablesHandler relays the ListTables request to Dynamo but first validates it through a local type. 31 | func ListTablesHandler(w http.ResponseWriter, req *http.Request) { 32 | if bbpd_runinfo.BBPDAbortIfClosed(w) { 33 | return 34 | } 35 | if req.Method == "GET" { 36 | listTables_GET_Handler(w, req) 37 | } else if req.Method == "POST" { 38 | listTables_POST_Handler(w, req) 39 | } else { 40 | e := fmt.Sprintf("list_tables_route.ListTablesHandler:bad method %s", req.Method) 41 | log.Printf(e) 42 | http.Error(w, e, http.StatusInternalServerError) 43 | } 44 | } 45 | 46 | // Executes ListTables assuming it were requested with the POST method. 47 | func listTables_POST_Handler(w http.ResponseWriter, req *http.Request) { 48 | start := time.Now() 49 | pathElts := strings.Split(req.URL.Path, "/") 50 | if len(pathElts) != 2 { 51 | e := "list_tables_route.listTables_POST_Handler:cannot parse path. try /batch-get-item" 52 | log.Printf(e) 53 | http.Error(w, e, http.StatusBadRequest) 54 | return 55 | } 56 | 57 | bodybytes, read_err := ioutil.ReadAll(req.Body) 58 | req.Body.Close() 59 | if read_err != nil && read_err != io.EOF { 60 | e := fmt.Sprintf("list_tables_route.listTables_POST_Handler err reading req body: %s", read_err.Error()) 61 | log.Printf(e) 62 | http.Error(w, e, http.StatusInternalServerError) 63 | return 64 | } 65 | 66 | var l list.List 67 | 68 | um_err := json.Unmarshal(bodybytes, &l) 69 | if um_err != nil { 70 | e := fmt.Sprintf("list_tables_route.listTables_POST_Handler unmarshal err on %s to Get %s", string(bodybytes), um_err.Error()) 71 | log.Printf(e) 72 | http.Error(w, e, http.StatusInternalServerError) 73 | return 74 | } 75 | 76 | resp_body, code, resp_err := l.EndpointReq() 77 | 78 | if resp_err != nil { 79 | e := fmt.Sprintf("list_table_route.ListTable_POST_Handler:err %s", 80 | resp_err.Error()) 81 | log.Printf(e) 82 | http.Error(w, e, http.StatusInternalServerError) 83 | return 84 | } 85 | 86 | if ep.HttpErr(code) { 87 | route_response.WriteError(w, code, "list_table_route.ListTable_POST_Handler", resp_body) 88 | return 89 | } 90 | 91 | mr_err := route_response.MakeRouteResponse( 92 | w, 93 | req, 94 | resp_body, 95 | code, 96 | start, 97 | list.ENDPOINT_NAME) 98 | if mr_err != nil { 99 | e := fmt.Sprintf("list_tables_route.listTables_POST_Handler %s", 100 | mr_err.Error()) 101 | log.Printf(e) 102 | http.Error(w, e, http.StatusInternalServerError) 103 | return 104 | } 105 | } 106 | 107 | // Executes ListTables assuming it were requested with the GET method. 108 | func listTables_GET_Handler(w http.ResponseWriter, req *http.Request) { 109 | start := time.Now() 110 | pathElts := strings.Split(req.URL.Path, "/") 111 | if len(pathElts) != 2 { 112 | e := "list_table_route.ListTablesHandler:cannot parse path." + 113 | "try /list?ExclusiveStartTableName=$T&Limit=$L" 114 | log.Printf(e) 115 | http.Error(w, e, http.StatusBadRequest) 116 | return 117 | } 118 | queryMap := make(map[string]string) 119 | for k, v := range req.URL.Query() { 120 | queryMap[strings.ToLower(k)] = v[0] 121 | } 122 | 123 | q_estn, estn_exists := queryMap[strings.ToLower(list.EXCLUSIVE_START_TABLE_NAME)] 124 | estn := "" 125 | if estn_exists { 126 | estn = q_estn 127 | } 128 | q_limit, limit_exists := queryMap[strings.ToLower(list.LIMIT)] 129 | limit := uint64(0) 130 | if limit_exists { 131 | limit_conv, conv_err := strconv.ParseUint(q_limit, 10, 64) 132 | if conv_err != nil { 133 | e := fmt.Sprintf("list_table_route.listTables_GET_Handler bad limit %s", q_limit) 134 | log.Printf(e) 135 | } else { 136 | limit = limit_conv 137 | if limit > DEFAULT_LIMIT { 138 | e := fmt.Sprintf("list_table_route.listTables_GET_Handler: high limit %d", limit_conv) 139 | log.Printf(e) 140 | limit = DEFAULT_LIMIT 141 | } 142 | } 143 | } 144 | 145 | l := list.List{ 146 | Limit: limit, 147 | ExclusiveStartTableName: estn} 148 | 149 | resp_body, code, resp_err := l.EndpointReq() 150 | 151 | if resp_err != nil { 152 | e := fmt.Sprintf("list_table_route.ListTable_GET_Handler:err %s", 153 | resp_err.Error()) 154 | log.Printf(e) 155 | http.Error(w, e, http.StatusInternalServerError) 156 | return 157 | } 158 | 159 | if ep.HttpErr(code) { 160 | route_response.WriteError(w, code, "list_table_route.ListTable_GET_Handler", resp_body) 161 | return 162 | } 163 | 164 | mr_err := route_response.MakeRouteResponse( 165 | w, 166 | req, 167 | resp_body, 168 | code, 169 | start, 170 | list.ENDPOINT_NAME) 171 | if mr_err != nil { 172 | e := fmt.Sprintf("list_table_route.listTable_GET_Handler %s", mr_err.Error()) 173 | log.Printf(e) 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /tests/batch_get_item-livetest1.sh: -------------------------------------------------------------------------------- 1 | curl -H "X-Amz-Target: DynamoDB_20120810.BatchGetItem" -X POST -d '{"RequestItems":{"test-godynamo-livetest":{"AttributesToGet":null,"ConsistentRead":false,"Keys":[{"TheHashKey":{"S":"AHashKey1"},"TheRangeKey":{"N":"1"}},{"TheHashKey":{"S":"AHashKey2"},"TheRangeKey":{"N":"2"}},{"TheHashKey":{"S":"AHashKey3"},"TheRangeKey":{"N":"3"}},{"TheHashKey":{"S":"AHashKey4"},"TheRangeKey":{"N":"4"}},{"TheHashKey":{"S":"AHashKey5"},"TheRangeKey":{"N":"5"}},{"TheHashKey":{"S":"AHashKey6"},"TheRangeKey":{"N":"6"}},{"TheHashKey":{"S":"AHashKey7"},"TheRangeKey":{"N":"7"}},{"TheHashKey":{"S":"AHashKey8"},"TheRangeKey":{"N":"8"}},{"TheHashKey":{"S":"AHashKey9"},"TheRangeKey":{"N":"9"}},{"TheHashKey":{"S":"AHashKey10"},"TheRangeKey":{"N":"10"}},{"TheHashKey":{"S":"AHashKey11"},"TheRangeKey":{"N":"11"}},{"TheHashKey":{"S":"AHashKey12"},"TheRangeKey":{"N":"12"}},{"TheHashKey":{"S":"AHashKey13"},"TheRangeKey":{"N":"13"}},{"TheHashKey":{"S":"AHashKey14"},"TheRangeKey":{"N":"14"}},{"TheHashKey":{"S":"AHashKey15"},"TheRangeKey":{"N":"15"}},{"TheHashKey":{"S":"AHashKey16"},"TheRangeKey":{"N":"16"}},{"TheHashKey":{"S":"AHashKey17"},"TheRangeKey":{"N":"17"}},{"TheHashKey":{"S":"AHashKey18"},"TheRangeKey":{"N":"18"}},{"TheHashKey":{"S":"AHashKey19"},"TheRangeKey":{"N":"19"}},{"TheHashKey":{"S":"AHashKey20"},"TheRangeKey":{"N":"20"}},{"TheHashKey":{"S":"AHashKey21"},"TheRangeKey":{"N":"21"}},{"TheHashKey":{"S":"AHashKey22"},"TheRangeKey":{"N":"22"}},{"TheHashKey":{"S":"AHashKey23"},"TheRangeKey":{"N":"23"}},{"TheHashKey":{"S":"AHashKey24"},"TheRangeKey":{"N":"24"}},{"TheHashKey":{"S":"AHashKey25"},"TheRangeKey":{"N":"25"}},{"TheHashKey":{"S":"AHashKey26"},"TheRangeKey":{"N":"26"}},{"TheHashKey":{"S":"AHashKey27"},"TheRangeKey":{"N":"27"}},{"TheHashKey":{"S":"AHashKey28"},"TheRangeKey":{"N":"28"}},{"TheHashKey":{"S":"AHashKey29"},"TheRangeKey":{"N":"29"}},{"TheHashKey":{"S":"AHashKey30"},"TheRangeKey":{"N":"30"}},{"TheHashKey":{"S":"AHashKey31"},"TheRangeKey":{"N":"31"}},{"TheHashKey":{"S":"AHashKey32"},"TheRangeKey":{"N":"32"}},{"TheHashKey":{"S":"AHashKey33"},"TheRangeKey":{"N":"33"}},{"TheHashKey":{"S":"AHashKey34"},"TheRangeKey":{"N":"34"}},{"TheHashKey":{"S":"AHashKey35"},"TheRangeKey":{"N":"35"}},{"TheHashKey":{"S":"AHashKey36"},"TheRangeKey":{"N":"36"}},{"TheHashKey":{"S":"AHashKey37"},"TheRangeKey":{"N":"37"}},{"TheHashKey":{"S":"AHashKey38"},"TheRangeKey":{"N":"38"}},{"TheHashKey":{"S":"AHashKey39"},"TheRangeKey":{"N":"39"}},{"TheHashKey":{"S":"AHashKey40"},"TheRangeKey":{"N":"40"}},{"TheHashKey":{"S":"AHashKey41"},"TheRangeKey":{"N":"41"}},{"TheHashKey":{"S":"AHashKey42"},"TheRangeKey":{"N":"42"}},{"TheHashKey":{"S":"AHashKey43"},"TheRangeKey":{"N":"43"}},{"TheHashKey":{"S":"AHashKey44"},"TheRangeKey":{"N":"44"}},{"TheHashKey":{"S":"AHashKey45"},"TheRangeKey":{"N":"45"}},{"TheHashKey":{"S":"AHashKey46"},"TheRangeKey":{"N":"46"}},{"TheHashKey":{"S":"AHashKey47"},"TheRangeKey":{"N":"47"}},{"TheHashKey":{"S":"AHashKey48"},"TheRangeKey":{"N":"48"}},{"TheHashKey":{"S":"AHashKey49"},"TheRangeKey":{"N":"49"}},{"TheHashKey":{"S":"AHashKey50"},"TheRangeKey":{"N":"50"}},{"TheHashKey":{"S":"AHashKey51"},"TheRangeKey":{"N":"51"}},{"TheHashKey":{"S":"AHashKey52"},"TheRangeKey":{"N":"52"}},{"TheHashKey":{"S":"AHashKey53"},"TheRangeKey":{"N":"53"}},{"TheHashKey":{"S":"AHashKey54"},"TheRangeKey":{"N":"54"}},{"TheHashKey":{"S":"AHashKey55"},"TheRangeKey":{"N":"55"}},{"TheHashKey":{"S":"AHashKey56"},"TheRangeKey":{"N":"56"}},{"TheHashKey":{"S":"AHashKey57"},"TheRangeKey":{"N":"57"}},{"TheHashKey":{"S":"AHashKey58"},"TheRangeKey":{"N":"58"}},{"TheHashKey":{"S":"AHashKey59"},"TheRangeKey":{"N":"59"}},{"TheHashKey":{"S":"AHashKey60"},"TheRangeKey":{"N":"60"}},{"TheHashKey":{"S":"AHashKey61"},"TheRangeKey":{"N":"61"}},{"TheHashKey":{"S":"AHashKey62"},"TheRangeKey":{"N":"62"}},{"TheHashKey":{"S":"AHashKey63"},"TheRangeKey":{"N":"63"}},{"TheHashKey":{"S":"AHashKey64"},"TheRangeKey":{"N":"64"}},{"TheHashKey":{"S":"AHashKey65"},"TheRangeKey":{"N":"65"}},{"TheHashKey":{"S":"AHashKey66"},"TheRangeKey":{"N":"66"}},{"TheHashKey":{"S":"AHashKey67"},"TheRangeKey":{"N":"67"}},{"TheHashKey":{"S":"AHashKey68"},"TheRangeKey":{"N":"68"}},{"TheHashKey":{"S":"AHashKey69"},"TheRangeKey":{"N":"69"}},{"TheHashKey":{"S":"AHashKey70"},"TheRangeKey":{"N":"70"}},{"TheHashKey":{"S":"AHashKey71"},"TheRangeKey":{"N":"71"}},{"TheHashKey":{"S":"AHashKey72"},"TheRangeKey":{"N":"72"}},{"TheHashKey":{"S":"AHashKey73"},"TheRangeKey":{"N":"73"}},{"TheHashKey":{"S":"AHashKey74"},"TheRangeKey":{"N":"74"}},{"TheHashKey":{"S":"AHashKey75"},"TheRangeKey":{"N":"75"}},{"TheHashKey":{"S":"AHashKey76"},"TheRangeKey":{"N":"76"}},{"TheHashKey":{"S":"AHashKey77"},"TheRangeKey":{"N":"77"}},{"TheHashKey":{"S":"AHashKey78"},"TheRangeKey":{"N":"78"}},{"TheHashKey":{"S":"AHashKey79"},"TheRangeKey":{"N":"79"}},{"TheHashKey":{"S":"AHashKey80"},"TheRangeKey":{"N":"80"}},{"TheHashKey":{"S":"AHashKey81"},"TheRangeKey":{"N":"81"}},{"TheHashKey":{"S":"AHashKey82"},"TheRangeKey":{"N":"82"}},{"TheHashKey":{"S":"AHashKey83"},"TheRangeKey":{"N":"83"}},{"TheHashKey":{"S":"AHashKey84"},"TheRangeKey":{"N":"84"}},{"TheHashKey":{"S":"AHashKey85"},"TheRangeKey":{"N":"85"}},{"TheHashKey":{"S":"AHashKey86"},"TheRangeKey":{"N":"86"}},{"TheHashKey":{"S":"AHashKey87"},"TheRangeKey":{"N":"87"}},{"TheHashKey":{"S":"AHashKey88"},"TheRangeKey":{"N":"88"}},{"TheHashKey":{"S":"AHashKey89"},"TheRangeKey":{"N":"89"}},{"TheHashKey":{"S":"AHashKey90"},"TheRangeKey":{"N":"90"}},{"TheHashKey":{"S":"AHashKey91"},"TheRangeKey":{"N":"91"}},{"TheHashKey":{"S":"AHashKey92"},"TheRangeKey":{"N":"92"}},{"TheHashKey":{"S":"AHashKey93"},"TheRangeKey":{"N":"93"}},{"TheHashKey":{"S":"AHashKey94"},"TheRangeKey":{"N":"94"}},{"TheHashKey":{"S":"AHashKey95"},"TheRangeKey":{"N":"95"}},{"TheHashKey":{"S":"AHashKey96"},"TheRangeKey":{"N":"96"}},{"TheHashKey":{"S":"AHashKey97"},"TheRangeKey":{"N":"97"}},{"TheHashKey":{"S":"AHashKey98"},"TheRangeKey":{"N":"98"}},{"TheHashKey":{"S":"AHashKey99"},"TheRangeKey":{"N":"99"}},{"TheHashKey":{"S":"AHashKey100"},"TheRangeKey":{"N":"100"}}]}},"ReturnConsumedCapacity":"NONE"}' http://localhost:12333 2 | -------------------------------------------------------------------------------- /lib/batch_write_item_route/batch_write_item_route.go: -------------------------------------------------------------------------------- 1 | // Supports proxying the BatchWriteItem endpoint. 2 | package batch_write_item_route 3 | 4 | import ( 5 | "encoding/json" 6 | "fmt" 7 | "github.com/smugmug/bbpd/lib/bbpd_runinfo" 8 | "github.com/smugmug/bbpd/lib/route_response" 9 | ep "github.com/smugmug/godynamo/endpoint" 10 | bwi "github.com/smugmug/godynamo/endpoints/batch_write_item" 11 | "io" 12 | "io/ioutil" 13 | "log" 14 | "net/http" 15 | "strings" 16 | "time" 17 | ) 18 | 19 | // BatchWriteItemHandler accepts arbitrarily-sized BatchWriteItem requests and relays them to Dynamo. 20 | func BatchWriteItemHandler(w http.ResponseWriter, req *http.Request) { 21 | if bbpd_runinfo.BBPDAbortIfClosed(w) { 22 | return 23 | } 24 | start := time.Now() 25 | if req.Method != "POST" { 26 | e := "batch_write_item_route.BatchWriteItemHandler:method only supports POST" 27 | log.Printf(e) 28 | http.Error(w, e, http.StatusBadRequest) 29 | return 30 | } 31 | pathElts := strings.Split(req.URL.Path, "/") 32 | if len(pathElts) != 2 { 33 | e := "batch_write_item_route.BatchWriteItemHandler:cannot parse path. try /batch-get-item" 34 | log.Printf(e) 35 | http.Error(w, e, http.StatusBadRequest) 36 | return 37 | } 38 | 39 | bodybytes, read_err := ioutil.ReadAll(req.Body) 40 | if read_err != nil && read_err != io.EOF { 41 | e := fmt.Sprintf("batch_write_item_route.BatchWriteItemHandler err reading req body: %s", read_err.Error()) 42 | log.Printf(e) 43 | http.Error(w, e, http.StatusInternalServerError) 44 | return 45 | } 46 | req.Body.Close() 47 | 48 | if len(bodybytes) > bwi.QUERY_LIM_BYTES { 49 | e := fmt.Sprintf("batch_write_item_route.BatchWriteItemHandler - payload over 1024kb, may be rejected by aws! splitting into segmented requests will likely mean each segment is accepted") 50 | log.Printf(e) 51 | } 52 | 53 | b := bwi.NewBatchWriteItem() 54 | 55 | um_err := json.Unmarshal(bodybytes, b) 56 | if um_err != nil { 57 | e := fmt.Sprintf("batch_write_item_route.BatchWriteItemHandler unmarshal err on %s to BatchWriteItem %s", string(bodybytes), um_err.Error()) 58 | log.Printf(e) 59 | http.Error(w, e, http.StatusInternalServerError) 60 | return 61 | } 62 | 63 | resp_body, code, resp_err := b.DoBatchWrite() 64 | 65 | if resp_err != nil { 66 | e := fmt.Sprintf("batch_write_item_route.BatchWriteItemHandler:err %s", 67 | resp_err.Error()) 68 | log.Printf(e) 69 | http.Error(w, e, http.StatusInternalServerError) 70 | return 71 | } 72 | 73 | if ep.HttpErr(code) { 74 | route_response.WriteError(w, code, "batch_write_item_route.BatchWriteItemHandler", resp_body) 75 | return 76 | } 77 | 78 | mr_err := route_response.MakeRouteResponse( 79 | w, 80 | req, 81 | resp_body, 82 | code, 83 | start, 84 | bwi.ENDPOINT_NAME) 85 | if mr_err != nil { 86 | e := fmt.Sprintf("batch_write_item_route.BatchWriteItemHandler %s", mr_err.Error()) 87 | log.Printf(e) 88 | } 89 | } 90 | 91 | // BBPD-only endpoint. 92 | // BatchWriteItemJSONHandler relays the BatchWriteItem request to Dynamo but first 93 | // validates it through a local type. 94 | // This variant allows the Items to be encoded as basic JSON. As there is always a conversion that 95 | // needs to be performed from a PutIemJSON struct to a BatchWriteItem, this endpoint cannot utilize 96 | // RawPost. 97 | func BatchWriteItemJSONHandler(w http.ResponseWriter, req *http.Request) { 98 | if bbpd_runinfo.BBPDAbortIfClosed(w) { 99 | return 100 | } 101 | start := time.Now() 102 | if req.Method != "POST" { 103 | e := "batch_write_item_route.BatchWriteItemJSONHandler:method only supports POST" 104 | log.Printf(e) 105 | http.Error(w, e, http.StatusBadRequest) 106 | return 107 | } 108 | pathElts := strings.Split(req.URL.Path, "/") 109 | if len(pathElts) != 2 { 110 | e := "batch_write_item_route.BatchWriteItemJSONHandler:cannot parse path. try /batch-get-item" 111 | log.Printf(e) 112 | http.Error(w, e, http.StatusBadRequest) 113 | return 114 | } 115 | 116 | bodybytes, read_err := ioutil.ReadAll(req.Body) 117 | if read_err != nil && read_err != io.EOF { 118 | e := fmt.Sprintf("batch_write_item_route.BatchWriteItemJSONHandler err reading req body: %s", read_err.Error()) 119 | log.Printf(e) 120 | http.Error(w, e, http.StatusInternalServerError) 121 | return 122 | } 123 | req.Body.Close() 124 | 125 | if len(bodybytes) > bwi.QUERY_LIM_BYTES { 126 | e := fmt.Sprintf("batch_write_item_route.BatchWriteItemJSONHandler - payload over 1024kb, may be rejected by aws! splitting into segmented requests will likely mean each segment is accepted") 127 | log.Printf(e) 128 | } 129 | 130 | b_json := bwi.NewBatchWriteItemJSON() 131 | 132 | um_err := json.Unmarshal(bodybytes, b_json) 133 | if um_err != nil { 134 | e := fmt.Sprintf("batch_write_item_route.BatchWriteItemJSONHandler unmarshal err on %s to BatchWriteItem %s", string(bodybytes), um_err.Error()) 135 | log.Printf(e) 136 | http.Error(w, e, http.StatusInternalServerError) 137 | return 138 | } 139 | 140 | b, berr := b_json.ToBatchWriteItem() 141 | if berr != nil { 142 | e := fmt.Sprintf("batch_write_item_route.BatchWriteItemJSONHandler cannot convert BatchWriteItemJSON to BatchWriteItem:%s", berr.Error()) 143 | log.Printf(e) 144 | http.Error(w, e, http.StatusInternalServerError) 145 | return 146 | } 147 | 148 | resp_body, code, resp_err := b.DoBatchWrite() 149 | 150 | if resp_err != nil { 151 | e := fmt.Sprintf("batch_write_item_route.BatchWriteItemJSONHandler:err %s", 152 | resp_err.Error()) 153 | log.Printf(e) 154 | http.Error(w, e, http.StatusInternalServerError) 155 | return 156 | } 157 | 158 | if ep.HttpErr(code) { 159 | route_response.WriteError(w, code, "batch_write_item_route.BatchWriteItemJSONHandler", resp_body) 160 | return 161 | } 162 | 163 | mr_err := route_response.MakeRouteResponse( 164 | w, 165 | req, 166 | resp_body, 167 | code, 168 | start, 169 | bwi.ENDPOINT_NAME) 170 | if mr_err != nil { 171 | e := fmt.Sprintf("batch_write_item_route.BatchWriteItemJSONHandler %s", mr_err.Error()) 172 | log.Printf(e) 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /tests/batch_get_item-livetest2.sh: -------------------------------------------------------------------------------- 1 | curl -H "X-Amz-Target: DynamoDB_20120810.BatchGetItem" -X POST -d '{"RequestItems":{"test-godynamo-livetest":{"AttributesToGet":null,"ConsistentRead":false,"Keys":[{"TheHashKey":{"S":"AHashKey101"},"TheRangeKey":{"N":"101"}},{"TheHashKey":{"S":"AHashKey102"},"TheRangeKey":{"N":"102"}},{"TheHashKey":{"S":"AHashKey103"},"TheRangeKey":{"N":"103"}},{"TheHashKey":{"S":"AHashKey104"},"TheRangeKey":{"N":"104"}},{"TheHashKey":{"S":"AHashKey105"},"TheRangeKey":{"N":"105"}},{"TheHashKey":{"S":"AHashKey106"},"TheRangeKey":{"N":"106"}},{"TheHashKey":{"S":"AHashKey107"},"TheRangeKey":{"N":"107"}},{"TheHashKey":{"S":"AHashKey108"},"TheRangeKey":{"N":"108"}},{"TheHashKey":{"S":"AHashKey109"},"TheRangeKey":{"N":"109"}},{"TheHashKey":{"S":"AHashKey110"},"TheRangeKey":{"N":"110"}},{"TheHashKey":{"S":"AHashKey111"},"TheRangeKey":{"N":"111"}},{"TheHashKey":{"S":"AHashKey112"},"TheRangeKey":{"N":"112"}},{"TheHashKey":{"S":"AHashKey113"},"TheRangeKey":{"N":"113"}},{"TheHashKey":{"S":"AHashKey114"},"TheRangeKey":{"N":"114"}},{"TheHashKey":{"S":"AHashKey115"},"TheRangeKey":{"N":"115"}},{"TheHashKey":{"S":"AHashKey116"},"TheRangeKey":{"N":"116"}},{"TheHashKey":{"S":"AHashKey117"},"TheRangeKey":{"N":"117"}},{"TheHashKey":{"S":"AHashKey118"},"TheRangeKey":{"N":"118"}},{"TheHashKey":{"S":"AHashKey119"},"TheRangeKey":{"N":"119"}},{"TheHashKey":{"S":"AHashKey120"},"TheRangeKey":{"N":"120"}},{"TheHashKey":{"S":"AHashKey121"},"TheRangeKey":{"N":"121"}},{"TheHashKey":{"S":"AHashKey122"},"TheRangeKey":{"N":"122"}},{"TheHashKey":{"S":"AHashKey123"},"TheRangeKey":{"N":"123"}},{"TheHashKey":{"S":"AHashKey124"},"TheRangeKey":{"N":"124"}},{"TheHashKey":{"S":"AHashKey125"},"TheRangeKey":{"N":"125"}},{"TheHashKey":{"S":"AHashKey126"},"TheRangeKey":{"N":"126"}},{"TheHashKey":{"S":"AHashKey127"},"TheRangeKey":{"N":"127"}},{"TheHashKey":{"S":"AHashKey128"},"TheRangeKey":{"N":"128"}},{"TheHashKey":{"S":"AHashKey129"},"TheRangeKey":{"N":"129"}},{"TheHashKey":{"S":"AHashKey130"},"TheRangeKey":{"N":"130"}},{"TheHashKey":{"S":"AHashKey131"},"TheRangeKey":{"N":"131"}},{"TheHashKey":{"S":"AHashKey132"},"TheRangeKey":{"N":"132"}},{"TheHashKey":{"S":"AHashKey133"},"TheRangeKey":{"N":"133"}},{"TheHashKey":{"S":"AHashKey134"},"TheRangeKey":{"N":"134"}},{"TheHashKey":{"S":"AHashKey135"},"TheRangeKey":{"N":"135"}},{"TheHashKey":{"S":"AHashKey136"},"TheRangeKey":{"N":"136"}},{"TheHashKey":{"S":"AHashKey137"},"TheRangeKey":{"N":"137"}},{"TheHashKey":{"S":"AHashKey138"},"TheRangeKey":{"N":"138"}},{"TheHashKey":{"S":"AHashKey139"},"TheRangeKey":{"N":"139"}},{"TheHashKey":{"S":"AHashKey140"},"TheRangeKey":{"N":"140"}},{"TheHashKey":{"S":"AHashKey141"},"TheRangeKey":{"N":"141"}},{"TheHashKey":{"S":"AHashKey142"},"TheRangeKey":{"N":"142"}},{"TheHashKey":{"S":"AHashKey143"},"TheRangeKey":{"N":"143"}},{"TheHashKey":{"S":"AHashKey144"},"TheRangeKey":{"N":"144"}},{"TheHashKey":{"S":"AHashKey145"},"TheRangeKey":{"N":"145"}},{"TheHashKey":{"S":"AHashKey146"},"TheRangeKey":{"N":"146"}},{"TheHashKey":{"S":"AHashKey147"},"TheRangeKey":{"N":"147"}},{"TheHashKey":{"S":"AHashKey148"},"TheRangeKey":{"N":"148"}},{"TheHashKey":{"S":"AHashKey149"},"TheRangeKey":{"N":"149"}},{"TheHashKey":{"S":"AHashKey150"},"TheRangeKey":{"N":"150"}},{"TheHashKey":{"S":"AHashKey151"},"TheRangeKey":{"N":"151"}},{"TheHashKey":{"S":"AHashKey152"},"TheRangeKey":{"N":"152"}},{"TheHashKey":{"S":"AHashKey153"},"TheRangeKey":{"N":"153"}},{"TheHashKey":{"S":"AHashKey154"},"TheRangeKey":{"N":"154"}},{"TheHashKey":{"S":"AHashKey155"},"TheRangeKey":{"N":"155"}},{"TheHashKey":{"S":"AHashKey156"},"TheRangeKey":{"N":"156"}},{"TheHashKey":{"S":"AHashKey157"},"TheRangeKey":{"N":"157"}},{"TheHashKey":{"S":"AHashKey158"},"TheRangeKey":{"N":"158"}},{"TheHashKey":{"S":"AHashKey159"},"TheRangeKey":{"N":"159"}},{"TheHashKey":{"S":"AHashKey160"},"TheRangeKey":{"N":"160"}},{"TheHashKey":{"S":"AHashKey161"},"TheRangeKey":{"N":"161"}},{"TheHashKey":{"S":"AHashKey162"},"TheRangeKey":{"N":"162"}},{"TheHashKey":{"S":"AHashKey163"},"TheRangeKey":{"N":"163"}},{"TheHashKey":{"S":"AHashKey164"},"TheRangeKey":{"N":"164"}},{"TheHashKey":{"S":"AHashKey165"},"TheRangeKey":{"N":"165"}},{"TheHashKey":{"S":"AHashKey166"},"TheRangeKey":{"N":"166"}},{"TheHashKey":{"S":"AHashKey167"},"TheRangeKey":{"N":"167"}},{"TheHashKey":{"S":"AHashKey168"},"TheRangeKey":{"N":"168"}},{"TheHashKey":{"S":"AHashKey169"},"TheRangeKey":{"N":"169"}},{"TheHashKey":{"S":"AHashKey170"},"TheRangeKey":{"N":"170"}},{"TheHashKey":{"S":"AHashKey171"},"TheRangeKey":{"N":"171"}},{"TheHashKey":{"S":"AHashKey172"},"TheRangeKey":{"N":"172"}},{"TheHashKey":{"S":"AHashKey173"},"TheRangeKey":{"N":"173"}},{"TheHashKey":{"S":"AHashKey174"},"TheRangeKey":{"N":"174"}},{"TheHashKey":{"S":"AHashKey175"},"TheRangeKey":{"N":"175"}},{"TheHashKey":{"S":"AHashKey176"},"TheRangeKey":{"N":"176"}},{"TheHashKey":{"S":"AHashKey177"},"TheRangeKey":{"N":"177"}},{"TheHashKey":{"S":"AHashKey178"},"TheRangeKey":{"N":"178"}},{"TheHashKey":{"S":"AHashKey179"},"TheRangeKey":{"N":"179"}},{"TheHashKey":{"S":"AHashKey180"},"TheRangeKey":{"N":"180"}},{"TheHashKey":{"S":"AHashKey181"},"TheRangeKey":{"N":"181"}},{"TheHashKey":{"S":"AHashKey182"},"TheRangeKey":{"N":"182"}},{"TheHashKey":{"S":"AHashKey183"},"TheRangeKey":{"N":"183"}},{"TheHashKey":{"S":"AHashKey184"},"TheRangeKey":{"N":"184"}},{"TheHashKey":{"S":"AHashKey185"},"TheRangeKey":{"N":"185"}},{"TheHashKey":{"S":"AHashKey186"},"TheRangeKey":{"N":"186"}},{"TheHashKey":{"S":"AHashKey187"},"TheRangeKey":{"N":"187"}},{"TheHashKey":{"S":"AHashKey188"},"TheRangeKey":{"N":"188"}},{"TheHashKey":{"S":"AHashKey189"},"TheRangeKey":{"N":"189"}},{"TheHashKey":{"S":"AHashKey190"},"TheRangeKey":{"N":"190"}},{"TheHashKey":{"S":"AHashKey191"},"TheRangeKey":{"N":"191"}},{"TheHashKey":{"S":"AHashKey192"},"TheRangeKey":{"N":"192"}},{"TheHashKey":{"S":"AHashKey193"},"TheRangeKey":{"N":"193"}},{"TheHashKey":{"S":"AHashKey194"},"TheRangeKey":{"N":"194"}},{"TheHashKey":{"S":"AHashKey195"},"TheRangeKey":{"N":"195"}},{"TheHashKey":{"S":"AHashKey196"},"TheRangeKey":{"N":"196"}},{"TheHashKey":{"S":"AHashKey197"},"TheRangeKey":{"N":"197"}},{"TheHashKey":{"S":"AHashKey198"},"TheRangeKey":{"N":"198"}},{"TheHashKey":{"S":"AHashKey199"},"TheRangeKey":{"N":"199"}},{"TheHashKey":{"S":"AHashKey200"},"TheRangeKey":{"N":"200"}}]}},"ReturnConsumedCapacity":"NONE"}' http://localhost:12333/ 2 | -------------------------------------------------------------------------------- /lib/batch_get_item_route/batch_get_item_route.go: -------------------------------------------------------------------------------- 1 | // Supports proxying the BatchGetItem endpoint. 2 | package batch_get_item_route 3 | 4 | import ( 5 | "encoding/json" 6 | "fmt" 7 | "github.com/smugmug/bbpd/lib/bbpd_runinfo" 8 | "github.com/smugmug/bbpd/lib/route_response" 9 | ep "github.com/smugmug/godynamo/endpoint" 10 | bgi "github.com/smugmug/godynamo/endpoints/batch_get_item" 11 | "io" 12 | "io/ioutil" 13 | "log" 14 | "net/http" 15 | "strings" 16 | "time" 17 | ) 18 | 19 | // BatchGetItemHandler accepts arbitrarily-sized BatchGetItem requests and relays them to Dynamo. 20 | func BatchGetItemHandler(w http.ResponseWriter, req *http.Request) { 21 | if bbpd_runinfo.BBPDAbortIfClosed(w) { 22 | return 23 | } 24 | start := time.Now() 25 | if req.Method != "POST" { 26 | e := "batch_get_item_route.BatchGetItemHandler:method only supports POST" 27 | log.Printf(e) 28 | http.Error(w, e, http.StatusBadRequest) 29 | return 30 | } 31 | pathElts := strings.Split(req.URL.Path, "/") 32 | if len(pathElts) != 2 { 33 | e := "batch_get_item_route.BatchGetItemHandler:cannot parse path. try /batch-get-item" 34 | log.Printf(e) 35 | http.Error(w, e, http.StatusBadRequest) 36 | return 37 | } 38 | 39 | bodybytes, read_err := ioutil.ReadAll(req.Body) 40 | if read_err != nil && read_err != io.EOF { 41 | e := fmt.Sprintf("batch_get_item_route.BatchGetItemHandler err reading req body: %s", read_err.Error()) 42 | log.Printf(e) 43 | http.Error(w, e, http.StatusInternalServerError) 44 | return 45 | } 46 | req.Body.Close() 47 | 48 | var b bgi.BatchGetItem 49 | 50 | um_err := json.Unmarshal(bodybytes, &b) 51 | if um_err != nil { 52 | e := fmt.Sprintf("batch_get_item_route.BatchGetItemHandler unmarshal err on %s to BatchGetItem %s", string(bodybytes), um_err.Error()) 53 | log.Printf(e) 54 | http.Error(w, e, http.StatusInternalServerError) 55 | return 56 | } 57 | 58 | if len(bodybytes) > bgi.QUERY_LIM_BYTES { 59 | e := fmt.Sprintf("batch_get_item_route.BatchGetItemHandler - payload over 1024kb, may be rejected by aws! splitting into segmented requests will likely mean each segment is accepted") 60 | log.Printf(e) 61 | } 62 | 63 | resp_body, code, resp_err := b.DoBatchGet() 64 | 65 | if resp_err != nil { 66 | e := fmt.Sprintf("batch_get_item_route.BatchGetItemHandler:err %s", 67 | resp_err.Error()) 68 | log.Printf(e) 69 | http.Error(w, e, http.StatusInternalServerError) 70 | return 71 | } 72 | 73 | if ep.HttpErr(code) { 74 | route_response.WriteError(w, code, "batch_get_item_route.BatchGetItemHandler", resp_body) 75 | return 76 | } 77 | 78 | mr_err := route_response.MakeRouteResponse( 79 | w, 80 | req, 81 | resp_body, 82 | code, 83 | start, 84 | bgi.ENDPOINT_NAME) 85 | if mr_err != nil { 86 | e := fmt.Sprintf("batch_get_item_route.BatchGetItemHandler %s", mr_err.Error()) 87 | log.Printf(e) 88 | } 89 | } 90 | 91 | func BatchGetItemJSONHandler(w http.ResponseWriter, req *http.Request) { 92 | if bbpd_runinfo.BBPDAbortIfClosed(w) { 93 | return 94 | } 95 | start := time.Now() 96 | if req.Method != "POST" { 97 | e := "batch_get_item_route.BatchGetItemJSONHandler:method only supports POST" 98 | log.Printf(e) 99 | http.Error(w, e, http.StatusBadRequest) 100 | return 101 | } 102 | pathElts := strings.Split(req.URL.Path, "/") 103 | if len(pathElts) != 2 { 104 | e := "batch_get_item_route.BatchGetItemJSONHandler:cannot parse path. try /batch-get-item" 105 | log.Printf(e) 106 | http.Error(w, e, http.StatusBadRequest) 107 | return 108 | } 109 | 110 | bodybytes, read_err := ioutil.ReadAll(req.Body) 111 | if read_err != nil && read_err != io.EOF { 112 | e := fmt.Sprintf("batch_get_item_route.BatchGetItemJSONHandler err reading req body: %s", read_err.Error()) 113 | log.Printf(e) 114 | http.Error(w, e, http.StatusInternalServerError) 115 | return 116 | } 117 | req.Body.Close() 118 | 119 | var b bgi.BatchGetItem 120 | 121 | um_err := json.Unmarshal(bodybytes, &b) 122 | if um_err != nil { 123 | e := fmt.Sprintf("batch_get_item_route.BatchGetItemJSONHandler unmarshal err on %s to BatchGetItem %s", string(bodybytes), um_err.Error()) 124 | log.Printf(e) 125 | http.Error(w, e, http.StatusInternalServerError) 126 | return 127 | } 128 | 129 | if len(bodybytes) > bgi.QUERY_LIM_BYTES { 130 | e := fmt.Sprintf("batch_get_item_route.BatchGetItemJSONHandler - payload over 1024kb, may be rejected by aws! splitting into segmented requests will likely mean each segment is accepted") 131 | log.Printf(e) 132 | } 133 | 134 | resp_body, code, resp_err := b.DoBatchGet() 135 | 136 | if resp_err != nil { 137 | e := fmt.Sprintf("batch_get_item_route.BatchGetItemJSONHandler:err %s", 138 | resp_err.Error()) 139 | log.Printf(e) 140 | http.Error(w, e, http.StatusInternalServerError) 141 | return 142 | } 143 | 144 | if ep.HttpErr(code) { 145 | route_response.WriteError(w, code, "batch_get_item_route.BatchGetItemJSONHandler", resp_body) 146 | return 147 | } 148 | 149 | // translate the Response to a ResponseItemsJSON 150 | resp := bgi.NewResponse() 151 | um_err = json.Unmarshal([]byte(resp_body), resp) 152 | if um_err != nil { 153 | e := fmt.Sprintf("batch_get_item_route.BatchGetItemJSONHandler:err %s", 154 | um_err.Error()) 155 | log.Printf(e) 156 | http.Error(w, e, http.StatusInternalServerError) 157 | return 158 | } 159 | resp_json, rerr := resp.ToResponseItemsJSON() 160 | if rerr != nil { 161 | e := fmt.Sprintf("batch_get_item_route.BatchGetItemJSONHandler:err %s", 162 | rerr.Error()) 163 | log.Printf(e) 164 | http.Error(w, e, http.StatusInternalServerError) 165 | return 166 | } 167 | json_body, jerr := json.Marshal(resp_json) 168 | if jerr != nil { 169 | e := fmt.Sprintf("batch_get_item_route.BatchGetItemJSONHandler:err %s", 170 | jerr.Error()) 171 | log.Printf(e) 172 | http.Error(w, e, http.StatusInternalServerError) 173 | return 174 | } 175 | 176 | mr_err := route_response.MakeRouteResponse( 177 | w, 178 | req, 179 | json_body, 180 | http.StatusOK, 181 | start, 182 | bgi.ENDPOINT_NAME) 183 | if mr_err != nil { 184 | e := fmt.Sprintf("batch_get_item_route.BatchGetItemJSONHandler %s", mr_err.Error()) 185 | log.Printf(e) 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /lib/describe_table_route/describe_table_route.go: -------------------------------------------------------------------------------- 1 | // Supports proxying the DescribeTable endpoint. 2 | package describe_table_route 3 | 4 | import ( 5 | "encoding/json" 6 | "fmt" 7 | "github.com/smugmug/bbpd/lib/bbpd_const" 8 | "github.com/smugmug/bbpd/lib/bbpd_msg" 9 | "github.com/smugmug/bbpd/lib/bbpd_runinfo" 10 | raw "github.com/smugmug/bbpd/lib/raw_post_route" 11 | "github.com/smugmug/bbpd/lib/route_response" 12 | ep "github.com/smugmug/godynamo/endpoint" 13 | desc "github.com/smugmug/godynamo/endpoints/describe_table" 14 | "io" 15 | "io/ioutil" 16 | "log" 17 | "net/http" 18 | "net/url" 19 | "strings" 20 | "time" 21 | ) 22 | 23 | // RawPostHandler relays the DescribeTable request to Dynamo directly. 24 | func RawPostHandler(w http.ResponseWriter, req *http.Request) { 25 | raw.RawPostReq(w, req, desc.DESCTABLE_ENDPOINT) 26 | } 27 | 28 | // StatusTableHandler is not a standard endpoint. It can be used to poll a table for readiness 29 | // after a CreateTable or UpdateTable request. 30 | func StatusTableHandler(w http.ResponseWriter, req *http.Request) { 31 | if bbpd_runinfo.BBPDAbortIfClosed(w) { 32 | return 33 | } 34 | start := time.Now() 35 | if req.Method != "GET" { 36 | e := "describe_table_route.StatusTableHandler:method only supports GET" 37 | log.Printf(e) 38 | http.Error(w, e, http.StatusBadRequest) 39 | return 40 | } 41 | pathElts := strings.Split(req.URL.Path, "/") 42 | if len(pathElts) != 3 { 43 | e := "describe_table_route.StatusTableHandler:cannot parse path. try /status-table/TABLENAME" 44 | log.Printf(e) 45 | http.Error(w, e, http.StatusBadRequest) 46 | return 47 | } 48 | ue_tn, ue_err := url.QueryUnescape(string(pathElts[2])) 49 | if ue_err != nil { 50 | e := fmt.Sprintf("cannot unescape %s, %s", 51 | string(pathElts[2]), ue_err.Error()) 52 | log.Printf(e) 53 | http.Error(w, e, http.StatusInternalServerError) 54 | return 55 | } 56 | 57 | status := "ACTIVE" // our default key 58 | if query_status, status_ok := req.URL.Query()["status"]; status_ok { 59 | status = query_status[0] 60 | } 61 | 62 | poll := false 63 | if query_poll, poll_ok := req.URL.Query()["poll"]; poll_ok { 64 | if query_poll[0] == "1" || query_poll[0] == "yes" { 65 | poll = true 66 | } 67 | } 68 | 69 | tries := 1 70 | if poll { 71 | tries = 50 72 | } 73 | is_status, status_err := desc.PollTableStatus( 74 | ue_tn, 75 | status, 76 | tries) 77 | 78 | if status_err != nil { 79 | e := fmt.Sprintf("describe_table_route.StatusTableHandler:cannot get status %s from %s, err %s", status, ue_tn, status_err.Error()) 80 | log.Printf(e) 81 | http.Error(w, e, http.StatusInternalServerError) 82 | return 83 | } 84 | 85 | s := desc.StatusResult{StatusResult: is_status} 86 | sj, sjerr := json.Marshal(s) 87 | if sjerr != nil { 88 | e := fmt.Sprintf("describe_table_route.StatusTableHandler:cannot get convert status to json, err %s", sjerr.Error()) 89 | log.Printf(e) 90 | http.Error(w, e, http.StatusInternalServerError) 91 | return 92 | } 93 | end := time.Now() 94 | duration := fmt.Sprintf("%v", end.Sub(start)) 95 | w.Header().Set(bbpd_const.CONTENTTYPE, bbpd_const.JSONMIME) 96 | b, json_err := json.Marshal(bbpd_msg.Response{ 97 | Name: desc.ENDPOINT_NAME, 98 | StatusCode: http.StatusOK, 99 | Body: sj, 100 | Run: bbpd_msg.RunInfo{Method: req.Method, 101 | Host: bbpd_const.LOCALHOST, 102 | Duration: duration, 103 | Start: start, 104 | End: end}}) 105 | if json_err != nil { 106 | e := fmt.Sprintf("describe_table_route.StatusTableHandler:desc marshal failure %s", json_err.Error()) 107 | log.Printf(e) 108 | http.Error(w, e, http.StatusInternalServerError) 109 | return 110 | } 111 | io.WriteString(w, string(b)) 112 | } 113 | 114 | // DescribeTableHandler can be used via POST (passing in JSON) or GET (as /DescribeTable/TableName). 115 | func DescribeTableHandler(w http.ResponseWriter, req *http.Request) { 116 | if bbpd_runinfo.BBPDAbortIfClosed(w) { 117 | return 118 | } 119 | if req.Method == "POST" { 120 | describeTable_POST_Handler(w, req) 121 | } else { 122 | e := fmt.Sprintf("describe_tables_route.DescribeTablesHandler:bad method %s", req.Method) 123 | log.Printf(e) 124 | http.Error(w, e, http.StatusInternalServerError) 125 | } 126 | } 127 | 128 | // Executes DescribeTable assuming it were requested with the POST method. 129 | func describeTable_POST_Handler(w http.ResponseWriter, req *http.Request) { 130 | start := time.Now() 131 | pathElts := strings.Split(req.URL.Path, "/") 132 | if len(pathElts) != 2 { 133 | e := "describe_table_route.describeTable_POST_Handler:cannot parse path." 134 | log.Printf(e) 135 | http.Error(w, e, http.StatusBadRequest) 136 | return 137 | } 138 | 139 | bodybytes, read_err := ioutil.ReadAll(req.Body) 140 | req.Body.Close() 141 | if read_err != nil && read_err != io.EOF { 142 | e := fmt.Sprintf("describe_table_route.describeTable_POST_Handler err reading req body: %s", read_err.Error()) 143 | log.Printf(e) 144 | http.Error(w, e, http.StatusInternalServerError) 145 | return 146 | } 147 | 148 | d := desc.NewDescribeTable() 149 | 150 | um_err := json.Unmarshal(bodybytes, d) 151 | if um_err != nil { 152 | e := fmt.Sprintf("describe_table_route.describeTable_POST_Handler unmarshal err on %s to Get %s", string(bodybytes), um_err.Error()) 153 | log.Printf(e) 154 | http.Error(w, e, http.StatusInternalServerError) 155 | return 156 | } 157 | 158 | resp_body, code, resp_err := d.EndpointReq() 159 | 160 | if resp_err != nil { 161 | e := fmt.Sprintf("describe_table_route.describeTable_POST_Handler:err %s", 162 | resp_err.Error()) 163 | log.Printf(e) 164 | http.Error(w, e, http.StatusInternalServerError) 165 | return 166 | } 167 | 168 | if ep.HttpErr(code) { 169 | route_response.WriteError(w, code, "describe_table_route.describeTable_POST_Handler", resp_body) 170 | return 171 | } 172 | 173 | mr_err := route_response.MakeRouteResponse( 174 | w, 175 | req, 176 | resp_body, 177 | code, 178 | start, 179 | desc.ENDPOINT_NAME) 180 | if mr_err != nil { 181 | e := fmt.Sprintf("describe_table_route.describeTable_POST_Handler %s", 182 | mr_err.Error()) 183 | log.Printf(e) 184 | http.Error(w, e, http.StatusInternalServerError) 185 | return 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## bbpd: A User's Guide 2 | 3 | ### Introduction 4 | 5 | *bbpd* is an http proxy for Amazon's DynamoDB service. 6 | 7 | For an overview of what DynamoDB is, please see: 8 | 9 | http://aws.amazon.com/dynamodb/ 10 | 11 | To install *bbpd*, run the following command: 12 | 13 | go get github.com/smugmug/bbpd 14 | 15 | *bbpd* is written in Go, and requires a Go 1.1 or higher toolchain to be installed on your system 16 | if you want to build it. If you just want to run it, then use apt-get as described above. 17 | 18 | If you want to hack on bbpd, you will need a Go environment. 19 | To understand how to use Go code in your environment, please see: 20 | 21 | http://golang.org/doc/install 22 | 23 | and other documentation on the golang.org site. 24 | 25 | ### Configuration 26 | 27 | **Important!** 28 | 29 | *bbpd* requires the configuration file used by its underlying library, GoDynamo. Please see 30 | the GoDynamo documentation (available at https://github.com/smugmug/godynamo/blob/master/README.md) for 31 | documentation regarding this configuration file. **You MUST properly configure GoDynamo for *bbpd* to 32 | function correctly.** 33 | 34 | 35 | ### Running 36 | 37 | When installed via `go get`, `bbpd` will reside in your `$GOPATH/bin` directory, and you should 38 | make sure that is in your shell's executable search path (`$PATH` etc), or you may copy the 39 | executable to an alternate location. 40 | 41 | This package also includes some convenience scripts for managing `bbpd` as a daemon which 42 | you may wish to use or alter. 43 | 44 | In the `bin/bbpd` directory are two shell scripts: `bbpd_daemon` and `bbpd_ctl`. Call 45 | `bbpd_ctl` with arguments `start` `stop` or `status`. These scripts assume `bbpd` has been copied into 46 | `/usr/bin`. These are useful if you want to avoid upstart (they are like old apachectl etc). 47 | 48 | ### Use 49 | 50 | The `curl` utility is used for examples below as it tends to be available for most platforms. 51 | 52 | First make sure that bbpd is running: 53 | 54 | curl "http://localhost:12333/Status" 55 | 56 | You should see some output. To make this more readable, add the `Verbose` and `Indent` options: 57 | 58 | curl -H "X-Bbpd-Verbose: True" -H "X-Bbpd-Indent: True" "http://localhost:12333/Status" 59 | 60 | Both of these output modifiers will take effect only if the headers are set, regardless of the 61 | header value. 62 | 63 | Here is an example using GetItem 64 | 65 | curl -H "X-Bbpd-Verbose: True" -H "X-Bbpd-Indent: True" -X POST -d '{"TableName":"mytable","Key":{"Date":{"N":"20131001"},"UserID":{"N":"1"}}}' "http://localhost:12333/GetItem" 66 | 67 | Other endpoints work similarly - you name the endpoint to be called, and provide a JSON serialization of the request you wish 68 | to submit. `bbpd` takes care of adding authorization and other headers for you. 69 | 70 | There is also a "compatibility mode" which makes the choice of endpoint a header, as described in the AWS documentation. 71 | In this mode, we could call GetItem like: 72 | 73 | curl -H "X-Amz-Target: DynamoDB_20120810.GetItem" -X POST -d '{"TableName":"mytable","Key":{"Date":{"N":"20131001"},"UserID":{"N":"1"}}}' "http://localhost:12333/" 74 | 75 | The "/" route is reserved for these "compatibility mode" endpoint. 76 | 77 | Other endpoints are accessed similarly. See the AWS documentation for specific request structure. 78 | 79 | ### JSON Documents 80 | 81 | Amazon has been augmenting their SDKs with wrappers that allow the caller to coerce 82 | their Items (both when writing and reading) to what I will refer to as "basic JSON". 83 | 84 | Basic JSON is stripped of the type signifiers ('S','NS', etc) that AWS specifies in their 85 | `AttributeValue` specification (http://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_AttributeValue.html). 86 | 87 | For example, the `AttributeValue` 88 | 89 | {"AString":{"S":"this is a string"}} 90 | 91 | is translated to this basic JSON: 92 | 93 | {"AString":"this is a string"} 94 | 95 | Here are some other examples: 96 | 97 | `AttrbiuteValue`: 98 | 99 | {"AStringSet":{"SS":["a","b","c"]}} 100 | {"ANumber":{"N":"4"}} 101 | {"AList":[{"N":"4"},{"SS":["a","b","c"]}]} 102 | 103 | are translated to these basic JSON values: 104 | 105 | {"AStringSet":["a","b","c"]} 106 | {"ANumber":4} 107 | {"AList":[4,["a","b","c"]]} 108 | 109 | `bbpd` now includes support for passing in basic JSON documents in place of Items in the 110 | following endpoints: 111 | 112 | - GetItemJSON 113 | - PutItemJSON 114 | - BatchGetItemJSON 115 | - BatchWriteItemJSON 116 | 117 | These are not AWS endpoints so they must be called explicitly (there are no `X-Amz-Target` 118 | designations for these, you cannot simply POST your input to the default toplevel route). 119 | 120 | They are called as 121 | 122 | http://localhost:$PORT/GetItemJSON 123 | http://localhost:$PORT/GetItemJSON 124 | http://localhost:$PORT/BatchGetItemJSON 125 | http://localhost:$PORT/BatchWriteItemJSON 126 | 127 | Note that AWS itself does not support basic JSON - the support is always delivered by a 128 | coercion of basic JSON to and from `AttrbiuteValue`. This coercion is lossy! For example, 129 | a `B` or `BS` will be coerced to a string type (`S`, `SS`) and `NULL` types will be 130 | coerced to `BOOL`. Use with caution. 131 | 132 | This feature is only enabled for `Item` types, not for `Key` or other `AttributeValue` 133 | aliases. So for example, `BatchWriteItemJSON` requests of type `DeleteRequest` cannot use 134 | basic JSON, only `PutRequest`. 135 | 136 | Here is a quick illustration that shows the same PutItem request using both AttributeValues 137 | and basic JSON: 138 | 139 | curl -H "X-Amz-Target: DynamoDB_20120810.PutItem" -X POST -d '{"TableName":"test-godynamo-livetest","Item":{"TheHashKey":{"S":"a-hash-key"},"TheRangeKey":{"N":"1"},"num":{"N":"1"},"numlist":{"NS":["1","2","3","-7234234234.234234"]},"stringlist":{"SS":["pk1_a","pk1_b","pk1_c"]}}}' http://localhost:12333/; 140 | 141 | curl -X POST -d '{"TableName":"test-godynamo-livetest","Item":{"TheHashKey":"a-hash-key","TheRangeKey":1,"num":1,"numlist":[1,2,3,9,-7234234234.234234],"stringlist":["pk1_a","pk1_b","pk1_c"]}}' http://localhost:12333/PutItemJSON; 142 | 143 | See the `tests` directory for more examples. 144 | -------------------------------------------------------------------------------- /tests/batch_write_item-livetest5.sh: -------------------------------------------------------------------------------- 1 | curl -H "X-Amz-Target: DynamoDB_20120810.BatchWriteItem" -X POST -d '{"RequestItems":{"test-godynamo-livetest":[{"PutRequest":{"Item":{"SomeValue":{"N":"201"},"TheHashKey":{"S":"AHashKey201"},"TheRangeKey":{"N":"201"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"202"},"TheHashKey":{"S":"AHashKey202"},"TheRangeKey":{"N":"202"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"203"},"TheHashKey":{"S":"AHashKey203"},"TheRangeKey":{"N":"203"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"204"},"TheHashKey":{"S":"AHashKey204"},"TheRangeKey":{"N":"204"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"205"},"TheHashKey":{"S":"AHashKey205"},"TheRangeKey":{"N":"205"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"206"},"TheHashKey":{"S":"AHashKey206"},"TheRangeKey":{"N":"206"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"207"},"TheHashKey":{"S":"AHashKey207"},"TheRangeKey":{"N":"207"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"208"},"TheHashKey":{"S":"AHashKey208"},"TheRangeKey":{"N":"208"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"209"},"TheHashKey":{"S":"AHashKey209"},"TheRangeKey":{"N":"209"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"210"},"TheHashKey":{"S":"AHashKey210"},"TheRangeKey":{"N":"210"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"211"},"TheHashKey":{"S":"AHashKey211"},"TheRangeKey":{"N":"211"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"212"},"TheHashKey":{"S":"AHashKey212"},"TheRangeKey":{"N":"212"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"213"},"TheHashKey":{"S":"AHashKey213"},"TheRangeKey":{"N":"213"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"214"},"TheHashKey":{"S":"AHashKey214"},"TheRangeKey":{"N":"214"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"215"},"TheHashKey":{"S":"AHashKey215"},"TheRangeKey":{"N":"215"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"216"},"TheHashKey":{"S":"AHashKey216"},"TheRangeKey":{"N":"216"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"217"},"TheHashKey":{"S":"AHashKey217"},"TheRangeKey":{"N":"217"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"218"},"TheHashKey":{"S":"AHashKey218"},"TheRangeKey":{"N":"218"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"219"},"TheHashKey":{"S":"AHashKey219"},"TheRangeKey":{"N":"219"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"220"},"TheHashKey":{"S":"AHashKey220"},"TheRangeKey":{"N":"220"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"221"},"TheHashKey":{"S":"AHashKey221"},"TheRangeKey":{"N":"221"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"222"},"TheHashKey":{"S":"AHashKey222"},"TheRangeKey":{"N":"222"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"223"},"TheHashKey":{"S":"AHashKey223"},"TheRangeKey":{"N":"223"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"224"},"TheHashKey":{"S":"AHashKey224"},"TheRangeKey":{"N":"224"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"225"},"TheHashKey":{"S":"AHashKey225"},"TheRangeKey":{"N":"225"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"226"},"TheHashKey":{"S":"AHashKey226"},"TheRangeKey":{"N":"226"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"227"},"TheHashKey":{"S":"AHashKey227"},"TheRangeKey":{"N":"227"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"228"},"TheHashKey":{"S":"AHashKey228"},"TheRangeKey":{"N":"228"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"229"},"TheHashKey":{"S":"AHashKey229"},"TheRangeKey":{"N":"229"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"230"},"TheHashKey":{"S":"AHashKey230"},"TheRangeKey":{"N":"230"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"231"},"TheHashKey":{"S":"AHashKey231"},"TheRangeKey":{"N":"231"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"232"},"TheHashKey":{"S":"AHashKey232"},"TheRangeKey":{"N":"232"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"233"},"TheHashKey":{"S":"AHashKey233"},"TheRangeKey":{"N":"233"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"234"},"TheHashKey":{"S":"AHashKey234"},"TheRangeKey":{"N":"234"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"235"},"TheHashKey":{"S":"AHashKey235"},"TheRangeKey":{"N":"235"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"236"},"TheHashKey":{"S":"AHashKey236"},"TheRangeKey":{"N":"236"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"237"},"TheHashKey":{"S":"AHashKey237"},"TheRangeKey":{"N":"237"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"238"},"TheHashKey":{"S":"AHashKey238"},"TheRangeKey":{"N":"238"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"239"},"TheHashKey":{"S":"AHashKey239"},"TheRangeKey":{"N":"239"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"240"},"TheHashKey":{"S":"AHashKey240"},"TheRangeKey":{"N":"240"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"241"},"TheHashKey":{"S":"AHashKey241"},"TheRangeKey":{"N":"241"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"242"},"TheHashKey":{"S":"AHashKey242"},"TheRangeKey":{"N":"242"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"243"},"TheHashKey":{"S":"AHashKey243"},"TheRangeKey":{"N":"243"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"244"},"TheHashKey":{"S":"AHashKey244"},"TheRangeKey":{"N":"244"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"245"},"TheHashKey":{"S":"AHashKey245"},"TheRangeKey":{"N":"245"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"246"},"TheHashKey":{"S":"AHashKey246"},"TheRangeKey":{"N":"246"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"247"},"TheHashKey":{"S":"AHashKey247"},"TheRangeKey":{"N":"247"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"248"},"TheHashKey":{"S":"AHashKey248"},"TheRangeKey":{"N":"248"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"249"},"TheHashKey":{"S":"AHashKey249"},"TheRangeKey":{"N":"249"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"250"},"TheHashKey":{"S":"AHashKey250"},"TheRangeKey":{"N":"250"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"251"},"TheHashKey":{"S":"AHashKey251"},"TheRangeKey":{"N":"251"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"252"},"TheHashKey":{"S":"AHashKey252"},"TheRangeKey":{"N":"252"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"253"},"TheHashKey":{"S":"AHashKey253"},"TheRangeKey":{"N":"253"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"254"},"TheHashKey":{"S":"AHashKey254"},"TheRangeKey":{"N":"254"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"255"},"TheHashKey":{"S":"AHashKey255"},"TheRangeKey":{"N":"255"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"256"},"TheHashKey":{"S":"AHashKey256"},"TheRangeKey":{"N":"256"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"257"},"TheHashKey":{"S":"AHashKey257"},"TheRangeKey":{"N":"257"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"258"},"TheHashKey":{"S":"AHashKey258"},"TheRangeKey":{"N":"258"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"259"},"TheHashKey":{"S":"AHashKey259"},"TheRangeKey":{"N":"259"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"260"},"TheHashKey":{"S":"AHashKey260"},"TheRangeKey":{"N":"260"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"261"},"TheHashKey":{"S":"AHashKey261"},"TheRangeKey":{"N":"261"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"262"},"TheHashKey":{"S":"AHashKey262"},"TheRangeKey":{"N":"262"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"263"},"TheHashKey":{"S":"AHashKey263"},"TheRangeKey":{"N":"263"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"264"},"TheHashKey":{"S":"AHashKey264"},"TheRangeKey":{"N":"264"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"265"},"TheHashKey":{"S":"AHashKey265"},"TheRangeKey":{"N":"265"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"266"},"TheHashKey":{"S":"AHashKey266"},"TheRangeKey":{"N":"266"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"267"},"TheHashKey":{"S":"AHashKey267"},"TheRangeKey":{"N":"267"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"268"},"TheHashKey":{"S":"AHashKey268"},"TheRangeKey":{"N":"268"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"269"},"TheHashKey":{"S":"AHashKey269"},"TheRangeKey":{"N":"269"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"270"},"TheHashKey":{"S":"AHashKey270"},"TheRangeKey":{"N":"270"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"271"},"TheHashKey":{"S":"AHashKey271"},"TheRangeKey":{"N":"271"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"272"},"TheHashKey":{"S":"AHashKey272"},"TheRangeKey":{"N":"272"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"273"},"TheHashKey":{"S":"AHashKey273"},"TheRangeKey":{"N":"273"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"274"},"TheHashKey":{"S":"AHashKey274"},"TheRangeKey":{"N":"274"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"275"},"TheHashKey":{"S":"AHashKey275"},"TheRangeKey":{"N":"275"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"276"},"TheHashKey":{"S":"AHashKey276"},"TheRangeKey":{"N":"276"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"277"},"TheHashKey":{"S":"AHashKey277"},"TheRangeKey":{"N":"277"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"278"},"TheHashKey":{"S":"AHashKey278"},"TheRangeKey":{"N":"278"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"279"},"TheHashKey":{"S":"AHashKey279"},"TheRangeKey":{"N":"279"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"280"},"TheHashKey":{"S":"AHashKey280"},"TheRangeKey":{"N":"280"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"281"},"TheHashKey":{"S":"AHashKey281"},"TheRangeKey":{"N":"281"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"282"},"TheHashKey":{"S":"AHashKey282"},"TheRangeKey":{"N":"282"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"283"},"TheHashKey":{"S":"AHashKey283"},"TheRangeKey":{"N":"283"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"284"},"TheHashKey":{"S":"AHashKey284"},"TheRangeKey":{"N":"284"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"285"},"TheHashKey":{"S":"AHashKey285"},"TheRangeKey":{"N":"285"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"286"},"TheHashKey":{"S":"AHashKey286"},"TheRangeKey":{"N":"286"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"287"},"TheHashKey":{"S":"AHashKey287"},"TheRangeKey":{"N":"287"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"288"},"TheHashKey":{"S":"AHashKey288"},"TheRangeKey":{"N":"288"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"289"},"TheHashKey":{"S":"AHashKey289"},"TheRangeKey":{"N":"289"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"290"},"TheHashKey":{"S":"AHashKey290"},"TheRangeKey":{"N":"290"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"291"},"TheHashKey":{"S":"AHashKey291"},"TheRangeKey":{"N":"291"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"292"},"TheHashKey":{"S":"AHashKey292"},"TheRangeKey":{"N":"292"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"293"},"TheHashKey":{"S":"AHashKey293"},"TheRangeKey":{"N":"293"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"294"},"TheHashKey":{"S":"AHashKey294"},"TheRangeKey":{"N":"294"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"295"},"TheHashKey":{"S":"AHashKey295"},"TheRangeKey":{"N":"295"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"296"},"TheHashKey":{"S":"AHashKey296"},"TheRangeKey":{"N":"296"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"297"},"TheHashKey":{"S":"AHashKey297"},"TheRangeKey":{"N":"297"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"298"},"TheHashKey":{"S":"AHashKey298"},"TheRangeKey":{"N":"298"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"299"},"TheHashKey":{"S":"AHashKey299"},"TheRangeKey":{"N":"299"}}}},{"PutRequest":{"Item":{"SomeValue":{"N":"300"},"TheHashKey":{"S":"AHashKey300"},"TheRangeKey":{"N":"300"}}}}]},"ReturnConsumedCapacity":"NONE","ReturnItemCollectionMetrics":"NONE"}' http://localhost:12333/ 2 | -------------------------------------------------------------------------------- /tests/batch_get_item-livetest3.sh: -------------------------------------------------------------------------------- 1 | curl -H "X-Amz-Target: DynamoDB_20120810.BatchGetItem" -X POST -d '{"RequestItems":{"test-godynamo-livetest":{"AttributesToGet":null,"ConsistentRead":false,"Keys":[{"TheHashKey":{"S":"AHashKey1"},"TheRangeKey":{"N":"1"}},{"TheHashKey":{"S":"AHashKey2"},"TheRangeKey":{"N":"2"}},{"TheHashKey":{"S":"AHashKey3"},"TheRangeKey":{"N":"3"}},{"TheHashKey":{"S":"AHashKey4"},"TheRangeKey":{"N":"4"}},{"TheHashKey":{"S":"AHashKey5"},"TheRangeKey":{"N":"5"}},{"TheHashKey":{"S":"AHashKey6"},"TheRangeKey":{"N":"6"}},{"TheHashKey":{"S":"AHashKey7"},"TheRangeKey":{"N":"7"}},{"TheHashKey":{"S":"AHashKey8"},"TheRangeKey":{"N":"8"}},{"TheHashKey":{"S":"AHashKey9"},"TheRangeKey":{"N":"9"}},{"TheHashKey":{"S":"AHashKey10"},"TheRangeKey":{"N":"10"}},{"TheHashKey":{"S":"AHashKey11"},"TheRangeKey":{"N":"11"}},{"TheHashKey":{"S":"AHashKey12"},"TheRangeKey":{"N":"12"}},{"TheHashKey":{"S":"AHashKey13"},"TheRangeKey":{"N":"13"}},{"TheHashKey":{"S":"AHashKey14"},"TheRangeKey":{"N":"14"}},{"TheHashKey":{"S":"AHashKey15"},"TheRangeKey":{"N":"15"}},{"TheHashKey":{"S":"AHashKey16"},"TheRangeKey":{"N":"16"}},{"TheHashKey":{"S":"AHashKey17"},"TheRangeKey":{"N":"17"}},{"TheHashKey":{"S":"AHashKey18"},"TheRangeKey":{"N":"18"}},{"TheHashKey":{"S":"AHashKey19"},"TheRangeKey":{"N":"19"}},{"TheHashKey":{"S":"AHashKey20"},"TheRangeKey":{"N":"20"}},{"TheHashKey":{"S":"AHashKey21"},"TheRangeKey":{"N":"21"}},{"TheHashKey":{"S":"AHashKey22"},"TheRangeKey":{"N":"22"}},{"TheHashKey":{"S":"AHashKey23"},"TheRangeKey":{"N":"23"}},{"TheHashKey":{"S":"AHashKey24"},"TheRangeKey":{"N":"24"}},{"TheHashKey":{"S":"AHashKey25"},"TheRangeKey":{"N":"25"}},{"TheHashKey":{"S":"AHashKey26"},"TheRangeKey":{"N":"26"}},{"TheHashKey":{"S":"AHashKey27"},"TheRangeKey":{"N":"27"}},{"TheHashKey":{"S":"AHashKey28"},"TheRangeKey":{"N":"28"}},{"TheHashKey":{"S":"AHashKey29"},"TheRangeKey":{"N":"29"}},{"TheHashKey":{"S":"AHashKey30"},"TheRangeKey":{"N":"30"}},{"TheHashKey":{"S":"AHashKey31"},"TheRangeKey":{"N":"31"}},{"TheHashKey":{"S":"AHashKey32"},"TheRangeKey":{"N":"32"}},{"TheHashKey":{"S":"AHashKey33"},"TheRangeKey":{"N":"33"}},{"TheHashKey":{"S":"AHashKey34"},"TheRangeKey":{"N":"34"}},{"TheHashKey":{"S":"AHashKey35"},"TheRangeKey":{"N":"35"}},{"TheHashKey":{"S":"AHashKey36"},"TheRangeKey":{"N":"36"}},{"TheHashKey":{"S":"AHashKey37"},"TheRangeKey":{"N":"37"}},{"TheHashKey":{"S":"AHashKey38"},"TheRangeKey":{"N":"38"}},{"TheHashKey":{"S":"AHashKey39"},"TheRangeKey":{"N":"39"}},{"TheHashKey":{"S":"AHashKey40"},"TheRangeKey":{"N":"40"}},{"TheHashKey":{"S":"AHashKey41"},"TheRangeKey":{"N":"41"}},{"TheHashKey":{"S":"AHashKey42"},"TheRangeKey":{"N":"42"}},{"TheHashKey":{"S":"AHashKey43"},"TheRangeKey":{"N":"43"}},{"TheHashKey":{"S":"AHashKey44"},"TheRangeKey":{"N":"44"}},{"TheHashKey":{"S":"AHashKey45"},"TheRangeKey":{"N":"45"}},{"TheHashKey":{"S":"AHashKey46"},"TheRangeKey":{"N":"46"}},{"TheHashKey":{"S":"AHashKey47"},"TheRangeKey":{"N":"47"}},{"TheHashKey":{"S":"AHashKey48"},"TheRangeKey":{"N":"48"}},{"TheHashKey":{"S":"AHashKey49"},"TheRangeKey":{"N":"49"}},{"TheHashKey":{"S":"AHashKey50"},"TheRangeKey":{"N":"50"}},{"TheHashKey":{"S":"AHashKey51"},"TheRangeKey":{"N":"51"}},{"TheHashKey":{"S":"AHashKey52"},"TheRangeKey":{"N":"52"}},{"TheHashKey":{"S":"AHashKey53"},"TheRangeKey":{"N":"53"}},{"TheHashKey":{"S":"AHashKey54"},"TheRangeKey":{"N":"54"}},{"TheHashKey":{"S":"AHashKey55"},"TheRangeKey":{"N":"55"}},{"TheHashKey":{"S":"AHashKey56"},"TheRangeKey":{"N":"56"}},{"TheHashKey":{"S":"AHashKey57"},"TheRangeKey":{"N":"57"}},{"TheHashKey":{"S":"AHashKey58"},"TheRangeKey":{"N":"58"}},{"TheHashKey":{"S":"AHashKey59"},"TheRangeKey":{"N":"59"}},{"TheHashKey":{"S":"AHashKey60"},"TheRangeKey":{"N":"60"}},{"TheHashKey":{"S":"AHashKey61"},"TheRangeKey":{"N":"61"}},{"TheHashKey":{"S":"AHashKey62"},"TheRangeKey":{"N":"62"}},{"TheHashKey":{"S":"AHashKey63"},"TheRangeKey":{"N":"63"}},{"TheHashKey":{"S":"AHashKey64"},"TheRangeKey":{"N":"64"}},{"TheHashKey":{"S":"AHashKey65"},"TheRangeKey":{"N":"65"}},{"TheHashKey":{"S":"AHashKey66"},"TheRangeKey":{"N":"66"}},{"TheHashKey":{"S":"AHashKey67"},"TheRangeKey":{"N":"67"}},{"TheHashKey":{"S":"AHashKey68"},"TheRangeKey":{"N":"68"}},{"TheHashKey":{"S":"AHashKey69"},"TheRangeKey":{"N":"69"}},{"TheHashKey":{"S":"AHashKey70"},"TheRangeKey":{"N":"70"}},{"TheHashKey":{"S":"AHashKey71"},"TheRangeKey":{"N":"71"}},{"TheHashKey":{"S":"AHashKey72"},"TheRangeKey":{"N":"72"}},{"TheHashKey":{"S":"AHashKey73"},"TheRangeKey":{"N":"73"}},{"TheHashKey":{"S":"AHashKey74"},"TheRangeKey":{"N":"74"}},{"TheHashKey":{"S":"AHashKey75"},"TheRangeKey":{"N":"75"}},{"TheHashKey":{"S":"AHashKey76"},"TheRangeKey":{"N":"76"}},{"TheHashKey":{"S":"AHashKey77"},"TheRangeKey":{"N":"77"}},{"TheHashKey":{"S":"AHashKey78"},"TheRangeKey":{"N":"78"}},{"TheHashKey":{"S":"AHashKey79"},"TheRangeKey":{"N":"79"}},{"TheHashKey":{"S":"AHashKey80"},"TheRangeKey":{"N":"80"}},{"TheHashKey":{"S":"AHashKey81"},"TheRangeKey":{"N":"81"}},{"TheHashKey":{"S":"AHashKey82"},"TheRangeKey":{"N":"82"}},{"TheHashKey":{"S":"AHashKey83"},"TheRangeKey":{"N":"83"}},{"TheHashKey":{"S":"AHashKey84"},"TheRangeKey":{"N":"84"}},{"TheHashKey":{"S":"AHashKey85"},"TheRangeKey":{"N":"85"}},{"TheHashKey":{"S":"AHashKey86"},"TheRangeKey":{"N":"86"}},{"TheHashKey":{"S":"AHashKey87"},"TheRangeKey":{"N":"87"}},{"TheHashKey":{"S":"AHashKey88"},"TheRangeKey":{"N":"88"}},{"TheHashKey":{"S":"AHashKey89"},"TheRangeKey":{"N":"89"}},{"TheHashKey":{"S":"AHashKey90"},"TheRangeKey":{"N":"90"}},{"TheHashKey":{"S":"AHashKey91"},"TheRangeKey":{"N":"91"}},{"TheHashKey":{"S":"AHashKey92"},"TheRangeKey":{"N":"92"}},{"TheHashKey":{"S":"AHashKey93"},"TheRangeKey":{"N":"93"}},{"TheHashKey":{"S":"AHashKey94"},"TheRangeKey":{"N":"94"}},{"TheHashKey":{"S":"AHashKey95"},"TheRangeKey":{"N":"95"}},{"TheHashKey":{"S":"AHashKey96"},"TheRangeKey":{"N":"96"}},{"TheHashKey":{"S":"AHashKey97"},"TheRangeKey":{"N":"97"}},{"TheHashKey":{"S":"AHashKey98"},"TheRangeKey":{"N":"98"}},{"TheHashKey":{"S":"AHashKey99"},"TheRangeKey":{"N":"99"}},{"TheHashKey":{"S":"AHashKey100"},"TheRangeKey":{"N":"100"}},{"TheHashKey":{"S":"AHashKey101"},"TheRangeKey":{"N":"101"}},{"TheHashKey":{"S":"AHashKey102"},"TheRangeKey":{"N":"102"}},{"TheHashKey":{"S":"AHashKey103"},"TheRangeKey":{"N":"103"}},{"TheHashKey":{"S":"AHashKey104"},"TheRangeKey":{"N":"104"}},{"TheHashKey":{"S":"AHashKey105"},"TheRangeKey":{"N":"105"}},{"TheHashKey":{"S":"AHashKey106"},"TheRangeKey":{"N":"106"}},{"TheHashKey":{"S":"AHashKey107"},"TheRangeKey":{"N":"107"}},{"TheHashKey":{"S":"AHashKey108"},"TheRangeKey":{"N":"108"}},{"TheHashKey":{"S":"AHashKey109"},"TheRangeKey":{"N":"109"}},{"TheHashKey":{"S":"AHashKey110"},"TheRangeKey":{"N":"110"}},{"TheHashKey":{"S":"AHashKey111"},"TheRangeKey":{"N":"111"}},{"TheHashKey":{"S":"AHashKey112"},"TheRangeKey":{"N":"112"}},{"TheHashKey":{"S":"AHashKey113"},"TheRangeKey":{"N":"113"}},{"TheHashKey":{"S":"AHashKey114"},"TheRangeKey":{"N":"114"}},{"TheHashKey":{"S":"AHashKey115"},"TheRangeKey":{"N":"115"}},{"TheHashKey":{"S":"AHashKey116"},"TheRangeKey":{"N":"116"}},{"TheHashKey":{"S":"AHashKey117"},"TheRangeKey":{"N":"117"}},{"TheHashKey":{"S":"AHashKey118"},"TheRangeKey":{"N":"118"}},{"TheHashKey":{"S":"AHashKey119"},"TheRangeKey":{"N":"119"}},{"TheHashKey":{"S":"AHashKey120"},"TheRangeKey":{"N":"120"}},{"TheHashKey":{"S":"AHashKey121"},"TheRangeKey":{"N":"121"}},{"TheHashKey":{"S":"AHashKey122"},"TheRangeKey":{"N":"122"}},{"TheHashKey":{"S":"AHashKey123"},"TheRangeKey":{"N":"123"}},{"TheHashKey":{"S":"AHashKey124"},"TheRangeKey":{"N":"124"}},{"TheHashKey":{"S":"AHashKey125"},"TheRangeKey":{"N":"125"}},{"TheHashKey":{"S":"AHashKey126"},"TheRangeKey":{"N":"126"}},{"TheHashKey":{"S":"AHashKey127"},"TheRangeKey":{"N":"127"}},{"TheHashKey":{"S":"AHashKey128"},"TheRangeKey":{"N":"128"}},{"TheHashKey":{"S":"AHashKey129"},"TheRangeKey":{"N":"129"}},{"TheHashKey":{"S":"AHashKey130"},"TheRangeKey":{"N":"130"}},{"TheHashKey":{"S":"AHashKey131"},"TheRangeKey":{"N":"131"}},{"TheHashKey":{"S":"AHashKey132"},"TheRangeKey":{"N":"132"}},{"TheHashKey":{"S":"AHashKey133"},"TheRangeKey":{"N":"133"}},{"TheHashKey":{"S":"AHashKey134"},"TheRangeKey":{"N":"134"}},{"TheHashKey":{"S":"AHashKey135"},"TheRangeKey":{"N":"135"}},{"TheHashKey":{"S":"AHashKey136"},"TheRangeKey":{"N":"136"}},{"TheHashKey":{"S":"AHashKey137"},"TheRangeKey":{"N":"137"}},{"TheHashKey":{"S":"AHashKey138"},"TheRangeKey":{"N":"138"}},{"TheHashKey":{"S":"AHashKey139"},"TheRangeKey":{"N":"139"}},{"TheHashKey":{"S":"AHashKey140"},"TheRangeKey":{"N":"140"}},{"TheHashKey":{"S":"AHashKey141"},"TheRangeKey":{"N":"141"}},{"TheHashKey":{"S":"AHashKey142"},"TheRangeKey":{"N":"142"}},{"TheHashKey":{"S":"AHashKey143"},"TheRangeKey":{"N":"143"}},{"TheHashKey":{"S":"AHashKey144"},"TheRangeKey":{"N":"144"}},{"TheHashKey":{"S":"AHashKey145"},"TheRangeKey":{"N":"145"}},{"TheHashKey":{"S":"AHashKey146"},"TheRangeKey":{"N":"146"}},{"TheHashKey":{"S":"AHashKey147"},"TheRangeKey":{"N":"147"}},{"TheHashKey":{"S":"AHashKey148"},"TheRangeKey":{"N":"148"}},{"TheHashKey":{"S":"AHashKey149"},"TheRangeKey":{"N":"149"}},{"TheHashKey":{"S":"AHashKey150"},"TheRangeKey":{"N":"150"}},{"TheHashKey":{"S":"AHashKey151"},"TheRangeKey":{"N":"151"}},{"TheHashKey":{"S":"AHashKey152"},"TheRangeKey":{"N":"152"}},{"TheHashKey":{"S":"AHashKey153"},"TheRangeKey":{"N":"153"}},{"TheHashKey":{"S":"AHashKey154"},"TheRangeKey":{"N":"154"}},{"TheHashKey":{"S":"AHashKey155"},"TheRangeKey":{"N":"155"}},{"TheHashKey":{"S":"AHashKey156"},"TheRangeKey":{"N":"156"}},{"TheHashKey":{"S":"AHashKey157"},"TheRangeKey":{"N":"157"}},{"TheHashKey":{"S":"AHashKey158"},"TheRangeKey":{"N":"158"}},{"TheHashKey":{"S":"AHashKey159"},"TheRangeKey":{"N":"159"}},{"TheHashKey":{"S":"AHashKey160"},"TheRangeKey":{"N":"160"}},{"TheHashKey":{"S":"AHashKey161"},"TheRangeKey":{"N":"161"}},{"TheHashKey":{"S":"AHashKey162"},"TheRangeKey":{"N":"162"}},{"TheHashKey":{"S":"AHashKey163"},"TheRangeKey":{"N":"163"}},{"TheHashKey":{"S":"AHashKey164"},"TheRangeKey":{"N":"164"}},{"TheHashKey":{"S":"AHashKey165"},"TheRangeKey":{"N":"165"}},{"TheHashKey":{"S":"AHashKey166"},"TheRangeKey":{"N":"166"}},{"TheHashKey":{"S":"AHashKey167"},"TheRangeKey":{"N":"167"}},{"TheHashKey":{"S":"AHashKey168"},"TheRangeKey":{"N":"168"}},{"TheHashKey":{"S":"AHashKey169"},"TheRangeKey":{"N":"169"}},{"TheHashKey":{"S":"AHashKey170"},"TheRangeKey":{"N":"170"}},{"TheHashKey":{"S":"AHashKey171"},"TheRangeKey":{"N":"171"}},{"TheHashKey":{"S":"AHashKey172"},"TheRangeKey":{"N":"172"}},{"TheHashKey":{"S":"AHashKey173"},"TheRangeKey":{"N":"173"}},{"TheHashKey":{"S":"AHashKey174"},"TheRangeKey":{"N":"174"}},{"TheHashKey":{"S":"AHashKey175"},"TheRangeKey":{"N":"175"}},{"TheHashKey":{"S":"AHashKey176"},"TheRangeKey":{"N":"176"}},{"TheHashKey":{"S":"AHashKey177"},"TheRangeKey":{"N":"177"}},{"TheHashKey":{"S":"AHashKey178"},"TheRangeKey":{"N":"178"}},{"TheHashKey":{"S":"AHashKey179"},"TheRangeKey":{"N":"179"}},{"TheHashKey":{"S":"AHashKey180"},"TheRangeKey":{"N":"180"}},{"TheHashKey":{"S":"AHashKey181"},"TheRangeKey":{"N":"181"}},{"TheHashKey":{"S":"AHashKey182"},"TheRangeKey":{"N":"182"}},{"TheHashKey":{"S":"AHashKey183"},"TheRangeKey":{"N":"183"}},{"TheHashKey":{"S":"AHashKey184"},"TheRangeKey":{"N":"184"}},{"TheHashKey":{"S":"AHashKey185"},"TheRangeKey":{"N":"185"}},{"TheHashKey":{"S":"AHashKey186"},"TheRangeKey":{"N":"186"}},{"TheHashKey":{"S":"AHashKey187"},"TheRangeKey":{"N":"187"}},{"TheHashKey":{"S":"AHashKey188"},"TheRangeKey":{"N":"188"}},{"TheHashKey":{"S":"AHashKey189"},"TheRangeKey":{"N":"189"}},{"TheHashKey":{"S":"AHashKey190"},"TheRangeKey":{"N":"190"}},{"TheHashKey":{"S":"AHashKey191"},"TheRangeKey":{"N":"191"}},{"TheHashKey":{"S":"AHashKey192"},"TheRangeKey":{"N":"192"}},{"TheHashKey":{"S":"AHashKey193"},"TheRangeKey":{"N":"193"}},{"TheHashKey":{"S":"AHashKey194"},"TheRangeKey":{"N":"194"}},{"TheHashKey":{"S":"AHashKey195"},"TheRangeKey":{"N":"195"}},{"TheHashKey":{"S":"AHashKey196"},"TheRangeKey":{"N":"196"}},{"TheHashKey":{"S":"AHashKey197"},"TheRangeKey":{"N":"197"}},{"TheHashKey":{"S":"AHashKey198"},"TheRangeKey":{"N":"198"}},{"TheHashKey":{"S":"AHashKey199"},"TheRangeKey":{"N":"199"}},{"TheHashKey":{"S":"AHashKey200"},"TheRangeKey":{"N":"200"}}]}},"ReturnConsumedCapacity":"NONE"}' http://localhost:12333/ 2 | -------------------------------------------------------------------------------- /lib/bbpd_route/bbpd_route.go: -------------------------------------------------------------------------------- 1 | // The core configuration of the http proxy. 2 | package bbpd_route 3 | 4 | import ( 5 | "encoding/json" 6 | "fmt" 7 | "github.com/smugmug/bbpd/lib/batch_get_item_route" 8 | "github.com/smugmug/bbpd/lib/batch_write_item_route" 9 | "github.com/smugmug/bbpd/lib/bbpd_const" 10 | "github.com/smugmug/bbpd/lib/bbpd_runinfo" 11 | "github.com/smugmug/bbpd/lib/bbpd_stats" 12 | "github.com/smugmug/bbpd/lib/create_table_route" 13 | "github.com/smugmug/bbpd/lib/delete_item_route" 14 | "github.com/smugmug/bbpd/lib/describe_table_route" 15 | "github.com/smugmug/bbpd/lib/get_item_route" 16 | "github.com/smugmug/bbpd/lib/list_tables_route" 17 | "github.com/smugmug/bbpd/lib/put_item_route" 18 | "github.com/smugmug/bbpd/lib/query_route" 19 | "github.com/smugmug/bbpd/lib/raw_post_route" 20 | "github.com/smugmug/bbpd/lib/route_response" 21 | "github.com/smugmug/bbpd/lib/scan_route" 22 | "github.com/smugmug/bbpd/lib/update_item_route" 23 | "github.com/smugmug/bbpd/lib/update_table_route" 24 | "github.com/smugmug/godynamo/aws_const" 25 | bgi "github.com/smugmug/godynamo/endpoints/batch_get_item" 26 | bwi "github.com/smugmug/godynamo/endpoints/batch_write_item" 27 | create "github.com/smugmug/godynamo/endpoints/create_table" 28 | delete_item "github.com/smugmug/godynamo/endpoints/delete_item" 29 | desc "github.com/smugmug/godynamo/endpoints/describe_table" 30 | get "github.com/smugmug/godynamo/endpoints/get_item" 31 | list "github.com/smugmug/godynamo/endpoints/list_tables" 32 | put "github.com/smugmug/godynamo/endpoints/put_item" 33 | query "github.com/smugmug/godynamo/endpoints/query" 34 | scan "github.com/smugmug/godynamo/endpoints/scan" 35 | update_item "github.com/smugmug/godynamo/endpoints/update_item" 36 | update_table "github.com/smugmug/godynamo/endpoints/update_table" 37 | "log" 38 | "net" 39 | "net/http" 40 | "strconv" 41 | "strings" 42 | "time" 43 | 44 | delete_table "github.com/smugmug/godynamo/endpoints/delete_table" 45 | // undelete to enable table deletions (dangerous!) 46 | // "github.com/smugmug/bbpd/lib/delete_table_route" 47 | ) 48 | 49 | const ( 50 | URI_PATH_SEP = "/" 51 | STATUSPATH = URI_PATH_SEP + "Status" 52 | STATUSTABLEPATH = URI_PATH_SEP + "StatusTable" + URI_PATH_SEP 53 | RAWPOSTPATH = URI_PATH_SEP + "RawPost" + URI_PATH_SEP 54 | DESCRIBETABLEPATH = URI_PATH_SEP + desc.ENDPOINT_NAME 55 | DESCRIBETABLEGETPATH = URI_PATH_SEP + desc.ENDPOINT_NAME + URI_PATH_SEP 56 | DELETETABLEPATH = URI_PATH_SEP + delete_table.ENDPOINT_NAME 57 | DELETETABLEGETPATH = URI_PATH_SEP + delete_table.ENDPOINT_NAME + URI_PATH_SEP 58 | LISTTABLESPATH = URI_PATH_SEP + list.ENDPOINT_NAME 59 | CREATETABLEPATH = URI_PATH_SEP + create.ENDPOINT_NAME 60 | UPDATETABLEPATH = URI_PATH_SEP + update_table.ENDPOINT_NAME 61 | PUTITEMPATH = URI_PATH_SEP + put.ENDPOINT_NAME 62 | PUTITEMJSONPATH = URI_PATH_SEP + put.JSON_ENDPOINT_NAME 63 | GETITEMPATH = URI_PATH_SEP + get.ENDPOINT_NAME 64 | GETITEMJSONPATH = URI_PATH_SEP + get.JSON_ENDPOINT_NAME 65 | BATCHGETITEMPATH = URI_PATH_SEP + bgi.ENDPOINT_NAME 66 | BATCHGETITEMJSONPATH = URI_PATH_SEP + bgi.JSON_ENDPOINT_NAME 67 | BATCHWRITEITEMPATH = URI_PATH_SEP + bwi.ENDPOINT_NAME 68 | BATCHWRITEITEMJSONPATH = URI_PATH_SEP + bwi.JSON_ENDPOINT_NAME 69 | DELETEITEMPATH = URI_PATH_SEP + delete_item.ENDPOINT_NAME 70 | UPDATEITEMPATH = URI_PATH_SEP + update_item.ENDPOINT_NAME 71 | QUERYPATH = URI_PATH_SEP + query.ENDPOINT_NAME 72 | SCANPATH = URI_PATH_SEP + scan.ENDPOINT_NAME 73 | COMPATPATH = URI_PATH_SEP 74 | ) 75 | 76 | var ( 77 | availableGetHandlers []string 78 | availablePostHandlers []string 79 | availableHandlers []string 80 | srv *http.Server 81 | port *int 82 | ) 83 | 84 | type Status_Struct struct { 85 | Status string 86 | AvailableHandlers []string 87 | Args map[string]string 88 | Summary bbpd_stats.Summary 89 | } 90 | 91 | func init() { 92 | // we want this to be initialized to be unuseable 93 | port = nil 94 | // available handlers 95 | availableGetHandlers = []string{ 96 | DESCRIBETABLEGETPATH, 97 | } 98 | availablePostHandlers = []string{ 99 | DELETEITEMPATH, 100 | LISTTABLESPATH, 101 | CREATETABLEPATH, 102 | UPDATETABLEPATH, 103 | STATUSTABLEPATH, 104 | PUTITEMPATH, 105 | PUTITEMJSONPATH, 106 | GETITEMPATH, 107 | GETITEMJSONPATH, 108 | BATCHGETITEMPATH, 109 | BATCHGETITEMJSONPATH, 110 | BATCHWRITEITEMPATH, 111 | BATCHWRITEITEMJSONPATH, 112 | UPDATEITEMPATH, 113 | QUERYPATH, 114 | SCANPATH, 115 | RAWPOSTPATH, 116 | COMPATPATH, 117 | } 118 | availableHandlers = append(availableHandlers, availableGetHandlers...) 119 | availableHandlers = append(availableHandlers, availablePostHandlers...) 120 | srv = nil 121 | } 122 | 123 | func Get_port() int { 124 | if port == nil { 125 | return 0 126 | } else { 127 | return *port 128 | } 129 | } 130 | 131 | // StatusHandler displays available handlers. 132 | func statusHandler(w http.ResponseWriter, req *http.Request) { 133 | if bbpd_runinfo.BBPDAbortIfClosed(w) { 134 | return 135 | } 136 | if req.Method != "GET" { 137 | e := "method only supports GET" 138 | http.Error(w, e, http.StatusBadRequest) 139 | return 140 | } 141 | var ss Status_Struct 142 | ss.Args = make(map[string]string) 143 | ss.Status = "ready" 144 | 145 | ss.Args[bbpd_const.X_BBPD_VERBOSE] = "set '-H \"X-Bbpd-Verbose: True\" ' to get verbose output" 146 | ss.Args[bbpd_const.X_BBPD_INDENT] = "set '-H \"X-Bbpd-Indent: True\" ' to indent the top-level json" 147 | ss.AvailableHandlers = availableHandlers 148 | ss.Summary = bbpd_stats.GetSummary() 149 | sj, sj_err := json.Marshal(ss) 150 | if sj_err != nil { 151 | e := fmt.Sprintf("bbpd_route.statusHandler:status marshal err %s", sj_err.Error()) 152 | log.Printf(e) 153 | http.Error(w, e, http.StatusInternalServerError) 154 | return 155 | } 156 | 157 | mr_err := route_response.MakeRouteResponse( 158 | w, 159 | req, 160 | sj, 161 | http.StatusOK, 162 | time.Now(), 163 | "Status") 164 | if mr_err != nil { 165 | e := fmt.Sprintf("bbpd_route.StatusHandler %s", mr_err.Error()) 166 | log.Printf(e) 167 | } 168 | } 169 | 170 | // can we use this port? 171 | func canAssignPort(requestedPort int) bool { 172 | _, err := net.Dial("tcp", bbpd_const.LOCALHOST+":"+strconv.Itoa(requestedPort)) 173 | return err != nil 174 | } 175 | 176 | // CompatHandler allows bbpd to act as a partial pass-through proxy. Users can provide 177 | // their own body and endpoint target header, but other headers are ignored. 178 | // To use this, set headers with your http client. For example, with curl: 179 | // curl -H "X-Amz-Target: DynamoDB_20120810.DescribeTable" -X POST -d '{"TableName":"mytable"}' http://localhost:12333/ 180 | // or alternately 181 | // curl -H "X-Amz-Target: DescribeTable" -X POST -d '{"TableName":"mytable"}' http://localhost:12333/ 182 | // if you wish to just use the default API version string. 183 | func CompatHandler(w http.ResponseWriter, req *http.Request) { 184 | if bbpd_runinfo.BBPDAbortIfClosed(w) { 185 | return 186 | } 187 | // look for the X-Amz-Target header 188 | target_, target_ok := req.Header[aws_const.AMZ_TARGET_HDR] 189 | if !target_ok { 190 | e := fmt.Sprintf("bbpd_route.CompatHandler:missing %s", aws_const.AMZ_TARGET_HDR) 191 | log.Printf(e) 192 | http.Error(w, e, http.StatusBadRequest) 193 | return 194 | } 195 | target := target_[0] 196 | normalized_target := target 197 | target_version_delim := "." 198 | // allow the header to have the API version string or not 199 | if strings.Contains(target, target_version_delim) { 200 | vers_target := strings.SplitN(target, target_version_delim, 2) 201 | if vers_target[0] != aws_const.CURRENT_API_VERSION { 202 | e := fmt.Sprintf("bbpd_route.CompatHandler:unsupported API version '%s'", vers_target[0]) 203 | log.Printf(e) 204 | http.Error(w, e, http.StatusBadRequest) 205 | return 206 | } 207 | normalized_target = vers_target[1] 208 | } 209 | endpoint_path := "/" + normalized_target 210 | if endpoint_path == COMPATPATH || normalized_target == "" { 211 | e := fmt.Sprintf("bbpd_route.CompatHandler:must call named endpoint") 212 | log.Printf(e) 213 | http.Error(w, e, http.StatusBadRequest) 214 | return 215 | } 216 | 217 | // call the proper handler for the header 218 | switch endpoint_path { 219 | case DESCRIBETABLEPATH: 220 | describe_table_route.RawPostHandler(w, req) 221 | return 222 | case LISTTABLESPATH: 223 | list_tables_route.RawPostHandler(w, req) 224 | return 225 | case CREATETABLEPATH: 226 | create_table_route.RawPostHandler(w, req) 227 | return 228 | case UPDATETABLEPATH: 229 | update_table_route.RawPostHandler(w, req) 230 | return 231 | case STATUSTABLEPATH: 232 | describe_table_route.StatusTableHandler(w, req) 233 | return 234 | case PUTITEMPATH: 235 | put_item_route.RawPostHandler(w, req) 236 | return 237 | case GETITEMPATH: 238 | get_item_route.RawPostHandler(w, req) 239 | return 240 | case BATCHGETITEMPATH: 241 | batch_get_item_route.BatchGetItemHandler(w, req) 242 | return 243 | case BATCHWRITEITEMPATH: 244 | batch_write_item_route.BatchWriteItemHandler(w, req) 245 | return 246 | case DELETEITEMPATH: 247 | delete_item_route.RawPostHandler(w, req) 248 | return 249 | case UPDATEITEMPATH: 250 | update_item_route.RawPostHandler(w, req) 251 | return 252 | case QUERYPATH: 253 | query_route.RawPostHandler(w, req) 254 | return 255 | case SCANPATH: 256 | scan_route.RawPostHandler(w, req) 257 | return 258 | default: 259 | e := fmt.Sprintf("bbpd_route.CompatHandler:unknown endpoint '%s'", endpoint_path) 260 | log.Printf(e) 261 | http.Error(w, e, http.StatusBadRequest) 262 | return 263 | } 264 | } 265 | 266 | // StartBBPD is where the proxy http server is started. 267 | // The requestedPort is a *int so it can be nil'able. passing 0 as an implied 268 | // null value could result in a dial that takes any available port, as is 269 | // implied by the go docs. 270 | func StartBBPD(requestedPorts []int) error { 271 | // try to get a port to listen to 272 | for _, p := range requestedPorts { 273 | e := fmt.Sprintf("trying to bind to port:%d", p) 274 | log.Printf(e) 275 | if canAssignPort(p) { 276 | port = &p 277 | break 278 | } else { 279 | e := fmt.Sprintf("port %d already in use", p) 280 | log.Printf(e) 281 | } 282 | } 283 | if port == nil { 284 | // if all ports are in use, we may assume that other bbpd invocations are 285 | // running correctly. in which case, return nil here and the caller will 286 | // exit with code 0, which is important to prevent rc managers etc from 287 | // automatically respawning the program 288 | log.Printf("bbpd_route.StartBBPD:no listen port") 289 | return nil 290 | } 291 | e := fmt.Sprintf("init routing on port %d", *port) 292 | log.Printf(e) 293 | http.HandleFunc(STATUSPATH, statusHandler) 294 | http.HandleFunc(DESCRIBETABLEPATH, describe_table_route.RawPostHandler) 295 | http.HandleFunc(DESCRIBETABLEGETPATH, describe_table_route.DescribeTableHandler) 296 | http.HandleFunc(LISTTABLESPATH, list_tables_route.ListTablesHandler) 297 | http.HandleFunc(CREATETABLEPATH, create_table_route.RawPostHandler) 298 | http.HandleFunc(UPDATETABLEPATH, update_table_route.RawPostHandler) 299 | http.HandleFunc(STATUSTABLEPATH, describe_table_route.StatusTableHandler) 300 | http.HandleFunc(PUTITEMPATH, put_item_route.RawPostHandler) 301 | http.HandleFunc(PUTITEMJSONPATH, put_item_route.PutItemJSONHandler) 302 | http.HandleFunc(GETITEMPATH, get_item_route.RawPostHandler) 303 | http.HandleFunc(GETITEMJSONPATH, get_item_route.GetItemJSONHandler) 304 | http.HandleFunc(BATCHGETITEMPATH, batch_get_item_route.BatchGetItemHandler) 305 | http.HandleFunc(BATCHGETITEMJSONPATH, batch_get_item_route.BatchGetItemJSONHandler) 306 | http.HandleFunc(BATCHWRITEITEMPATH, batch_write_item_route.BatchWriteItemHandler) 307 | http.HandleFunc(BATCHWRITEITEMJSONPATH, batch_write_item_route.BatchWriteItemJSONHandler) 308 | http.HandleFunc(DELETEITEMPATH, delete_item_route.RawPostHandler) 309 | http.HandleFunc(UPDATEITEMPATH, update_item_route.RawPostHandler) 310 | http.HandleFunc(QUERYPATH, query_route.RawPostHandler) 311 | http.HandleFunc(SCANPATH, scan_route.RawPostHandler) 312 | http.HandleFunc(RAWPOSTPATH, raw_post_route.RawPostHandler) 313 | http.HandleFunc(COMPATPATH, CompatHandler) 314 | 315 | // undelete these to enable table deletions, a little dangerous! 316 | // http.HandleFunc(DELETETABLEPATH, delete_table_route.RawPostHandler) 317 | // http.HandleFunc(DELETETABLEGETPATH, delete_table_route.DeleteTableHandler) 318 | 319 | const SERV_TIMEOUT = 20 320 | srv = &http.Server{ 321 | Addr: ":" + strconv.Itoa(*port), 322 | // The timeouts seems too-long, but they accomodates the exponential decay retry loop. 323 | // Programs using this can either change these directly or use goroutine timeouts 324 | // to impose a local minimum. 325 | ReadTimeout: SERV_TIMEOUT * time.Second, 326 | WriteTimeout: SERV_TIMEOUT * time.Second, 327 | ConnState: func(conn net.Conn, new_state http.ConnState) { 328 | bbpd_runinfo.RecordConnState(new_state) 329 | return 330 | }, 331 | } 332 | bbpd_runinfo.SetBBPDAccept() 333 | return srv.ListenAndServe() 334 | } 335 | 336 | func StopBBPD() error { 337 | return bbpd_runinfo.StopBBPD() 338 | } 339 | --------------------------------------------------------------------------------