Beispiel #1
0
// 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
}
Beispiel #2
0
// 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)
	}
}
Beispiel #3
0
// 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
}
Beispiel #5
0
// 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)
	}
}
Beispiel #6
0
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)
	}
}
Beispiel #7
0
// 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)
	}
}
Beispiel #8
0
// 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)
	}
}
Beispiel #10
0
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
}