package gorqlite /* this file has low level stuff: rqliteApiGet() rqliteApiPost() There is some code duplication between those and they should probably be combined into one function. nothing public here. */ import "bytes" import "encoding/json" import "errors" import "fmt" import "io/ioutil" import "net/http" import "time" /* ***************************************************************** method: rqliteApiGet() - for api_STATUS - lowest level interface - does not do any JSON unmarshaling - handles retries - handles timeouts * *****************************************************************/ func (conn *Connection) rqliteApiGet(apiOp apiOperation) ([]byte, error) { var responseBody []byte trace("%s: rqliteApiGet() called",conn.ID) // only api_STATUS now - maybe someday BACKUP if ( apiOp != api_STATUS ) { return responseBody, errors.New("rqliteApiGet() called for invalid api operation") } // just to be safe, check this peersToTry := conn.cluster.makePeerList() if ( len(peersToTry) < 1 ) { return responseBody, errors.New("I don't have any cluster info") } trace("%s: I have a peer list %d peers long",conn.ID,len(peersToTry)) // failure log is used so that if all peers fail, we can say something // about why each failed failureLog := make([]string,0) PeerLoop: for peerNum, peerToTry := range peersToTry { trace("%s: attemping to contact peer %d",conn.ID,peerNum) // docs say default GET policy is up to 10 follows automatically url := conn.assembleURL(api_STATUS,peerToTry) req, err := http.NewRequest("GET",url,nil) if ( err != nil ) { trace("%s: got error '%s' doing http.NewRequest", conn.ID,err.Error()) failureLog = append(failureLog,fmt.Sprintf("%s failed due to %s",url,err.Error())) continue PeerLoop } trace("%s: http.NewRequest() OK") req.Header.Set("Content-Type","application/json") client := &http.Client{} client.Timeout = time.Duration(conn.timeout) * time.Second response, err := client.Do(req) if ( err != nil ) { trace("%s: got error '%s' doing client.Do", conn.ID,err.Error()) failureLog = append(failureLog,fmt.Sprintf("%s failed due to %s",url,err.Error())) continue PeerLoop } defer response.Body.Close() trace("%s: client.Do() OK") responseBody, err := ioutil.ReadAll(response.Body) if ( err != nil ) { trace("%s: got error '%s' doing ioutil.ReadAll", conn.ID,err.Error()) failureLog = append(failureLog,fmt.Sprintf("%s failed due to %s",url,err.Error())) continue PeerLoop } trace("%s: ioutil.ReadAll() OK") if ( response.Status != "200 OK" ) { trace("%s: got code %s",conn.ID,response.Status) failureLog = append(failureLog,fmt.Sprintf("%s failed, got: %s",url,response.Status)) continue PeerLoop } // if we got here, we succeeded trace("%s: api call OK, returning",conn.ID) return responseBody, nil } // if we got here, all peers failed. Let's build a verbose error message var stringBuffer bytes.Buffer stringBuffer.WriteString("tried all peers unsuccessfully. here are the results:\n") for n, v := range failureLog { stringBuffer.WriteString(fmt.Sprintf(" peer #%d: %s\n",n,v)) } return responseBody, errors.New(stringBuffer.String()) } /* ***************************************************************** method: rqliteApiPost() - for api_QUERY and api_WRITE - lowest level interface - does not do any JSON unmarshaling - handles 301s, etc. - handles retries - handles timeouts it is called with an apiOperation type because the URL it will use varies depending on the API operation type (api_QUERY vs. api_WRITE) * *****************************************************************/ func (conn *Connection) rqliteApiPost (apiOp apiOperation, sqlStatements []string) ([]byte, error) { var responseBody []byte switch (apiOp) { case api_QUERY: trace("%s: rqliteApiGet() post called for a QUERY of %d statements",conn.ID,len(sqlStatements)) case api_WRITE: trace("%s: rqliteApiGet() post called for a QUERY of %d statements",conn.ID,len(sqlStatements)) default: return responseBody, errors.New("weird! called for an invalid apiOperation in rqliteApiPost()") } // jsonify the statements. not really needed in the // case of api_STATUS but doesn't hurt jStatements , err := json.Marshal(sqlStatements) if ( err != nil ) { return nil, err } // just to be safe, check this peersToTry := conn.cluster.makePeerList() if ( len(peersToTry) < 1 ) { return responseBody, errors.New("I don't have any cluster info") } // failure log is used so that if all peers fail, we can say something // about why each failed failureLog := make([]string,0) PeerLoop: for peerNum, peer := range peersToTry { trace("%s: trying peer #%d",conn.ID,peerNum) // we're doing a post, and the RFCs say that if you get a 301, it's not // automatically followed, so we have to do that ourselves responseStatus := "Haven't Tried Yet" var url string for ( responseStatus == "Haven't Tried Yet" || responseStatus == "301 Moved Permanently" ) { url = conn.assembleURL(apiOp,peer) req, err := http.NewRequest("POST",url,bytes.NewBuffer(jStatements)) if ( err != nil ) { trace("%s: got error '%s' doing http.NewRequest", conn.ID,err.Error()) failureLog = append(failureLog,fmt.Sprintf("%s failed due to %s",url,err.Error())) continue PeerLoop } req.Header.Set("Content-Type","application/json") client := &http.Client{} response, err := client.Do(req) if ( err != nil ) { trace("%s: got error '%s' doing client.Do", conn.ID,err.Error()) failureLog = append(failureLog,fmt.Sprintf("%s failed due to %s",url,err.Error())) continue PeerLoop } defer response.Body.Close() responseBody, err = ioutil.ReadAll(response.Body) if ( err != nil ) { trace("%s: got error '%s' doing ioutil.ReadAll", conn.ID,err.Error()) failureLog = append(failureLog,fmt.Sprintf("%s failed due to %s",url,err.Error())) continue PeerLoop } responseStatus = response.Status if ( responseStatus == "301 Moved Permanently" ) { v := response.Header["Location"] failureLog = append(failureLog,fmt.Sprintf("%s redirected me to %s",url,v[0])) url = v[0] continue PeerLoop } else if ( responseStatus == "200 OK" ) { trace("%s: api call OK, returning",conn.ID) return responseBody, nil } else { trace("%s: got error in responseStatus: %s", conn.ID, responseStatus) failureLog = append(failureLog,fmt.Sprintf("%s failed, got: %s",url,response.Status)) continue PeerLoop } } } // if we got here, all peers failed. Let's build a verbose error message var stringBuffer bytes.Buffer stringBuffer.WriteString("tried all peers unsuccessfully. here are the results:\n") for n, v := range failureLog { stringBuffer.WriteString(fmt.Sprintf(" peer #%d: %s\n",n,v)) } return responseBody, errors.New(stringBuffer.String()) }