github.com/leonlxy/hyperledger@v1.0.0-alpha.0.20170427033203-34922035d248/core/ledger/util/couchdb/couchdb.go (about)

     1  /*
     2  Copyright IBM Corp. 2016, 2017 All Rights Reserved.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8  		 http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package couchdb
    18  
    19  import (
    20  	"bytes"
    21  	"compress/gzip"
    22  	"encoding/base64"
    23  	"encoding/json"
    24  	"fmt"
    25  	"io"
    26  	"io/ioutil"
    27  	"log"
    28  	"mime"
    29  	"mime/multipart"
    30  	"net/http"
    31  	"net/http/httputil"
    32  	"net/textproto"
    33  	"net/url"
    34  	"regexp"
    35  	"strconv"
    36  	"strings"
    37  	"time"
    38  	"unicode/utf8"
    39  
    40  	"github.com/hyperledger/fabric/common/flogging"
    41  	logging "github.com/op/go-logging"
    42  )
    43  
    44  var logger = flogging.MustGetLogger("couchdb")
    45  
    46  //time between retry attempts in milliseconds
    47  const retryWaitTime = 125
    48  
    49  // DBOperationResponse is body for successful database calls.
    50  type DBOperationResponse struct {
    51  	Ok  bool
    52  	id  string
    53  	rev string
    54  }
    55  
    56  // DBInfo is body for database information.
    57  type DBInfo struct {
    58  	DbName    string `json:"db_name"`
    59  	UpdateSeq string `json:"update_seq"`
    60  	Sizes     struct {
    61  		File     int `json:"file"`
    62  		External int `json:"external"`
    63  		Active   int `json:"active"`
    64  	} `json:"sizes"`
    65  	PurgeSeq int `json:"purge_seq"`
    66  	Other    struct {
    67  		DataSize int `json:"data_size"`
    68  	} `json:"other"`
    69  	DocDelCount       int    `json:"doc_del_count"`
    70  	DocCount          int    `json:"doc_count"`
    71  	DiskSize          int    `json:"disk_size"`
    72  	DiskFormatVersion int    `json:"disk_format_version"`
    73  	DataSize          int    `json:"data_size"`
    74  	CompactRunning    bool   `json:"compact_running"`
    75  	InstanceStartTime string `json:"instance_start_time"`
    76  }
    77  
    78  //ConnectionInfo is a structure for capturing the database info and version
    79  type ConnectionInfo struct {
    80  	Couchdb string `json:"couchdb"`
    81  	Version string `json:"version"`
    82  	Vendor  struct {
    83  		Name string `json:"name"`
    84  	} `json:"vendor"`
    85  }
    86  
    87  //RangeQueryResponse is used for processing REST range query responses from CouchDB
    88  type RangeQueryResponse struct {
    89  	TotalRows int `json:"total_rows"`
    90  	Offset    int `json:"offset"`
    91  	Rows      []struct {
    92  		ID    string `json:"id"`
    93  		Key   string `json:"key"`
    94  		Value struct {
    95  			Rev string `json:"rev"`
    96  		} `json:"value"`
    97  		Doc json.RawMessage `json:"doc"`
    98  	} `json:"rows"`
    99  }
   100  
   101  //QueryResponse is used for processing REST query responses from CouchDB
   102  type QueryResponse struct {
   103  	Warning string            `json:"warning"`
   104  	Docs    []json.RawMessage `json:"docs"`
   105  }
   106  
   107  //Doc is used for capturing if attachments are return in the query from CouchDB
   108  type Doc struct {
   109  	ID          string          `json:"_id"`
   110  	Rev         string          `json:"_rev"`
   111  	Attachments json.RawMessage `json:"_attachments"`
   112  }
   113  
   114  //DocID is a minimal structure for capturing the ID from a query result
   115  type DocID struct {
   116  	ID string `json:"_id"`
   117  }
   118  
   119  //QueryResult is used for returning query results from CouchDB
   120  type QueryResult struct {
   121  	ID          string
   122  	Value       []byte
   123  	Attachments []*Attachment
   124  }
   125  
   126  //CouchConnectionDef contains parameters
   127  type CouchConnectionDef struct {
   128  	URL                 string
   129  	Username            string
   130  	Password            string
   131  	MaxRetries          int
   132  	MaxRetriesOnStartup int
   133  	RequestTimeout      time.Duration
   134  }
   135  
   136  //CouchInstance represents a CouchDB instance
   137  type CouchInstance struct {
   138  	conf   CouchConnectionDef //connection configuration
   139  	client *http.Client       // a client to connect to this instance
   140  }
   141  
   142  //CouchDatabase represents a database within a CouchDB instance
   143  type CouchDatabase struct {
   144  	CouchInstance CouchInstance //connection configuration
   145  	DBName        string
   146  }
   147  
   148  //DBReturn contains an error reported by CouchDB
   149  type DBReturn struct {
   150  	StatusCode int    `json:"status_code"`
   151  	Error      string `json:"error"`
   152  	Reason     string `json:"reason"`
   153  }
   154  
   155  //Attachment contains the definition for an attached file for couchdb
   156  type Attachment struct {
   157  	Name            string
   158  	ContentType     string
   159  	Length          uint64
   160  	AttachmentBytes []byte
   161  }
   162  
   163  //DocMetadata returns the ID, version and revision for a couchdb document
   164  type DocMetadata struct {
   165  	ID      string
   166  	Rev     string
   167  	Version string
   168  }
   169  
   170  //FileDetails defines the structure needed to send an attachment to couchdb
   171  type FileDetails struct {
   172  	Follows     bool   `json:"follows"`
   173  	ContentType string `json:"content_type"`
   174  	Length      int    `json:"length"`
   175  }
   176  
   177  //CouchDoc defines the structure for a JSON document value
   178  type CouchDoc struct {
   179  	JSONValue   []byte
   180  	Attachments []*Attachment
   181  }
   182  
   183  //BatchRetrieveDocMedatadataResponse is used for processing REST batch responses from CouchDB
   184  type BatchRetrieveDocMedatadataResponse struct {
   185  	Rows []struct {
   186  		ID  string `json:"id"`
   187  		Doc struct {
   188  			ID      string `json:"_id"`
   189  			Rev     string `json:"_rev"`
   190  			Version string `json:"version"`
   191  		} `json:"doc"`
   192  	} `json:"rows"`
   193  }
   194  
   195  //BatchUpdateResponse defines a structure for batch update response
   196  type BatchUpdateResponse struct {
   197  	ID     string `json:"id"`
   198  	Error  string `json:"error"`
   199  	Reason string `json:"reason"`
   200  	Ok     bool   `json:"ok"`
   201  	Rev    string `json:"rev"`
   202  }
   203  
   204  //Base64Attachment contains the definition for an attached file for couchdb
   205  type Base64Attachment struct {
   206  	ContentType    string `json:"content_type"`
   207  	AttachmentData string `json:"data"`
   208  }
   209  
   210  // closeResponseBody discards the body and then closes it to enable returning it to
   211  // connection pool
   212  func closeResponseBody(resp *http.Response) {
   213  	io.Copy(ioutil.Discard, resp.Body) // discard whatever is remaining of body
   214  	resp.Body.Close()
   215  }
   216  
   217  //CreateConnectionDefinition for a new client connection
   218  func CreateConnectionDefinition(couchDBAddress, username, password string, maxRetries,
   219  	maxRetriesOnStartup int, requestTimeout time.Duration) (*CouchConnectionDef, error) {
   220  
   221  	logger.Debugf("Entering CreateConnectionDefinition()")
   222  
   223  	connectURL := &url.URL{
   224  		Host:   couchDBAddress,
   225  		Scheme: "http",
   226  	}
   227  
   228  	//parse the constructed URL to verify no errors
   229  	finalURL, err := url.Parse(connectURL.String())
   230  	if err != nil {
   231  		logger.Errorf("URL parse error: %s", err.Error())
   232  		return nil, err
   233  	}
   234  
   235  	logger.Debugf("Created database configuration  URL=[%s]", finalURL.String())
   236  	logger.Debugf("Exiting CreateConnectionDefinition()")
   237  
   238  	//return an object containing the connection information
   239  	return &CouchConnectionDef{finalURL.String(), username, password, maxRetries,
   240  		maxRetriesOnStartup, requestTimeout}, nil
   241  
   242  }
   243  
   244  //CreateDatabaseIfNotExist method provides function to create database
   245  func (dbclient *CouchDatabase) CreateDatabaseIfNotExist() (*DBOperationResponse, error) {
   246  
   247  	logger.Debugf("Entering CreateDatabaseIfNotExist()")
   248  
   249  	dbInfo, couchDBReturn, err := dbclient.GetDatabaseInfo()
   250  	if err != nil {
   251  		if couchDBReturn == nil || couchDBReturn.StatusCode != 404 {
   252  			return nil, err
   253  		}
   254  	}
   255  
   256  	if dbInfo == nil && couchDBReturn.StatusCode == 404 {
   257  
   258  		logger.Debugf("Database %s does not exist.", dbclient.DBName)
   259  
   260  		connectURL, err := url.Parse(dbclient.CouchInstance.conf.URL)
   261  		if err != nil {
   262  			logger.Errorf("URL parse error: %s", err.Error())
   263  			return nil, err
   264  		}
   265  		connectURL.Path = dbclient.DBName
   266  
   267  		//get the number of retries
   268  		maxRetries := dbclient.CouchInstance.conf.MaxRetries
   269  
   270  		//process the URL with a PUT, creates the database
   271  		resp, _, err := dbclient.CouchInstance.handleRequest(http.MethodPut, connectURL.String(), nil, "", "", maxRetries)
   272  		if err != nil {
   273  			return nil, err
   274  		}
   275  		defer closeResponseBody(resp)
   276  
   277  		//Get the response from the create REST call
   278  		dbResponse := &DBOperationResponse{}
   279  		json.NewDecoder(resp.Body).Decode(&dbResponse)
   280  
   281  		if dbResponse.Ok == true {
   282  			logger.Debugf("Created database %s ", dbclient.DBName)
   283  		}
   284  
   285  		logger.Debugf("Exiting CreateDatabaseIfNotExist()")
   286  
   287  		return dbResponse, nil
   288  
   289  	}
   290  
   291  	logger.Debugf("Database %s already exists", dbclient.DBName)
   292  
   293  	logger.Debugf("Exiting CreateDatabaseIfNotExist()")
   294  
   295  	return nil, nil
   296  
   297  }
   298  
   299  //GetDatabaseInfo method provides function to retrieve database information
   300  func (dbclient *CouchDatabase) GetDatabaseInfo() (*DBInfo, *DBReturn, error) {
   301  
   302  	connectURL, err := url.Parse(dbclient.CouchInstance.conf.URL)
   303  	if err != nil {
   304  		logger.Errorf("URL parse error: %s", err.Error())
   305  		return nil, nil, err
   306  	}
   307  	connectURL.Path = dbclient.DBName
   308  
   309  	//get the number of retries
   310  	maxRetries := dbclient.CouchInstance.conf.MaxRetries
   311  
   312  	resp, couchDBReturn, err := dbclient.CouchInstance.handleRequest(http.MethodGet, connectURL.String(), nil, "", "", maxRetries)
   313  	if err != nil {
   314  		return nil, couchDBReturn, err
   315  	}
   316  	defer closeResponseBody(resp)
   317  
   318  	dbResponse := &DBInfo{}
   319  	json.NewDecoder(resp.Body).Decode(&dbResponse)
   320  
   321  	// trace the database info response
   322  	if logger.IsEnabledFor(logging.DEBUG) {
   323  		dbResponseJSON, err := json.Marshal(dbResponse)
   324  		if err == nil {
   325  			logger.Debugf("GetDatabaseInfo() dbResponseJSON: %s", dbResponseJSON)
   326  		}
   327  	}
   328  
   329  	return dbResponse, couchDBReturn, nil
   330  
   331  }
   332  
   333  //VerifyCouchConfig method provides function to verify the connection information
   334  func (couchInstance *CouchInstance) VerifyCouchConfig() (*ConnectionInfo, *DBReturn, error) {
   335  
   336  	logger.Debugf("Entering VerifyCouchConfig()")
   337  	defer logger.Debugf("Exiting VerifyCouchConfig()")
   338  
   339  	connectURL, err := url.Parse(couchInstance.conf.URL)
   340  	if err != nil {
   341  		logger.Errorf("URL parse error: %s", err.Error())
   342  		return nil, nil, err
   343  	}
   344  	connectURL.Path = "/"
   345  
   346  	//get the number of retries for startup
   347  	maxRetriesOnStartup := couchInstance.conf.MaxRetriesOnStartup
   348  
   349  	resp, couchDBReturn, err := couchInstance.handleRequest(http.MethodGet, connectURL.String(), nil,
   350  		couchInstance.conf.Username, couchInstance.conf.Password, maxRetriesOnStartup)
   351  
   352  	if err != nil {
   353  		return nil, couchDBReturn, fmt.Errorf("Unable to connect to CouchDB, check the hostname and port: %s", err.Error())
   354  	}
   355  	defer closeResponseBody(resp)
   356  
   357  	dbResponse := &ConnectionInfo{}
   358  	errJSON := json.NewDecoder(resp.Body).Decode(&dbResponse)
   359  	if errJSON != nil {
   360  		return nil, nil, fmt.Errorf("Unable to connect to CouchDB, check the hostname and port: %s", errJSON.Error())
   361  	}
   362  
   363  	// trace the database info response
   364  	if logger.IsEnabledFor(logging.DEBUG) {
   365  		dbResponseJSON, err := json.Marshal(dbResponse)
   366  		if err == nil {
   367  			logger.Debugf("VerifyConnection() dbResponseJSON: %s", dbResponseJSON)
   368  		}
   369  	}
   370  
   371  	//check to see if the system databases exist
   372  	//Verifying the existence of the system database accomplishes two steps
   373  	//1.  Ensures the system databases are created
   374  	//2.  Verifies the username password provided in the CouchDB config are valid for system admin
   375  	err = CreateSystemDatabasesIfNotExist(*couchInstance)
   376  	if err != nil {
   377  		logger.Errorf("Unable to connect to CouchDB,  error: %s   Check the admin username and password.\n", err.Error())
   378  		return nil, nil, fmt.Errorf("Unable to connect to CouchDB,  error: %s   Check the admin username and password.\n", err.Error())
   379  	}
   380  
   381  	return dbResponse, couchDBReturn, nil
   382  }
   383  
   384  //DropDatabase provides method to drop an existing database
   385  func (dbclient *CouchDatabase) DropDatabase() (*DBOperationResponse, error) {
   386  
   387  	logger.Debugf("Entering DropDatabase()")
   388  
   389  	connectURL, err := url.Parse(dbclient.CouchInstance.conf.URL)
   390  	if err != nil {
   391  		logger.Errorf("URL parse error: %s", err.Error())
   392  		return nil, err
   393  	}
   394  	connectURL.Path = dbclient.DBName
   395  
   396  	//get the number of retries
   397  	maxRetries := dbclient.CouchInstance.conf.MaxRetries
   398  
   399  	resp, _, err := dbclient.CouchInstance.handleRequest(http.MethodDelete, connectURL.String(), nil, "", "", maxRetries)
   400  	if err != nil {
   401  		return nil, err
   402  	}
   403  	defer closeResponseBody(resp)
   404  
   405  	dbResponse := &DBOperationResponse{}
   406  	json.NewDecoder(resp.Body).Decode(&dbResponse)
   407  
   408  	if dbResponse.Ok == true {
   409  		logger.Debugf("Dropped database %s ", dbclient.DBName)
   410  	}
   411  
   412  	logger.Debugf("Exiting DropDatabase()")
   413  
   414  	if dbResponse.Ok == true {
   415  
   416  		return dbResponse, nil
   417  
   418  	}
   419  
   420  	return dbResponse, fmt.Errorf("Error dropping database")
   421  
   422  }
   423  
   424  // EnsureFullCommit calls _ensure_full_commit for explicit fsync
   425  func (dbclient *CouchDatabase) EnsureFullCommit() (*DBOperationResponse, error) {
   426  
   427  	logger.Debugf("Entering EnsureFullCommit()")
   428  
   429  	connectURL, err := url.Parse(dbclient.CouchInstance.conf.URL)
   430  	if err != nil {
   431  		logger.Errorf("URL parse error: %s", err.Error())
   432  		return nil, err
   433  	}
   434  	connectURL.Path = dbclient.DBName + "/_ensure_full_commit"
   435  
   436  	//get the number of retries
   437  	maxRetries := dbclient.CouchInstance.conf.MaxRetries
   438  
   439  	resp, _, err := dbclient.CouchInstance.handleRequest(http.MethodPost, connectURL.String(), nil, "", "", maxRetries)
   440  	if err != nil {
   441  		logger.Errorf("Failed to invoke _ensure_full_commit Error: %s\n", err.Error())
   442  		return nil, err
   443  	}
   444  	defer closeResponseBody(resp)
   445  
   446  	dbResponse := &DBOperationResponse{}
   447  	json.NewDecoder(resp.Body).Decode(&dbResponse)
   448  
   449  	if dbResponse.Ok == true {
   450  		logger.Debugf("_ensure_full_commit database %s ", dbclient.DBName)
   451  	}
   452  
   453  	logger.Debugf("Exiting EnsureFullCommit()")
   454  
   455  	if dbResponse.Ok == true {
   456  
   457  		return dbResponse, nil
   458  
   459  	}
   460  
   461  	return dbResponse, fmt.Errorf("Error syncing database")
   462  }
   463  
   464  //SaveDoc method provides a function to save a document, id and byte array
   465  func (dbclient *CouchDatabase) SaveDoc(id string, rev string, couchDoc *CouchDoc) (string, error) {
   466  
   467  	logger.Debugf("Entering SaveDoc()  id=[%s]", id)
   468  	if !utf8.ValidString(id) {
   469  		return "", fmt.Errorf("doc id [%x] not a valid utf8 string", id)
   470  	}
   471  
   472  	saveURL, err := url.Parse(dbclient.CouchInstance.conf.URL)
   473  	if err != nil {
   474  		logger.Errorf("URL parse error: %s", err.Error())
   475  		return "", err
   476  	}
   477  
   478  	saveURL.Path = dbclient.DBName
   479  	// id can contain a '/', so encode separately
   480  	saveURL = &url.URL{Opaque: saveURL.String() + "/" + encodePathElement(id)}
   481  
   482  	if rev == "" {
   483  
   484  		//See if the document already exists, we need the rev for save
   485  		_, revdoc, err2 := dbclient.ReadDoc(id)
   486  		if err2 != nil {
   487  			//set the revision to indicate that the document was not found
   488  			rev = ""
   489  		} else {
   490  			//set the revision to the rev returned from the document read
   491  			rev = revdoc
   492  		}
   493  	}
   494  
   495  	logger.Debugf("  rev=%s", rev)
   496  
   497  	//Set up a buffer for the data to be pushed to couchdb
   498  	data := []byte{}
   499  
   500  	//Set up a default boundary for use by multipart if sending attachments
   501  	defaultBoundary := ""
   502  
   503  	//check to see if attachments is nil, if so, then this is a JSON only
   504  	if couchDoc.Attachments == nil {
   505  
   506  		//Test to see if this is a valid JSON
   507  		if IsJSON(string(couchDoc.JSONValue)) != true {
   508  			return "", fmt.Errorf("JSON format is not valid")
   509  		}
   510  
   511  		// if there are no attachments, then use the bytes passed in as the JSON
   512  		data = couchDoc.JSONValue
   513  
   514  	} else { // there are attachments
   515  
   516  		//attachments are included, create the multipart definition
   517  		multipartData, multipartBoundary, err3 := createAttachmentPart(couchDoc, defaultBoundary)
   518  		if err3 != nil {
   519  			return "", err3
   520  		}
   521  
   522  		//Set the data buffer to the data from the create multi-part data
   523  		data = multipartData.Bytes()
   524  
   525  		//Set the default boundary to the value generated in the multipart creation
   526  		defaultBoundary = multipartBoundary
   527  
   528  	}
   529  
   530  	//get the number of retries
   531  	maxRetries := dbclient.CouchInstance.conf.MaxRetries
   532  
   533  	//handle the request for saving the JSON or attachments
   534  	resp, _, err := dbclient.CouchInstance.handleRequest(http.MethodPut, saveURL.String(), data, rev, defaultBoundary, maxRetries)
   535  	if err != nil {
   536  		return "", err
   537  	}
   538  	defer closeResponseBody(resp)
   539  
   540  	//get the revision and return
   541  	revision, err := getRevisionHeader(resp)
   542  	if err != nil {
   543  		return "", err
   544  	}
   545  
   546  	logger.Debugf("Exiting SaveDoc()")
   547  
   548  	return revision, nil
   549  
   550  }
   551  
   552  func createAttachmentPart(couchDoc *CouchDoc, defaultBoundary string) (bytes.Buffer, string, error) {
   553  
   554  	//Create a buffer for writing the result
   555  	writeBuffer := new(bytes.Buffer)
   556  
   557  	// read the attachment and save as an attachment
   558  	writer := multipart.NewWriter(writeBuffer)
   559  
   560  	//retrieve the boundary for the multipart
   561  	defaultBoundary = writer.Boundary()
   562  
   563  	fileAttachments := map[string]FileDetails{}
   564  
   565  	for _, attachment := range couchDoc.Attachments {
   566  		fileAttachments[attachment.Name] = FileDetails{true, attachment.ContentType, len(attachment.AttachmentBytes)}
   567  	}
   568  
   569  	attachmentJSONMap := map[string]interface{}{
   570  		"_attachments": fileAttachments}
   571  
   572  	//Add any data uploaded with the files
   573  	if couchDoc.JSONValue != nil {
   574  
   575  		//create a generic map
   576  		genericMap := make(map[string]interface{})
   577  
   578  		//unmarshal the data into the generic map
   579  		decoder := json.NewDecoder(bytes.NewBuffer(couchDoc.JSONValue))
   580  		decoder.UseNumber()
   581  		decoder.Decode(&genericMap)
   582  
   583  		//add all key/values to the attachmentJSONMap
   584  		for jsonKey, jsonValue := range genericMap {
   585  			attachmentJSONMap[jsonKey] = jsonValue
   586  		}
   587  
   588  	}
   589  
   590  	filesForUpload, _ := json.Marshal(attachmentJSONMap)
   591  	logger.Debugf(string(filesForUpload))
   592  
   593  	//create the header for the JSON
   594  	header := make(textproto.MIMEHeader)
   595  	header.Set("Content-Type", "application/json")
   596  
   597  	part, err := writer.CreatePart(header)
   598  	if err != nil {
   599  		return *writeBuffer, defaultBoundary, err
   600  	}
   601  
   602  	part.Write(filesForUpload)
   603  
   604  	for _, attachment := range couchDoc.Attachments {
   605  
   606  		header := make(textproto.MIMEHeader)
   607  		part, err2 := writer.CreatePart(header)
   608  		if err2 != nil {
   609  			return *writeBuffer, defaultBoundary, err2
   610  		}
   611  		part.Write(attachment.AttachmentBytes)
   612  
   613  	}
   614  
   615  	err = writer.Close()
   616  	if err != nil {
   617  		return *writeBuffer, defaultBoundary, err
   618  	}
   619  
   620  	return *writeBuffer, defaultBoundary, nil
   621  
   622  }
   623  
   624  func getRevisionHeader(resp *http.Response) (string, error) {
   625  
   626  	revision := resp.Header.Get("Etag")
   627  
   628  	if revision == "" {
   629  		return "", fmt.Errorf("No revision tag detected")
   630  	}
   631  
   632  	reg := regexp.MustCompile(`"([^"]*)"`)
   633  	revisionNoQuotes := reg.ReplaceAllString(revision, "${1}")
   634  	return revisionNoQuotes, nil
   635  
   636  }
   637  
   638  //ReadDoc method provides function to retrieve a document from the database by id
   639  func (dbclient *CouchDatabase) ReadDoc(id string) (*CouchDoc, string, error) {
   640  	var couchDoc CouchDoc
   641  	attachments := []*Attachment{}
   642  
   643  	logger.Debugf("Entering ReadDoc()  id=[%s]", id)
   644  	if !utf8.ValidString(id) {
   645  		return nil, "", fmt.Errorf("doc id [%x] not a valid utf8 string", id)
   646  	}
   647  
   648  	readURL, err := url.Parse(dbclient.CouchInstance.conf.URL)
   649  	if err != nil {
   650  		logger.Errorf("URL parse error: %s", err.Error())
   651  		return nil, "", err
   652  	}
   653  	readURL.Path = dbclient.DBName
   654  	// id can contain a '/', so encode separately
   655  	readURL = &url.URL{Opaque: readURL.String() + "/" + encodePathElement(id)}
   656  
   657  	query := readURL.Query()
   658  	query.Add("attachments", "true")
   659  
   660  	readURL.RawQuery = query.Encode()
   661  
   662  	//get the number of retries
   663  	maxRetries := dbclient.CouchInstance.conf.MaxRetries
   664  
   665  	resp, couchDBReturn, err := dbclient.CouchInstance.handleRequest(http.MethodGet, readURL.String(), nil, "", "", maxRetries)
   666  	if err != nil {
   667  		if couchDBReturn != nil && couchDBReturn.StatusCode == 404 {
   668  			logger.Debug("Document not found (404), returning nil value instead of 404 error")
   669  			// non-existent document should return nil value instead of a 404 error
   670  			// for details see https://github.com/hyperledger-archives/fabric/issues/936
   671  			return nil, "", nil
   672  		}
   673  		logger.Debugf("couchDBReturn=%v\n", couchDBReturn)
   674  		return nil, "", err
   675  	}
   676  	defer closeResponseBody(resp)
   677  
   678  	//Get the media type from the Content-Type header
   679  	mediaType, params, err := mime.ParseMediaType(resp.Header.Get("Content-Type"))
   680  	if err != nil {
   681  		log.Fatal(err)
   682  	}
   683  
   684  	//Get the revision from header
   685  	revision, err := getRevisionHeader(resp)
   686  	if err != nil {
   687  		return nil, "", err
   688  	}
   689  
   690  	//check to see if the is multipart,  handle as attachment if multipart is detected
   691  	if strings.HasPrefix(mediaType, "multipart/") {
   692  		//Set up the multipart reader based on the boundary
   693  		multipartReader := multipart.NewReader(resp.Body, params["boundary"])
   694  		for {
   695  			p, err := multipartReader.NextPart()
   696  			if err == io.EOF {
   697  				break // processed all parts
   698  			}
   699  			if err != nil {
   700  				return nil, "", err
   701  			}
   702  
   703  			defer p.Close()
   704  
   705  			logger.Debugf("part header=%s", p.Header)
   706  			switch p.Header.Get("Content-Type") {
   707  			case "application/json":
   708  				partdata, err := ioutil.ReadAll(p)
   709  				if err != nil {
   710  					return nil, "", err
   711  				}
   712  				couchDoc.JSONValue = partdata
   713  			default:
   714  
   715  				//Create an attachment structure and load it
   716  				attachment := &Attachment{}
   717  				attachment.ContentType = p.Header.Get("Content-Type")
   718  				contentDispositionParts := strings.Split(p.Header.Get("Content-Disposition"), ";")
   719  				if strings.TrimSpace(contentDispositionParts[0]) == "attachment" {
   720  					switch p.Header.Get("Content-Encoding") {
   721  					case "gzip": //See if the part is gzip encoded
   722  
   723  						var respBody []byte
   724  
   725  						gr, err := gzip.NewReader(p)
   726  						if err != nil {
   727  							return nil, "", err
   728  						}
   729  						respBody, err = ioutil.ReadAll(gr)
   730  						if err != nil {
   731  							return nil, "", err
   732  						}
   733  
   734  						logger.Debugf("Retrieved attachment data")
   735  						attachment.AttachmentBytes = respBody
   736  						attachment.Name = p.FileName()
   737  						attachments = append(attachments, attachment)
   738  
   739  					default:
   740  
   741  						//retrieve the data,  this is not gzip
   742  						partdata, err := ioutil.ReadAll(p)
   743  						if err != nil {
   744  							return nil, "", err
   745  						}
   746  						logger.Debugf("Retrieved attachment data")
   747  						attachment.AttachmentBytes = partdata
   748  						attachment.Name = p.FileName()
   749  						attachments = append(attachments, attachment)
   750  
   751  					} // end content-encoding switch
   752  				} // end if attachment
   753  			} // end content-type switch
   754  		} // for all multiparts
   755  
   756  		couchDoc.Attachments = attachments
   757  
   758  		return &couchDoc, revision, nil
   759  	}
   760  
   761  	//handle as JSON document
   762  	couchDoc.JSONValue, err = ioutil.ReadAll(resp.Body)
   763  	if err != nil {
   764  		return nil, "", err
   765  	}
   766  
   767  	logger.Debugf("Exiting ReadDoc()")
   768  	return &couchDoc, revision, nil
   769  }
   770  
   771  //ReadDocRange method provides function to a range of documents based on the start and end keys
   772  //startKey and endKey can also be empty strings.  If startKey and endKey are empty, all documents are returned
   773  //This function provides a limit option to specify the max number of entries and is supplied by config.
   774  //Skip is reserved for possible future future use.
   775  func (dbclient *CouchDatabase) ReadDocRange(startKey, endKey string, limit, skip int) (*[]QueryResult, error) {
   776  
   777  	logger.Debugf("Entering ReadDocRange()  startKey=%s, endKey=%s", startKey, endKey)
   778  
   779  	var results []QueryResult
   780  
   781  	rangeURL, err := url.Parse(dbclient.CouchInstance.conf.URL)
   782  	if err != nil {
   783  		logger.Errorf("URL parse error: %s", err.Error())
   784  		return nil, err
   785  	}
   786  	rangeURL.Path = dbclient.DBName + "/_all_docs"
   787  
   788  	queryParms := rangeURL.Query()
   789  	queryParms.Set("limit", strconv.Itoa(limit))
   790  	queryParms.Add("skip", strconv.Itoa(skip))
   791  	queryParms.Add("include_docs", "true")
   792  	queryParms.Add("inclusive_end", "false") // endkey should be exclusive to be consistent with goleveldb
   793  
   794  	//Append the startKey if provided
   795  
   796  	if startKey != "" {
   797  		var err error
   798  		if startKey, err = encodeForJSON(startKey); err != nil {
   799  			return nil, err
   800  		}
   801  		queryParms.Add("startkey", "\""+startKey+"\"")
   802  	}
   803  
   804  	//Append the endKey if provided
   805  	if endKey != "" {
   806  		var err error
   807  		if endKey, err = encodeForJSON(endKey); err != nil {
   808  			return nil, err
   809  		}
   810  		queryParms.Add("endkey", "\""+endKey+"\"")
   811  	}
   812  
   813  	rangeURL.RawQuery = queryParms.Encode()
   814  
   815  	//get the number of retries
   816  	maxRetries := dbclient.CouchInstance.conf.MaxRetries
   817  
   818  	resp, _, err := dbclient.CouchInstance.handleRequest(http.MethodGet, rangeURL.String(), nil, "", "", maxRetries)
   819  	if err != nil {
   820  		return nil, err
   821  	}
   822  	defer closeResponseBody(resp)
   823  
   824  	if logger.IsEnabledFor(logging.DEBUG) {
   825  		dump, err2 := httputil.DumpResponse(resp, true)
   826  		if err2 != nil {
   827  			log.Fatal(err2)
   828  		}
   829  		logger.Debugf("%s", dump)
   830  	}
   831  
   832  	//handle as JSON document
   833  	jsonResponseRaw, err := ioutil.ReadAll(resp.Body)
   834  	if err != nil {
   835  		return nil, err
   836  	}
   837  
   838  	var jsonResponse = &RangeQueryResponse{}
   839  	err2 := json.Unmarshal(jsonResponseRaw, &jsonResponse)
   840  	if err2 != nil {
   841  		return nil, err2
   842  	}
   843  
   844  	logger.Debugf("Total Rows: %d", jsonResponse.TotalRows)
   845  
   846  	for _, row := range jsonResponse.Rows {
   847  
   848  		var jsonDoc = &Doc{}
   849  		err3 := json.Unmarshal(row.Doc, &jsonDoc)
   850  		if err3 != nil {
   851  			return nil, err3
   852  		}
   853  
   854  		if jsonDoc.Attachments != nil {
   855  
   856  			logger.Debugf("Adding JSON document and attachments for id: %s", jsonDoc.ID)
   857  
   858  			couchDoc, _, err := dbclient.ReadDoc(jsonDoc.ID)
   859  			if err != nil {
   860  				return nil, err
   861  			}
   862  
   863  			var addDocument = &QueryResult{jsonDoc.ID, couchDoc.JSONValue, couchDoc.Attachments}
   864  			results = append(results, *addDocument)
   865  
   866  		} else {
   867  
   868  			logger.Debugf("Adding json docment for id: %s", jsonDoc.ID)
   869  
   870  			var addDocument = &QueryResult{jsonDoc.ID, row.Doc, nil}
   871  			results = append(results, *addDocument)
   872  
   873  		}
   874  
   875  	}
   876  
   877  	logger.Debugf("Exiting ReadDocRange()")
   878  
   879  	return &results, nil
   880  
   881  }
   882  
   883  //DeleteDoc method provides function to delete a document from the database by id
   884  func (dbclient *CouchDatabase) DeleteDoc(id, rev string) error {
   885  
   886  	logger.Debugf("Entering DeleteDoc()  id=%s", id)
   887  
   888  	deleteURL, err := url.Parse(dbclient.CouchInstance.conf.URL)
   889  	if err != nil {
   890  		logger.Errorf("URL parse error: %s", err.Error())
   891  		return err
   892  	}
   893  
   894  	deleteURL.Path = dbclient.DBName
   895  	// id can contain a '/', so encode separately
   896  	deleteURL = &url.URL{Opaque: deleteURL.String() + "/" + encodePathElement(id)}
   897  
   898  	if rev == "" {
   899  
   900  		//See if the document already exists, we need the rev for delete
   901  		_, revdoc, err2 := dbclient.ReadDoc(id)
   902  		if err2 != nil {
   903  			//set the revision to indicate that the document was not found
   904  			rev = ""
   905  		} else {
   906  			//set the revision to the rev returned from the document read
   907  			rev = revdoc
   908  		}
   909  	}
   910  
   911  	logger.Debugf("  rev=%s", rev)
   912  
   913  	//get the number of retries
   914  	maxRetries := dbclient.CouchInstance.conf.MaxRetries
   915  
   916  	resp, couchDBReturn, err := dbclient.CouchInstance.handleRequest(http.MethodDelete, deleteURL.String(), nil, rev, "", maxRetries)
   917  	if err != nil {
   918  		fmt.Printf("couchDBReturn=%v", couchDBReturn)
   919  		if couchDBReturn != nil && couchDBReturn.StatusCode == 404 {
   920  			logger.Debug("Document not found (404), returning nil value instead of 404 error")
   921  			// non-existent document should return nil value instead of a 404 error
   922  			// for details see https://github.com/hyperledger-archives/fabric/issues/936
   923  			return nil
   924  		}
   925  		return err
   926  	}
   927  	defer closeResponseBody(resp)
   928  
   929  	logger.Debugf("Exiting DeleteDoc()")
   930  
   931  	return nil
   932  
   933  }
   934  
   935  //QueryDocuments method provides function for processing a query
   936  func (dbclient *CouchDatabase) QueryDocuments(query string) (*[]QueryResult, error) {
   937  
   938  	logger.Debugf("Entering QueryDocuments()  query=%s", query)
   939  
   940  	var results []QueryResult
   941  
   942  	queryURL, err := url.Parse(dbclient.CouchInstance.conf.URL)
   943  	if err != nil {
   944  		logger.Errorf("URL parse error: %s", err.Error())
   945  		return nil, err
   946  	}
   947  
   948  	queryURL.Path = dbclient.DBName + "/_find"
   949  
   950  	//get the number of retries
   951  	maxRetries := dbclient.CouchInstance.conf.MaxRetries
   952  
   953  	resp, _, err := dbclient.CouchInstance.handleRequest(http.MethodPost, queryURL.String(), []byte(query), "", "", maxRetries)
   954  	if err != nil {
   955  		return nil, err
   956  	}
   957  	defer closeResponseBody(resp)
   958  
   959  	if logger.IsEnabledFor(logging.DEBUG) {
   960  		dump, err2 := httputil.DumpResponse(resp, true)
   961  		if err2 != nil {
   962  			log.Fatal(err2)
   963  		}
   964  		logger.Debugf("%s", dump)
   965  	}
   966  
   967  	//handle as JSON document
   968  	jsonResponseRaw, err := ioutil.ReadAll(resp.Body)
   969  	if err != nil {
   970  		return nil, err
   971  	}
   972  
   973  	var jsonResponse = &QueryResponse{}
   974  
   975  	err2 := json.Unmarshal(jsonResponseRaw, &jsonResponse)
   976  	if err2 != nil {
   977  		return nil, err2
   978  	}
   979  
   980  	for _, row := range jsonResponse.Docs {
   981  
   982  		var jsonDoc = &Doc{}
   983  		err3 := json.Unmarshal(row, &jsonDoc)
   984  		if err3 != nil {
   985  			return nil, err3
   986  		}
   987  
   988  		if jsonDoc.Attachments != nil {
   989  
   990  			logger.Debugf("Adding JSON docment and attachments for id: %s", jsonDoc.ID)
   991  
   992  			couchDoc, _, err := dbclient.ReadDoc(jsonDoc.ID)
   993  			if err != nil {
   994  				return nil, err
   995  			}
   996  			var addDocument = &QueryResult{ID: jsonDoc.ID, Value: couchDoc.JSONValue, Attachments: couchDoc.Attachments}
   997  			results = append(results, *addDocument)
   998  
   999  		} else {
  1000  			logger.Debugf("Adding json docment for id: %s", jsonDoc.ID)
  1001  			var addDocument = &QueryResult{ID: jsonDoc.ID, Value: row, Attachments: nil}
  1002  
  1003  			results = append(results, *addDocument)
  1004  
  1005  		}
  1006  	}
  1007  	logger.Debugf("Exiting QueryDocuments()")
  1008  
  1009  	return &results, nil
  1010  
  1011  }
  1012  
  1013  //BatchRetrieveIDRevision - batch method to retrieve IDs and revisions
  1014  func (dbclient *CouchDatabase) BatchRetrieveIDRevision(keys []string) ([]*DocMetadata, error) {
  1015  
  1016  	batchURL, err := url.Parse(dbclient.CouchInstance.conf.URL)
  1017  	if err != nil {
  1018  		logger.Errorf("URL parse error: %s", err.Error())
  1019  		return nil, err
  1020  	}
  1021  	batchURL.Path = dbclient.DBName + "/_all_docs"
  1022  
  1023  	queryParms := batchURL.Query()
  1024  	queryParms.Add("include_docs", "true")
  1025  	batchURL.RawQuery = queryParms.Encode()
  1026  
  1027  	keymap := make(map[string]interface{})
  1028  
  1029  	keymap["keys"] = keys
  1030  
  1031  	jsonKeys, err := json.Marshal(keymap)
  1032  	if err != nil {
  1033  		return nil, err
  1034  	}
  1035  
  1036  	//get the number of retries
  1037  	maxRetries := dbclient.CouchInstance.conf.MaxRetries
  1038  
  1039  	resp, _, err := dbclient.CouchInstance.handleRequest(http.MethodPost, batchURL.String(), jsonKeys, "", "", maxRetries)
  1040  	if err != nil {
  1041  		return nil, err
  1042  	}
  1043  	defer closeResponseBody(resp)
  1044  
  1045  	if logger.IsEnabledFor(logging.DEBUG) {
  1046  		dump, _ := httputil.DumpResponse(resp, false)
  1047  		// compact debug log by replacing carriage return / line feed with dashes to separate http headers
  1048  		logger.Debugf("HTTP Response: %s", bytes.Replace(dump, []byte{0x0d, 0x0a}, []byte{0x20, 0x7c, 0x20}, -1))
  1049  	}
  1050  
  1051  	//handle as JSON document
  1052  	jsonResponseRaw, err := ioutil.ReadAll(resp.Body)
  1053  	if err != nil {
  1054  		return nil, err
  1055  	}
  1056  
  1057  	var jsonResponse = &BatchRetrieveDocMedatadataResponse{}
  1058  
  1059  	err2 := json.Unmarshal(jsonResponseRaw, &jsonResponse)
  1060  	if err2 != nil {
  1061  		return nil, err2
  1062  	}
  1063  
  1064  	revisionDocs := []*DocMetadata{}
  1065  
  1066  	for _, row := range jsonResponse.Rows {
  1067  		revisionDoc := &DocMetadata{ID: row.ID, Rev: row.Doc.Rev, Version: row.Doc.Version}
  1068  		revisionDocs = append(revisionDocs, revisionDoc)
  1069  	}
  1070  
  1071  	return revisionDocs, nil
  1072  
  1073  }
  1074  
  1075  //BatchUpdateDocuments - batch method to batch update documents
  1076  func (dbclient *CouchDatabase) BatchUpdateDocuments(documents []*CouchDoc) ([]*BatchUpdateResponse, error) {
  1077  
  1078  	logger.Debugf("Entering BatchUpdateDocuments()  documents=%v", documents)
  1079  
  1080  	batchURL, err := url.Parse(dbclient.CouchInstance.conf.URL)
  1081  	if err != nil {
  1082  		logger.Errorf("URL parse error: %s", err.Error())
  1083  		return nil, err
  1084  	}
  1085  	batchURL.Path = dbclient.DBName + "/_bulk_docs"
  1086  
  1087  	documentMap := make(map[string]interface{})
  1088  
  1089  	var jsonDocumentMap []interface{}
  1090  
  1091  	for _, jsonDocument := range documents {
  1092  
  1093  		//create a document map
  1094  		var document = make(map[string]interface{})
  1095  
  1096  		//unmarshal the JSON component of the CouchDoc into the document
  1097  		json.Unmarshal(jsonDocument.JSONValue, &document)
  1098  
  1099  		//iterate through any attachments
  1100  		if len(jsonDocument.Attachments) > 0 {
  1101  
  1102  			//create a file attachment map
  1103  			fileAttachment := make(map[string]interface{})
  1104  
  1105  			//for each attachment, create a Base64Attachment, name the attachment,
  1106  			//add the content type and base64 encode the attachment
  1107  			for _, attachment := range jsonDocument.Attachments {
  1108  				fileAttachment[attachment.Name] = Base64Attachment{attachment.ContentType,
  1109  					base64.StdEncoding.EncodeToString(attachment.AttachmentBytes)}
  1110  			}
  1111  
  1112  			//add attachments to the document
  1113  			document["_attachments"] = fileAttachment
  1114  
  1115  		}
  1116  
  1117  		//Append the document to the map of documents
  1118  		jsonDocumentMap = append(jsonDocumentMap, document)
  1119  
  1120  	}
  1121  
  1122  	//Add the documents to the "docs" item
  1123  	documentMap["docs"] = jsonDocumentMap
  1124  
  1125  	jsonKeys, err := json.Marshal(documentMap)
  1126  
  1127  	if err != nil {
  1128  		return nil, err
  1129  	}
  1130  
  1131  	//get the number of retries
  1132  	maxRetries := dbclient.CouchInstance.conf.MaxRetries
  1133  
  1134  	resp, _, err := dbclient.CouchInstance.handleRequest(http.MethodPost, batchURL.String(), jsonKeys, "", "", maxRetries)
  1135  	if err != nil {
  1136  		return nil, err
  1137  	}
  1138  	defer closeResponseBody(resp)
  1139  
  1140  	if logger.IsEnabledFor(logging.DEBUG) {
  1141  		dump, _ := httputil.DumpResponse(resp, false)
  1142  		// compact debug log by replacing carriage return / line feed with dashes to separate http headers
  1143  		logger.Debugf("HTTP Response: %s", bytes.Replace(dump, []byte{0x0d, 0x0a}, []byte{0x20, 0x7c, 0x20}, -1))
  1144  	}
  1145  
  1146  	//handle as JSON document
  1147  	jsonResponseRaw, err := ioutil.ReadAll(resp.Body)
  1148  	if err != nil {
  1149  		return nil, err
  1150  	}
  1151  
  1152  	var jsonResponse = []*BatchUpdateResponse{}
  1153  
  1154  	err2 := json.Unmarshal(jsonResponseRaw, &jsonResponse)
  1155  	if err2 != nil {
  1156  		return nil, err2
  1157  	}
  1158  
  1159  	logger.Debugf("Exiting BatchUpdateDocuments()")
  1160  
  1161  	return jsonResponse, nil
  1162  
  1163  }
  1164  
  1165  //handleRequest method is a generic http request handler.
  1166  // if it returns an error, it ensures that the response body is closed, else it is the
  1167  // callee's responsibility to close response correctly
  1168  func (couchInstance *CouchInstance) handleRequest(method, connectURL string, data []byte, rev string,
  1169  	multipartBoundary string, maxRetries int) (*http.Response, *DBReturn, error) {
  1170  
  1171  	logger.Debugf("Entering handleRequest()  method=%s  url=%v", method, connectURL)
  1172  
  1173  	//create the return objects for couchDB
  1174  	var resp *http.Response
  1175  	var errResp error
  1176  	couchDBReturn := &DBReturn{}
  1177  
  1178  	//set initial wait duration for retries
  1179  	waitDuration := retryWaitTime * time.Millisecond
  1180  
  1181  	//attempt the http request for the max number of retries
  1182  	for attempts := 0; attempts < maxRetries; attempts++ {
  1183  
  1184  		//Set up a buffer for the payload data
  1185  		payloadData := new(bytes.Buffer)
  1186  
  1187  		payloadData.ReadFrom(bytes.NewReader(data))
  1188  
  1189  		//Create request based on URL for couchdb operation
  1190  		req, err := http.NewRequest(method, connectURL, payloadData)
  1191  		if err != nil {
  1192  			return nil, nil, err
  1193  		}
  1194  
  1195  		//add content header for PUT
  1196  		if method == http.MethodPut || method == http.MethodPost || method == http.MethodDelete {
  1197  
  1198  			//If the multipartBoundary is not set, then this is a JSON and content-type should be set
  1199  			//to application/json.   Else, this is contains an attachment and needs to be multipart
  1200  			if multipartBoundary == "" {
  1201  				req.Header.Set("Content-Type", "application/json")
  1202  			} else {
  1203  				req.Header.Set("Content-Type", "multipart/related;boundary=\""+multipartBoundary+"\"")
  1204  			}
  1205  
  1206  			//check to see if the revision is set,  if so, pass as a header
  1207  			if rev != "" {
  1208  				req.Header.Set("If-Match", rev)
  1209  			}
  1210  		}
  1211  
  1212  		//add content header for PUT
  1213  		if method == http.MethodPut || method == http.MethodPost {
  1214  			req.Header.Set("Accept", "application/json")
  1215  		}
  1216  
  1217  		//add content header for GET
  1218  		if method == http.MethodGet {
  1219  			req.Header.Set("Accept", "multipart/related")
  1220  		}
  1221  
  1222  		//If username and password are set the use basic auth
  1223  		if couchInstance.conf.Username != "" && couchInstance.conf.Password != "" {
  1224  			req.SetBasicAuth(couchInstance.conf.Username, couchInstance.conf.Password)
  1225  		}
  1226  
  1227  		if logger.IsEnabledFor(logging.DEBUG) {
  1228  			dump, _ := httputil.DumpRequestOut(req, false)
  1229  			// compact debug log by replacing carriage return / line feed with dashes to separate http headers
  1230  			logger.Debugf("HTTP Request: %s", bytes.Replace(dump, []byte{0x0d, 0x0a}, []byte{0x20, 0x7c, 0x20}, -1))
  1231  		}
  1232  
  1233  		//Execute http request
  1234  		resp, errResp = couchInstance.client.Do(req)
  1235  
  1236  		//if an error is not detected then drop out of the retry
  1237  		if errResp == nil && resp != nil && resp.StatusCode < 500 {
  1238  			break
  1239  		}
  1240  
  1241  		//if this is an error, record the retry error, else this is a 500 error
  1242  		if errResp != nil {
  1243  
  1244  			//Log the error with the retry count and continue
  1245  			logger.Warningf("Retrying couchdb request in %s. Attempt:%v  Error:%v",
  1246  				waitDuration.String(), attempts+1, errResp.Error())
  1247  
  1248  		} else {
  1249  
  1250  			//Read the response body and close it for next attempt
  1251  			jsonError, err := ioutil.ReadAll(resp.Body)
  1252  			closeResponseBody(resp)
  1253  			if err != nil {
  1254  				return nil, nil, err
  1255  			}
  1256  
  1257  			errorBytes := []byte(jsonError)
  1258  
  1259  			//Unmarshal the response
  1260  			json.Unmarshal(errorBytes, &couchDBReturn)
  1261  
  1262  			//Log the 500 error with the retry count and continue
  1263  			logger.Warningf("Retrying couchdb request in %s. Attempt:%v  Couch DB Error:%s,  Status Code:%v  Reason:%v",
  1264  				waitDuration.String(), attempts+1, couchDBReturn.Error, resp.Status, couchDBReturn.Reason)
  1265  
  1266  		}
  1267  		//sleep for specified sleep time, then retry
  1268  		time.Sleep(waitDuration)
  1269  
  1270  		//backoff, doubling the retry time for next attempt
  1271  		waitDuration *= 2
  1272  
  1273  	}
  1274  
  1275  	//if the error present, return the error
  1276  	if errResp != nil {
  1277  		return nil, nil, errResp
  1278  	}
  1279  
  1280  	//set the return code for the couchDB request
  1281  	couchDBReturn.StatusCode = resp.StatusCode
  1282  
  1283  	//check to see if the status code is 400 or higher
  1284  	//response codes 4XX and 500 will be treated as errors
  1285  	if resp.StatusCode >= 400 {
  1286  		// close the response before returning error
  1287  		defer closeResponseBody(resp)
  1288  
  1289  		//Read the response body
  1290  		jsonError, err := ioutil.ReadAll(resp.Body)
  1291  		if err != nil {
  1292  			return nil, nil, err
  1293  		}
  1294  
  1295  		errorBytes := []byte(jsonError)
  1296  
  1297  		//marshal the response
  1298  		json.Unmarshal(errorBytes, &couchDBReturn)
  1299  
  1300  		logger.Debugf("Couch DB Error:%s,  Status Code:%v,  Reason:%s",
  1301  			couchDBReturn.Error, resp.StatusCode, couchDBReturn.Reason)
  1302  
  1303  		return nil, couchDBReturn, fmt.Errorf("Couch DB Error:%s,  Status Code:%v,  Reason:%s",
  1304  			couchDBReturn.Error, resp.StatusCode, couchDBReturn.Reason)
  1305  
  1306  	}
  1307  
  1308  	logger.Debugf("Exiting handleRequest()")
  1309  
  1310  	//If no errors, then return the results
  1311  	return resp, couchDBReturn, nil
  1312  }
  1313  
  1314  //IsJSON tests a string to determine if a valid JSON
  1315  func IsJSON(s string) bool {
  1316  	var js map[string]interface{}
  1317  	return json.Unmarshal([]byte(s), &js) == nil
  1318  }
  1319  
  1320  // encodePathElement uses Golang for encoding and in addition, replaces a '/' by %2F.
  1321  // Otherwise, in the regular encoding, a '/' is treated as a path separator in the url
  1322  func encodePathElement(str string) string {
  1323  	u := &url.URL{}
  1324  	u.Path = str
  1325  	encodedStr := u.String()
  1326  	encodedStr = strings.Replace(encodedStr, "/", "%2F", -1)
  1327  	return encodedStr
  1328  }
  1329  
  1330  func encodeForJSON(str string) (string, error) {
  1331  	buf := &bytes.Buffer{}
  1332  	encoder := json.NewEncoder(buf)
  1333  	if err := encoder.Encode(str); err != nil {
  1334  		return "", err
  1335  	}
  1336  	// Encode adds double quotes to string and terminates with \n - stripping them as bytes as they are all ascii(0-127)
  1337  	buffer := buf.Bytes()
  1338  	return string(buffer[1 : len(buffer)-2]), nil
  1339  }