// Negotiate inspects the Accept header of the provided request and determines // what the most appropriate response type should be. Defaults to HAL. func Negotiate(ctx context.Context, r *http.Request) string { alternatives := []string{MimeHal, MimeJSON, MimeEventStream} accept := r.Header.Get("Accept") if accept == "" { return MimeHal } result := goautoneg.Negotiate(r.Header.Get("Accept"), alternatives) log.WithFields(ctx, logrus.Fields{ "content_type": result, "accept": accept, }).Debug("Negotiated content type") return result }
// Handler function for /bid/<bidid> func handleRenderBid(w http.ResponseWriter, r *http.Request) { bidId := r.URL.Path[5:] if r.Method == "GET" { acceptable := []string{"text/html", "application/json"} contentType := goautoneg.Negotiate(r.Header.Get("Accept"), acceptable) if contentType == "" { http.Error(w, fmt.Sprintf("No accepted content type found. Supported: %v", acceptable), http.StatusNotAcceptable) return } c := appengine.NewContext(r) bid, err := db.GetBid(c, bidId) if err != nil { http.Error(w, "Bid not found: "+bidId, http.StatusNotFound) c.Warningf("Non-existing bid queried: '%v'", bidId) return } // ETag handling using status and content-type etag := fmt.Sprintf("\"s%v-c%v\"", bid.State, len(contentType)) if cachedEtag := r.Header.Get("If-None-Match"); cachedEtag == etag { w.Header().Del("Content-Type") w.WriteHeader(http.StatusNotModified) return } w.Header().Set("X-ETag", etag) w.Header().Set("ETag", etag) w.Header().Set("Content-Type", contentType) if contentType == "application/json" { err = renderBidJson(w, bidId, bid) } else { err = renderBidHtml(w, bidId, bid) } if err != nil { c.Errorf("Error rendering %v as %v: %v", r.URL, contentType, err) } } else { w.WriteHeader(http.StatusMethodNotAllowed) } }
// Handler function for /account/<accountId> func handleAccount(w http.ResponseWriter, r *http.Request) { accountId := r.URL.Path[9:] if r.Method == "GET" { acceptable := []string{"text/html", "application/json"} contentType := goautoneg.Negotiate(r.Header.Get("Accept"), acceptable) if contentType == "" { http.Error(w, fmt.Sprintf("No accepted content type found. Supported: %v", acceptable), http.StatusNotAcceptable) return } if err := checkBitcoinAddress(accountId); err != nil { http.Error(w, err.Error(), http.StatusNotFound) return } c := appengine.NewContext(r) dao := db.NewGaeAccountingDao(c) var err error account, err := dao.GetAccount(accountId) if err != nil { http.Error(w, "Error retrieving account", http.StatusInternalServerError) c.Errorf("Error getting account %v: %v", accountId, err) return } w.Header().Set("Content-Type", contentType) if contentType == "application/json" { err = renderAccountJson(w, &account) } else { err = renderAccountHtml(w, &account) } if err != nil { http.Error(w, "Error rendering account", http.StatusInternalServerError) c.Errorf("Error rendering %v as %v: %v", r.URL, contentType, err) } } else { w.WriteHeader(http.StatusMethodNotAllowed) } }
func (self *SerializerWrapper) newRequestContext(ctx context.Context, rctx *controller.RequestContext) (*requestContext, error) { ctype := strings.Split(rctx.Header("Content-Type"), ";")[0] ctype = strings.Trim(ctype, " ") if ctype == "" { ctype = self.consumes[0] } else { var matched bool for _, consume_type := range self.consumes { if ctype == consume_type { matched = true } break } if !matched { return nil, fmt.Errorf( "No support for Content-Type media type: %s", ctype, ) } } accept := rctx.Header("Accept") var atype string if accept == "" { atype = self.produces[0] } else { atype = goautoneg.Negotiate(accept, self.produces) if atype == "" { return nil, fmt.Errorf( "No support for Accept media type(s): %s", atype, ) } } return &requestContext{ rctx: rctx, deserializer: serializers[ctype], serializer: serializers[atype], }, nil }
// Handler function for /ledger/<accountMovementKey> func handleAccountMovement(w http.ResponseWriter, r *http.Request) { movementKey := r.URL.Path[8:] if r.Method == "GET" { acceptable := []string{"text/html", "application/json"} contentType := goautoneg.Negotiate(r.Header.Get("Accept"), acceptable) if contentType == "" { http.Error(w, fmt.Sprintf("No accepted content type found. Supported: %v", acceptable), http.StatusNotAcceptable) return } c := appengine.NewContext(r) dao := db.NewGaeAccountingDao(c, false) var err error movement, err := dao.GetMovement(movementKey) if err == bitwrk.ErrNoSuchObject { http.Error(w, "No such ledger entry", http.StatusNotFound) return } else if err != nil { http.Error(w, "Error retrieving ledger entry", http.StatusInternalServerError) c.Errorf("Error getting account movement %v: %v", movementKey, err) return } w.Header().Set("Content-Type", contentType) if contentType == "application/json" { err = renderAccountMovementJson(w, &movement) } else { err = renderAccountMovementHtml(w, &movement) } if err != nil { http.Error(w, "Error rendering ledger entry", http.StatusInternalServerError) c.Errorf("Error rendering account movement %v as %v: %v", r.URL, contentType, err) } } else { w.WriteHeader(http.StatusMethodNotAllowed) } }
func (me *HTTPResponseWrapper) NegotiateAndCallHandler() { accept := me.req.Header.Get(internalAcceptHeader) if len(accept) == 0 { accept = me.req.Header.Get(http.CanonicalHeaderKey("Accept")) } debugf("Looking up handler for Accept: %q", accept) debugf("Available content type handlers: %v", me.handledContentTypes) negotiated := goautoneg.Negotiate(accept, me.handledContentTypes) if len(negotiated) == 0 { me.err = http406 return } handlerFunc, ok := me.contentTypeHandlers[negotiated] if ok { debugf("Calling handler %v for negotiated content type %q", handlerFunc, negotiated) handlerFunc(me) } }
// Handler function for /deposit/<uid> func handleRenderDeposit(w http.ResponseWriter, r *http.Request) { uid := r.URL.Path[9:] if r.Method != "GET" { http.Error(w, "Only GET allowed", http.StatusMethodNotAllowed) return } acceptable := []string{"text/html", "application/json"} contentType := goautoneg.Negotiate(r.Header.Get("Accept"), acceptable) if contentType == "" { http.Error(w, fmt.Sprintf("No accepted content type found. Supported: %v", acceptable), http.StatusNotAcceptable) return } c := appengine.NewContext(r) dao := db.NewGaeAccountingDao(c, false) deposit, err := dao.GetDeposit(uid) if err != nil { http.Error(w, "Deposit not found: "+uid, http.StatusNotFound) c.Warningf("Non-existing deposit queried: '%v'", uid) return } w.Header().Set("Content-Type", contentType) if contentType == "application/json" { err = renderDepositJson(w, deposit) } else { err = renderDepositHtml(w, uid, deposit) } if err != nil { c.Errorf("Error rendering %v as %v: %v", r.URL, contentType, err) } }
// Handler function for /account/<accountId> func handleAccount(w http.ResponseWriter, r *http.Request) { accountId := r.URL.Path[9:] if r.Method == "GET" { acceptable := []string{"text/html", "application/json"} contentType := goautoneg.Negotiate(r.Header.Get("Accept"), acceptable) if contentType == "" { http.Error(w, fmt.Sprintf("No accepted content type found. Supported: %v", acceptable), http.StatusNotAcceptable) return } if err := checkBitcoinAddress(accountId); err != nil { http.Error(w, err.Error(), http.StatusNotFound) return } c := appengine.NewContext(r) dao := db.NewGaeAccountingDao(c, false) var err error account, err := dao.GetAccount(accountId) if err != nil { http.Error(w, "Error retrieving account", http.StatusInternalServerError) c.Errorf("Error getting account %v: %v", accountId, err) return } w.Header().Set("Content-Type", contentType) if contentType == "application/json" { err = renderAccountJson(w, &account) } else { devmode := r.FormValue("developermode") != "" err = renderAccountHtml(w, &account, devmode) } if err != nil { http.Error(w, "Error rendering account", http.StatusInternalServerError) c.Errorf("Error rendering %v as %v: %v", r.URL, contentType, err) } } else if r.Method == "POST" { c := appengine.NewContext(r) c.Infof("Got POST for account: %v", accountId) action := r.FormValue("action") if action == "storedepositinfo" { if err := storeDepositInfo(c, r, accountId); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) } else { http.Redirect(w, r, r.RequestURI, http.StatusSeeOther) } } else if action == "requestdepositaddress" { if err := requestDepositAddress(c, r, accountId); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) } else { http.Redirect(w, r, r.RequestURI, http.StatusSeeOther) } } else { http.Error(w, "invalid action: "+action, http.StatusInternalServerError) } } else { http.Error(w, "Method not allowed: "+r.Method, http.StatusMethodNotAllowed) } }
// Handler for /tx/* URLs func handleTx(w http.ResponseWriter, r *http.Request) { if r.Method != "GET" && r.Method != "POST" { w.WriteHeader(http.StatusMethodNotAllowed) return } acceptable := []string{"text/html", "application/json"} contentType := goautoneg.Negotiate(r.Header.Get("Accept"), acceptable) if contentType == "" { http.Error(w, fmt.Sprintf("No accepted content type found. Supported: %v", acceptable), http.StatusNotAcceptable) return } c := appengine.NewContext(r) txId := r.URL.Path[4:] txKey, err := datastore.DecodeKey(txId) if err != nil { c.Warningf("Illegal tx id queried: '%v'", txId) http.Error(w, "Transaction not found: "+txId, http.StatusNotFound) return } var tx *bitwrk.Transaction var messages []bitwrk.Tmessage if r.Method == "POST" { err = updateTransaction(c, r, txId, txKey) if err != nil { message := fmt.Sprintf("Couldn't update transaction %#v: %v", txId, err) c.Warningf("%v", message) http.Error(w, message, http.StatusInternalServerError) } else { redirectToTransaction(txId, w, r) } return } // GET only tx, err = db.GetTransaction(c, txKey) if err != nil { c.Warningf("Datastore lookup failed for tx id: '%v'", txId) c.Warningf("Reason: %v", err) http.Error(w, "Transaction not found: "+txId, http.StatusNotFound) return } // ETag handling using transaction's revision number and content type etag := fmt.Sprintf("\"r%v-c%v\"", tx.Revision, len(contentType)) if cachedEtag := r.Header.Get("If-None-Match"); cachedEtag == etag { w.Header().Del("Content-Type") w.WriteHeader(http.StatusNotModified) return } messages, _ = db.GetTransactionMessages(c, txKey) w.Header().Set("Content-Type", contentType) w.Header().Set("ETag", etag) w.Header().Set("X-ETag", etag) if contentType == "application/json" { err = renderTxJson(w, txId, tx) } else { err = renderTxHtml(w, txId, tx, messages) } if err != nil { c.Errorf("Error rendering %v as %v: %v", r.URL, contentType, err) } }
func speechHandler(resp http.ResponseWriter, req *http.Request) error { msg := req.FormValue("text") if msg == "" { return &httpError{status: http.StatusBadRequest, err: errors.New("missing `text` parameter")} } // name match is highest priority, followed by gender/locale match, and then fallback var voiceSpec *mactts.VoiceSpec voiceName := req.FormValue("voice") if voiceName != "" { v := voices.FindByName(voiceName) if v == nil { return &httpError{status: http.StatusNotFound, err: fmt.Errorf("voice `%s` not found", voiceName)} } voiceSpec = v.Spec() } else { var gender mactts.Gender switch req.FormValue("gender") { case "": gender = mactts.GenderNil case "male": gender = mactts.GenderMale case "neuter": gender = mactts.GenderNeuter case "female": gender = mactts.GenderFemale default: return &httpError{status: http.StatusBadRequest, err: fmt.Errorf("invalid `gender` parameter")} } locale := req.FormValue("lang") if gender != mactts.GenderNil || locale != "" { v := voices.Match(gender, locale) if v == nil { return &httpError{status: http.StatusNotFound, err: errors.New("cannot find voice with specified gender and/or language")} } voiceSpec = v.Spec() } } // finally if no matcher hits, try to load a universally available default if voiceSpec == nil { v := voices.FindByName("Fred") if v == nil { return &httpError{status: http.StatusNotFound, err: errors.New("unable to find suitable voice")} } voiceSpec = v.Spec() } var sampleRate int switch req.FormValue("samplerate") { case "8000": sampleRate = 8000 case "11025": sampleRate = 11025 case "16000": sampleRate = 16000 case "", "22050": sampleRate = 22050 case "32000": sampleRate = 32000 case "44100": sampleRate = 44100 case "48000": sampleRate = 48000 default: return &httpError{status: http.StatusBadRequest, err: errors.New("invalid `samplerate` parameter")} } // default speaking rates vary for each voice. Retreiving them during initialization would be possible, but painful // and probably unnecessary. A rate of 0 means default. var rate uint16 if p := req.FormValue("rate"); p != "" { pv, err := strconv.ParseUint(p, 10, 16) if err != nil { return &httpError{status: http.StatusBadRequest, err: errors.New("invalid `rate` parameter")} } rate = uint16(pv) } var pitch float64 if p := req.FormValue("pitch"); p != "" { var err error pitch, err = strconv.ParseFloat(p, 64) if err != nil || math.IsNaN(pitch) || pitch < 0 || pitch > 4096.0 { return &httpError{status: http.StatusBadRequest, err: errors.New("invalid `pitch` parameter")} } } if req.Method == "POST" { attachmentName := req.FormValue("attachment") if attachmentName != "" { resp.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", attachmentName)) } } f := resp.(*ResponseBuffer) acceptMimeType := req.FormValue("type") if acceptMimeType == "" { acceptMimeType = req.Header.Get("Accept") } acceptType := goautoneg.Negotiate(acceptMimeType, []string{ "audio/wave", "audio/wav", "audio/x-wav", "audio/vnd.wav", "audio/mp4", }) responseType := "audio/wav" newFileFunc := mactts.NewOutputWAVEFile if acceptType == "audio/mp4" { responseType = "audio/mp4" newFileFunc = mactts.NewOutputAACFile } resp.Header().Set("Content-Type", responseType) if *useEtag { // compute the Etag etagBuf := make([]byte, 18, 48+len(msg)) binary.BigEndian.PutUint32(etagBuf, etagGeneration) binary.BigEndian.PutUint32(etagBuf[4:], uint32(sampleRate)) binary.BigEndian.PutUint16(etagBuf[8:], rate) binary.BigEndian.PutUint64(etagBuf[10:], math.Float64bits(pitch)) vsb, _ := voiceSpec.MarshalBinary() etagBuf = append(etagBuf, vsb...) etagBuf = append(etagBuf, responseType...) etagBuf = append(etagBuf, msg...) etagSum := md5.New() etagSum.Write(etagBuf) etag := hex.EncodeToString(etagSum.Sum(nil)) resp.Header().Set("Etag", etag) if done := checkEtagDone(req, etag); done { resp.WriteHeader(http.StatusNotModified) return nil } } af, err := newFileFunc(f, float64(sampleRate), 1, 16) if err != nil { return err } defer af.Close() eaf, err := af.ExtAudioFile() if err != nil { return err } defer eaf.Close() sc, err := mactts.NewChannel(voiceSpec) if err != nil { return err } defer sc.Close() if rate != 0 { if err := sc.SetRate(int(rate)); err != nil { return err } } if pitch != 0.0 { if err := sc.SetPitchBase(pitch); err != nil { return err } } if err = sc.SetExtAudioFile(eaf); err != nil { return err } done := make(chan int) err = sc.SetDone(func() { done <- 1 close(done) }) if err != nil { return err } if err = sc.SpeakString(msg); err != nil { return nil } select { case <-done: case <-time.After(1 * time.Minute): return errors.New("timed out synthesizing speech") } return nil }