Example #1
0
// HTTP handler for a POST to _bulk_get
// Request looks like POST /db/_bulk_get?revs=___&attachments=___
// where the boolean ?revs parameter adds a revision history to each doc
// and the boolean ?attachments parameter includes attachment bodies.
// The body of the request is JSON and looks like:
// {
//   "docs": [
//		{"id": "docid", "rev": "revid", "atts_since": [12,...]}, ...
// 	 ]
// }
func (h *handler) handleBulkGet() error {
	includeRevs := h.getBoolQuery("revs")
	includeAttachments := h.getBoolQuery("attachments")
	body, err := h.readJSON()
	if err != nil {
		return err
	}

	err = h.writeMultipart(func(writer *multipart.Writer) error {
		for _, item := range body["docs"].([]interface{}) {
			doc := item.(map[string]interface{})
			docid, _ := doc["id"].(string)
			revid := ""
			revok := true
			if doc["rev"] != nil {
				revid, revok = doc["rev"].(string)
			}
			if docid == "" || !revok {
				return base.HTTPErrorf(http.StatusBadRequest, "Invalid doc/rev ID")
			}

			var attsSince []string = nil
			if includeAttachments {
				if doc["atts_since"] != nil {
					raw, ok := doc["atts_since"].([]interface{})
					if ok {
						attsSince = make([]string, len(raw))
						for i := 0; i < len(raw); i++ {
							attsSince[i], ok = raw[i].(string)
							if !ok {
								break
							}
						}
					}
					if !ok {
						return base.HTTPErrorf(http.StatusBadRequest, "Invalid atts_since")
					}
				} else {
					attsSince = []string{}
				}
			}

			body, err := h.db.GetRev(docid, revid, includeRevs, attsSince)
			if err != nil {
				status, reason := base.ErrorAsHTTPStatus(err)
				errStr := base.CouchHTTPErrorName(status)
				body = db.Body{"id": docid, "error": errStr, "reason": reason, "status": status}
				if revid != "" {
					body["rev"] = revid
				}
			}

			h.db.WriteRevisionAsPart(body, err != nil, writer)
		}
		return nil
	})

	return err
}
Example #2
0
// HTTP handler for a POST to _bulk_docs
func (h *handler) handleBulkDocs() error {
	body, err := h.readJSON()
	if err != nil {
		return err
	}
	newEdits, ok := body["new_edits"].(bool)
	if !ok {
		newEdits = true
	}

	docs := body["docs"].([]interface{})
	h.db.ReserveSequences(uint64(len(docs)))

	result := make([]db.Body, 0, len(docs))
	for _, item := range docs {
		doc := item.(map[string]interface{})
		docid, _ := doc["_id"].(string)
		var err error
		var revid string
		if newEdits {
			if docid != "" {
				revid, err = h.db.Put(docid, doc)
			} else {
				docid, revid, err = h.db.Post(doc)
			}
		} else {
			revisions := db.ParseRevisions(doc)
			if revisions == nil {
				err = &base.HTTPError{http.StatusBadRequest, "Bad _revisions"}
			} else {
				revid = revisions[0]
				err = h.db.PutExistingRev(docid, doc, revisions)
			}
		}

		status := db.Body{}
		if docid != "" {
			status["id"] = docid
		}
		if err != nil {
			code, msg := base.ErrorAsHTTPStatus(err)
			status["status"] = code
			status["error"] = base.CouchHTTPErrorName(code)
			status["reason"] = msg
			base.Log("\tBulkDocs: Doc %q --> %v", docid, err)
			err = nil // wrote it to output already; not going to return it
		} else {
			status["rev"] = revid
		}
		result = append(result, status)
	}

	h.writeJSONStatus(http.StatusCreated, result)
	return nil
}
Example #3
0
// HTTP handler for a POST to _bulk_get
func (h *handler) handleBulkGet() error {
	includeRevs := h.getBoolQuery("revs")
	includeAttachments := h.getBoolQuery("attachments")
	body, err := h.readJSON()
	if err != nil {
		return err
	}

	h.setHeader("Content-Type", "application/json")
	h.response.Write([]byte(`[`))

	for i, item := range body["docs"].([]interface{}) {
		doc := item.(map[string]interface{})
		docid, _ := doc["id"].(string)
		revid := ""
		revok := true
		if doc["rev"] != nil {
			revid, revok = doc["rev"].(string)
		}
		if docid == "" || !revok {
			return &base.HTTPError{http.StatusBadRequest, "Invalid doc/rev ID"}
		}

		var attsSince []string = nil
		if includeAttachments {
			if doc["atts_since"] != nil {
				raw, ok := doc["atts_since"].([]interface{})
				if ok {
					attsSince = make([]string, len(raw))
					for i := 0; i < len(raw); i++ {
						attsSince[i], ok = raw[i].(string)
						if !ok {
							break
						}
					}
				}
				if !ok {
					return &base.HTTPError{http.StatusBadRequest, "Invalid atts_since"}
				}
			} else {
				attsSince = []string{}
			}
		}

		body, err := h.db.GetRev(docid, revid, includeRevs, attsSince)
		if err != nil {
			status, reason := base.ErrorAsHTTPStatus(err)
			errStr := base.CouchHTTPErrorName(status)
			body = db.Body{"id": docid, "error": errStr, "reason": reason, "status": status}
			if revid != "" {
				body["rev"] = revid
			}
		}

		if i > 0 {
			h.response.Write([]byte(",\n"))
		}
		h.addJSON(body)
	}

	h.response.Write([]byte(`]`))
	return nil
}
Example #4
0
// HTTP handler for a POST to _bulk_get
// Request looks like POST /db/_bulk_get?revs=___&attachments=___
// where the boolean ?revs parameter adds a revision history to each doc
// and the boolean ?attachments parameter includes attachment bodies.
// The body of the request is JSON and looks like:
// {
//   "docs": [
//		{"id": "docid", "rev": "revid", "atts_since": [12,...]}, ...
// 	 ]
// }
func (h *handler) handleBulkGet() error {
	includeRevs := h.getBoolQuery("revs")
	includeAttachments := h.getBoolQuery("attachments")
	canCompress := strings.Contains(h.rq.Header.Get("X-Accept-Part-Encoding"), "gzip")
	body, err := h.readJSON()
	if err != nil {
		return err
	}

	err = h.writeMultipart(func(writer *multipart.Writer) error {
		for _, item := range body["docs"].([]interface{}) {
			var body db.Body
			var attsSince []string
			var err error

			doc := item.(map[string]interface{})
			docid, _ := doc["id"].(string)
			revid := ""
			revok := true
			if doc["rev"] != nil {
				revid, revok = doc["rev"].(string)
			}
			if docid == "" || !revok {
				err = base.HTTPErrorf(http.StatusBadRequest, "Invalid doc/rev ID in _bulk_get")
			} else {
				if includeAttachments {
					if doc["atts_since"] != nil {
						raw, ok := doc["atts_since"].([]interface{})
						if ok {
							attsSince = make([]string, len(raw))
							for i := 0; i < len(raw); i++ {
								attsSince[i], ok = raw[i].(string)
								if !ok {
									break
								}
							}
						}
						if !ok {
							err = base.HTTPErrorf(http.StatusBadRequest, "Invalid atts_since")
						}
					} else {
						attsSince = []string{}
					}
				}
			}

			if err == nil {
				body, err = h.db.GetRev(docid, revid, includeRevs, attsSince)
			}

			if err != nil {
				// Report error in the response for this doc:
				status, reason := base.ErrorAsHTTPStatus(err)
				errStr := base.CouchHTTPErrorName(status)
				body = db.Body{"id": docid, "error": errStr, "reason": reason, "status": status}
				if revid != "" {
					body["rev"] = revid
				}
			}

			h.db.WriteRevisionAsPart(body, err != nil, canCompress, writer)
		}
		return nil
	})

	return err
}
Example #5
0
// HTTP handler for _all_docs
func (h *handler) handleAllDocs() error {
	// http://wiki.apache.org/couchdb/HTTP_Bulk_Document_API
	includeDocs := h.getBoolQuery("include_docs")
	includeChannels := h.getBoolQuery("channels")
	includeAccess := h.getBoolQuery("access") && h.user == nil
	includeRevs := h.getBoolQuery("revs")
	includeSeqs := h.getBoolQuery("update_seq")

	// Get the doc IDs if this is a POST request:
	var explicitDocIDs []string
	if h.rq.Method == "POST" {
		input, err := h.readJSON()
		if err == nil {
			keys, ok := input["keys"].([]interface{})
			explicitDocIDs = make([]string, len(keys))
			for i := 0; i < len(keys); i++ {
				if explicitDocIDs[i], ok = keys[i].(string); !ok {
					break
				}
			}
			if !ok {
				err = base.HTTPErrorf(http.StatusBadRequest, "Bad/missing keys")
			}
		}
	}

	// Get the set of channels the user has access to; nil if user is admin or has access to "*"
	var availableChannels channels.TimedSet
	if h.user != nil {
		availableChannels = h.user.InheritedChannels()
		if availableChannels == nil {
			panic("no channels for user?")
		}
		if availableChannels.Contains("*") {
			availableChannels = nil
		}
	}

	// Subroutines that filter a channel list down to the ones that the user has access to:
	filterChannels := func(channels []string) []string {
		if availableChannels == nil {
			return channels
		}
		dst := 0
		for _, ch := range channels {
			if availableChannels.Contains(ch) {
				channels[dst] = ch
				dst++
			}
		}
		if dst == 0 {
			return nil
		}
		return channels[0:dst]
	}
	filterChannelSet := func(channelMap channels.ChannelMap) []string {
		var result []string
		if availableChannels == nil {
			result = []string{}
		}
		for ch, _ := range channelMap {
			if availableChannels == nil || availableChannels.Contains(ch) {
				result = append(result, ch)
			}
		}
		return result
	}

	type allDocsRowValue struct {
		Rev      string              `json:"rev"`
		Channels []string            `json:"channels,omitempty"`
		Access   map[string]base.Set `json:"access,omitempty"` // for admins only
	}
	type allDocsRow struct {
		Key       string           `json:"key"`
		ID        string           `json:"id,omitempty"`
		Value     *allDocsRowValue `json:"value,omitempty"`
		Doc       db.Body          `json:"doc,omitempty"`
		UpdateSeq uint64           `json:"update_seq,omitempty"`
		Error     string           `json:"error,omitempty"`
		Status    int              `json:"status,omitempty"`
	}

	// Subroutine that creates a response row for a document:
	totalRows := 0
	createRow := func(doc db.IDAndRev, channels []string) *allDocsRow {
		row := &allDocsRow{Key: doc.DocID}
		value := allDocsRowValue{}

		// Filter channels to ones available to user, and bail out if inaccessible:
		if explicitDocIDs == nil {
			if channels = filterChannels(channels); channels == nil {
				return nil // silently skip this doc
			}
		}

		if explicitDocIDs != nil || includeDocs || includeAccess {
			// Fetch the document body and other metadata that lives with it:
			body, channelSet, access, roleAccess, err := h.db.GetRevAndChannels(doc.DocID, doc.RevID, includeRevs)
			if err != nil {
				row.Status, _ = base.ErrorAsHTTPStatus(err)
				return row
			} else if body["_removed"] != nil {
				row.Status = http.StatusForbidden
				return row
			}
			if explicitDocIDs != nil {
				if channels = filterChannelSet(channelSet); channels == nil {
					row.Status = http.StatusForbidden
					return row
				}
				doc.RevID = body["_rev"].(string)
			}
			if includeDocs {
				row.Doc = body
			}
			if includeAccess && (access != nil || roleAccess != nil) {
				value.Access = map[string]base.Set{}
				for userName, channels := range access {
					value.Access[userName] = channels.AsSet()
				}
				for roleName, channels := range roleAccess {
					value.Access["role:"+roleName] = channels.AsSet()
				}
			}
		}

		row.Value = &value
		row.ID = doc.DocID
		value.Rev = doc.RevID
		if includeSeqs {
			row.UpdateSeq = doc.Sequence
		}
		if includeChannels {
			row.Value.Channels = channels
		}
		return row
	}

	// Subroutine that writes a response entry for a document:
	writeDoc := func(doc db.IDAndRev, channels []string) error {
		row := createRow(doc, channels)
		if row != nil {
			if row.Status >= 300 {
				row.Error = base.CouchHTTPErrorName(row.Status)
			}
			if totalRows > 0 {
				h.response.Write([]byte(","))
			}
			totalRows++
			h.addJSON(row)
		}
		return nil
	}

	// Now it's time to actually write the response!
	lastSeq, _ := h.db.LastSequence()
	h.setHeader("Content-Type", "application/json")
	h.response.Write([]byte(`{"rows":[` + "\n"))

	if explicitDocIDs != nil {
		for _, docID := range explicitDocIDs {
			writeDoc(db.IDAndRev{DocID: docID, RevID: "", Sequence: 0}, nil)
		}
	} else {
		if err := h.db.ForEachDocID(writeDoc); err != nil {
			return err
		}
	}

	h.response.Write([]byte(fmt.Sprintf("],\n"+`"total_rows":%d,"update_seq":%d}`,
		totalRows, lastSeq)))
	return nil
}