Permalink
Browse files

Initial commit

  • Loading branch information...
dougt committed Mar 2, 2013
1 parent 64ee60d commit ee12a19b80940e6c2739f17a021e729309b56aa9
Showing with 379 additions and 0 deletions.
  1. +30 −0 LICENCE
  2. +228 −0 server.go
  3. +71 −0 test.html
  4. +19 −0 uuid/uuid.go
  5. +31 −0 uuid/uuid_test.go
View
30 LICENCE
@@ -0,0 +1,30 @@
+Copyright (c) 2013, Mozilla. All rights reserved.
+
+Redistribution and use of this software in source and binary forms,
+with or without modification, are permitted provided that the following
+conditions are met:
+
+* Redistributions of source code must retain the above
+ copyright notice, this list of conditions and the
+ following disclaimer.
+
+* Redistributions in binary form must reproduce the above
+ copyright notice, this list of conditions and the
+ following disclaimer in the documentation and/or other
+ materials provided with the distribution.
+
+* Neither the name of Mozilla. nor the names of its contributors
+ may be used to endorse or promote products derived from this
+ software without specific prior written permission of Mozilla.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
View
228 server.go
@@ -0,0 +1,228 @@
+package main
+
+import (
+ "code.google.com/p/go.net/websocket"
+ "net/http"
+ "log"
+ "encoding/json"
+ "strings"
+ "./uuid"
+)
+
+const (
+ HOST_NAME = "localhost"
+ PORT_NUMBER = "8080"
+ APPSERVER_API_PREFIX = "/notify/"
+)
+
+type Client struct {
+ Websocket *websocket.Conn
+ UAID string
+}
+
+type Channel struct {
+ ChannelID string `json:"channelID"`
+ Version string `json:"version"`
+}
+
+var uaid_to_channel map[string][]Channel;
+var channel_to_client map[string] *Client;
+
+func handleRegister(client *Client, f map[string]interface{}) {
+
+ log.Println(" -> handleRegister");
+
+ if (f["channelID"] == nil) {
+ log.Println("channelID is missing!");
+ return;
+ }
+
+ var channelID = f["channelID"].(string);
+ var pushEndpoint = "http://" + HOST_NAME + ":" + PORT_NUMBER + APPSERVER_API_PREFIX + channelID;
+
+ uaid_to_channel[client.UAID] = append(uaid_to_channel[client.UAID], Channel{channelID, ""});
+ channel_to_client[channelID] = client;
+
+ type RegisterResponse struct {
+ Name string `json:"messageType"`
+ Status int `json:"status"`
+ PushEndpoint string `json:"pushEndpoint"`
+ ChannelID string `json:"channelID"`
+ }
+
+ register := RegisterResponse{"register", 200, pushEndpoint, channelID}
+
+ j, err := json.Marshal(register);
+ if err != nil || string(j) == "null" {
+ log.Println("Could not convert hello response to json %s",err)
+ return;
+ }
+
+ if err = websocket.Message.Send(client.Websocket, string(j)); err != nil {
+ // we could not send the message to a peer
+ log.Println("Could not send message to ", client.Websocket, err.Error())
+ }
+}
+
+func handleHello(client *Client, f map[string]interface{}) {
+ log.Println(" -> handleHello");
+
+ status := 200;
+
+ if (f["uaid"] == nil) {
+ uaid, err := uuid.GenUUID()
+ if err != nil {
+ status = 400;
+ log.Println("GenUUID error %s",err)
+ }
+ client.UAID = uaid;
+ } else {
+ client.UAID = f["uaid"].(string)
+ }
+
+ type HelloResponse struct {
+ Name string `json:"messageType"`
+ Status int `json:"status"`
+ UAID string `json:"uaid"`
+ Channels []Channel `json:"channelIDs"`
+ }
+
+ hello := HelloResponse{"hello", status, client.UAID, uaid_to_channel[client.UAID]}
+
+ j, err := json.Marshal(hello);
+ if err != nil || string(j) == "null" {
+ log.Println("Could not convert hello response to json %s",err)
+ return;
+ }
+
+ // update the channel_to_client table
+// for channel := range uaid_to_channel[client.UAID] {
+// channel_to_client[channel.ChannelID] = client;
+// }
+
+
+ log.Println("going to send: \n ", string(j));
+ if err = websocket.Message.Send(client.Websocket, string(j)); err != nil {
+ log.Println("Could not send message to ", client.Websocket, err.Error())
+ }
+}
+
+func handleAck(client *Client, f map[string]interface{}) {
+ log.Println(" -> ack");
+}
+
+func pushHandler(ws *websocket.Conn) {
+
+ client := Client{ws, ""}
+
+ for {
+ var f map[string]interface{}
+
+ var err error
+ if err = websocket.JSON.Receive(ws, &f); err != nil {
+ log.Println("Websocket Disconnected.", err.Error())
+ ws.Close();
+ return;
+ }
+
+ switch f["messageType"] {
+ case "hello":
+ handleHello(&client, f);
+ break;
+ case "register":
+ handleRegister(&client, f);
+ break;
+
+ case "ack":
+ handleAck(&client, f);
+ break;
+ default:
+ log.Println(" -> Unknown", f);
+ break;
+ }
+ }
+}
+
+func notifyHandler(w http.ResponseWriter, r *http.Request) {
+
+ if r.Method != "PUT" {
+ w.WriteHeader(http.StatusBadRequest)
+ w.Write([]byte("Method must be PUT."))
+ return
+ }
+
+ channelID := strings.Trim(r.URL.Path, APPSERVER_API_PREFIX)
+
+ if (strings.Contains(channelID, "/") || len(channelID) != (32+4)) {
+ w.WriteHeader(http.StatusBadRequest)
+ w.Write([]byte("Could not find a valid channelID."))
+ return;
+ }
+
+
+ if err := r.ParseForm(); err != nil {
+ w.WriteHeader(http.StatusBadRequest)
+ w.Write([]byte("Could not find a valid version."))
+ return;
+ }
+
+ values := r.Form["version"]
+
+ if (len(values) != 1) {
+ w.WriteHeader(http.StatusBadRequest)
+ w.Write([]byte("Could not find one version."))
+ return;
+ }
+
+ version := values[0];
+ client := channel_to_client[channelID];
+ if (client == nil) {
+ w.WriteHeader(http.StatusBadRequest)
+ w.Write([]byte("Bad Channel ID."))
+ return;
+ }
+
+ uaid := client.UAID;
+ log.Println("uaid: ", uaid, "channelID: ", channelID, " version: ", version);
+
+ // update the channel with the new version.
+ for i := range uaid_to_channel[uaid] {
+ if (uaid_to_channel[uaid][i].ChannelID == channelID) {
+ uaid_to_channel[uaid][i].Version = version;
+ }
+ }
+
+ type NotificationResponse struct {
+ Name string `json:"messageType"`
+ Channels []Channel `json:"updates"`
+ }
+
+ notification := NotificationResponse{"notification", uaid_to_channel[client.UAID]}
+
+ j, err := json.Marshal(notification);
+ if err != nil || string(j) == "null" {
+ log.Println("Could not convert hello response to json %s",err)
+ return;
+ }
+
+ log.Println("going to send: \n ", string(j));
+ if err = websocket.Message.Send(client.Websocket, string(j)); err != nil {
+ log.Println("Could not send message to ", client.Websocket, err.Error())
+ }
+
+}
+
+func main() {
+
+ uaid_to_channel = make(map[string][]Channel)
+ channel_to_client = make(map[string] *Client);
+
+ http.Handle("/", http.FileServer(http.Dir(".")))
+
+ http.Handle("/push", websocket.Handler(pushHandler))
+ http.HandleFunc(APPSERVER_API_PREFIX, notifyHandler);
+
+ log.Println("Listening on ", HOST_NAME + ":" + PORT_NUMBER );
+ log.Fatal(http.ListenAndServe(HOST_NAME + ":" + PORT_NUMBER , nil))
+}
+
View
@@ -0,0 +1,71 @@
+<html>
+<head>
+<title>push notification test</title>
+
+<style>
+p { font-size: 12px;
+ color: rgb(0, 220, 98);
+ background-color: black;
+}
+body { background-color: black; }
+
+</style>
+<meta content="text/html;charset=utf-8" http-equiv="Content-Type">
+<meta content="utf-8" http-equiv="encoding">
+
+<script>
+
+navigator.mozSetMessageHandler('push', function(e) {
+ alert("Got system message! ", e);
+});
+
+var version = 0;
+
+function log(x) {
+ var d = document.getElementById("d");
+ d.innerHTML += "<p>" + x + "</p>";
+}
+
+window.addEventListener('load', start, false);
+
+function start() {
+
+ var push = navigator.pushNotification;
+
+ var req = push.register();
+ req.onsuccess = function(e) {
+ var pushEndpoint = e.target.result.pushEndpoint;
+
+ log("pushEndpoint: " + pushEndpoint);
+
+ var button = document.getElementById("button");
+ button.addEventListener("click", function() {
+
+ var request = new XMLHttpRequest();
+ request.onload = function() {
+ log("update successful to " + pushEndpoint);
+ };
+ request.onerror = function(e) {
+ log("error when trying to create an update " + e.error);
+ };
+ request.open("PUT", pushEndpoint, true);
+ request.setRequestHeader('Content-type','application/x-www-form-urlencoded');
+ request.send("version=" + version);
+ version++;
+ }, false);
+ }
+
+ req.onerror = function(e) {
+ log("Error: " + JSON.stringify(e));
+ }
+}
+
+</script>
+</head>
+<body>
+<div id="d">
+push notification test
+</div>
+<button type="button" id="button">Generate update</button>
+</body>
+</html>
View
@@ -0,0 +1,19 @@
+package uuid
+
+import (
+ "encoding/hex"
+ "crypto/rand"
+)
+
+func GenUUID() (string, error) {
+ uuid := make([]byte, 16)
+ n, err := rand.Read(uuid)
+ if n != len(uuid) || err != nil {
+ return "", err
+ }
+ // TODO: verify the two lines implement RFC 4122 correctly
+ uuid[8] = 0x80 // variant bits see page 5
+ uuid[4] = 0x40 // version 4 Pseudo Random, see page 7
+
+ return hex.EncodeToString(uuid), nil
+}
View
@@ -0,0 +1,31 @@
+package uuid
+
+import (
+ "testing"
+)
+
+func TestUUID(t *testing.T) {
+ uuid, err := GenUUID()
+ if err != nil {
+ t.Fatalf("GenUUID error %s",err)
+ }
+ t.Logf("uuid[%s]\n",uuid)
+}
+
+func BenchmarkUUID(b *testing.B) {
+ m := make(map[string]int,1000)
+ for i := 0; i < b.N; i++ {
+ uuid, err := GenUUID()
+ if err != nil {
+ b.Fatalf("GenUUID error %s",err)
+ }
+ b.StopTimer()
+ c := m[uuid]
+ if c > 0 {
+ b.Fatalf("duplicate uuid[%s] count %d",uuid,c )
+ }
+ m[uuid] = c+1
+ b.StartTimer()
+ }
+}
+

0 comments on commit ee12a19

Please sign in to comment.