/** * This file provided by Facebook is for non-commercial testing and evaluation * purposes only. Facebook reserves all rights not expressly granted. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ package main import ( "bytes" "encoding/json" "fmt" "io" "io/ioutil" "log" "net/http" "os" "sync" "time" ) type comment struct { ID int64 `json:"id"` Author string `json:"author"` Text string `json:"text"` } const dataFile = "./comments.json" var commentMutex = new(sync.Mutex) func updateComments(w http.ResponseWriter, r *http.Request, commentData []byte, fi os.FileInfo) { // Decode the JSON data var comments []comment if err := json.Unmarshal(commentData, &comments); err != nil { http.Error(w, fmt.Sprintf("Unable to Unmarshal comments from data file (%s): %s", dataFile, err), http.StatusInternalServerError) return } // Add a new comment to the in memory slice of comments comments = append(comments, comment{ID: time.Now().UnixNano() / 1000000, Author: r.FormValue("author"), Text: r.FormValue("text")}) // Marshal the comments to indented json. commentData, err := json.MarshalIndent(comments, "", " ") if err != nil { http.Error(w, fmt.Sprintf("Unable to marshal comments to json: %s", err), http.StatusInternalServerError) return } // Write out the comments to the file, preserving permissions. err = ioutil.WriteFile(dataFile, commentData, fi.Mode()) if err != nil { http.Error(w, fmt.Sprintf("Unable to write comments to data file (%s): %s", dataFile, err), http.StatusInternalServerError) return } w.Header().Set("Content-Type", "application/json") w.Header().Set("Cache-Control", "no-cache") io.Copy(w, bytes.NewReader(commentData)) } // Handle comments func handleComments(w http.ResponseWriter, r *http.Request) { // Since multiple requests could come in at once, ensure we have a lock // around all file operations commentMutex.Lock() defer commentMutex.Unlock() // Stat the file, so we can find its current permissions fi, err := os.Stat(dataFile) if err != nil { http.Error(w, fmt.Sprintf("Unable to stat the data file (%s): %s", dataFile, err), http.StatusInternalServerError) return } // Read the comments from the file. commentData, err := ioutil.ReadFile(dataFile) if err != nil { http.Error(w, fmt.Sprintf("Unable to read the data file (%s): %s", dataFile, err), http.StatusInternalServerError) return } switch r.Method { case "POST": updateComments(w, r, commentData, fi); case "GET": w.Header().Set("Content-Type", "application/json") w.Header().Set("Cache-Control", "no-cache") // stream the contents of the file to the response io.Copy(w, bytes.NewReader(commentData)) default: // Don't know the method, so error. http.Error(w, fmt.Sprintf("Unsupported method: %s", r.Method), http.StatusMethodNotAllowed) } } func main() { port := os.Getenv("PORT") if port == "" { port = "3000" } http.HandleFunc("/api/comments", handleComments) http.Handle("/", http.FileServer(http.Dir("./public"))) log.Println("Server started: http://localhost:" + port) log.Fatal(http.ListenAndServe(":" + port, nil)) }