github.com/qxnw/lib4go@v0.0.0-20180426074627-c80c7e84b925/influxdb/v2/client.go (about) 1 // Package client (v2) is the current official Go client for InfluxDB. 2 package client // import "github.com/influxdata/influxdb/client/v2" 3 4 import ( 5 "bytes" 6 "crypto/tls" 7 "encoding/json" 8 "errors" 9 "fmt" 10 "io" 11 "io/ioutil" 12 "net/http" 13 "net/url" 14 "strconv" 15 "strings" 16 "time" 17 18 "github.com/qxnw/influxdb/models" 19 ) 20 21 // HTTPConfig is the config data needed to create an HTTP Client. 22 type HTTPConfig struct { 23 // Addr should be of the form "http://host:port" 24 // or "http://[ipv6-host%zone]:port". 25 Addr string 26 27 // Username is the influxdb username, optional. 28 Username string 29 30 // Password is the influxdb password, optional. 31 Password string 32 33 // UserAgent is the http User Agent, defaults to "InfluxDBClient". 34 UserAgent string 35 36 // Timeout for influxdb writes, defaults to no timeout. 37 Timeout time.Duration 38 39 // InsecureSkipVerify gets passed to the http client, if true, it will 40 // skip https certificate verification. Defaults to false. 41 InsecureSkipVerify bool 42 43 // TLSConfig allows the user to set their own TLS config for the HTTP 44 // Client. If set, this option overrides InsecureSkipVerify. 45 TLSConfig *tls.Config 46 } 47 48 // BatchPointsConfig is the config data needed to create an instance of the BatchPoints struct. 49 type BatchPointsConfig struct { 50 // Precision is the write precision of the points, defaults to "ns". 51 Precision string 52 53 // Database is the database to write points to. 54 Database string 55 56 // RetentionPolicy is the retention policy of the points. 57 RetentionPolicy string 58 59 // Write consistency is the number of servers required to confirm write. 60 WriteConsistency string 61 } 62 63 // Client is a client interface for writing & querying the database. 64 type Client interface { 65 // Ping checks that status of cluster, and will always return 0 time and no 66 // error for UDP clients. 67 Ping(timeout time.Duration) (time.Duration, string, error) 68 69 // Write takes a BatchPoints object and writes all Points to InfluxDB. 70 Write(bp BatchPoints) error 71 72 // Query makes an InfluxDB Query on the database. This will fail if using 73 // the UDP client. 74 Query(q Query) (*Response, error) 75 76 // Close releases any resources a Client may be using. 77 Close() error 78 } 79 80 // NewHTTPClient returns a new Client from the provided config. 81 // Client is safe for concurrent use by multiple goroutines. 82 func NewHTTPClient(conf HTTPConfig) (Client, error) { 83 if conf.UserAgent == "" { 84 conf.UserAgent = "InfluxDBClient" 85 } 86 87 u, err := url.Parse(conf.Addr) 88 if err != nil { 89 return nil, err 90 } else if u.Scheme != "http" && u.Scheme != "https" { 91 m := fmt.Sprintf("Unsupported protocol scheme: %s, your address"+ 92 " must start with http:// or https://", u.Scheme) 93 return nil, errors.New(m) 94 } 95 96 tr := &http.Transport{ 97 TLSClientConfig: &tls.Config{ 98 InsecureSkipVerify: conf.InsecureSkipVerify, 99 }, 100 } 101 if conf.TLSConfig != nil { 102 tr.TLSClientConfig = conf.TLSConfig 103 } 104 return &client{ 105 url: *u, 106 username: conf.Username, 107 password: conf.Password, 108 useragent: conf.UserAgent, 109 httpClient: &http.Client{ 110 Timeout: conf.Timeout, 111 Transport: tr, 112 }, 113 transport: tr, 114 }, nil 115 } 116 117 // Ping will check to see if the server is up with an optional timeout on waiting for leader. 118 // Ping returns how long the request took, the version of the server it connected to, and an error if one occurred. 119 func (c *client) Ping(timeout time.Duration) (time.Duration, string, error) { 120 now := time.Now() 121 u := c.url 122 u.Path = "ping" 123 124 req, err := http.NewRequest("GET", u.String(), nil) 125 if err != nil { 126 return 0, "", err 127 } 128 129 req.Header.Set("User-Agent", c.useragent) 130 131 if c.username != "" { 132 req.SetBasicAuth(c.username, c.password) 133 } 134 135 if timeout > 0 { 136 params := req.URL.Query() 137 params.Set("wait_for_leader", fmt.Sprintf("%.0fs", timeout.Seconds())) 138 req.URL.RawQuery = params.Encode() 139 } 140 141 resp, err := c.httpClient.Do(req) 142 if err != nil { 143 return 0, "", err 144 } 145 defer resp.Body.Close() 146 147 body, err := ioutil.ReadAll(resp.Body) 148 if err != nil { 149 return 0, "", err 150 } 151 152 if resp.StatusCode != http.StatusNoContent { 153 var err = fmt.Errorf(string(body)) 154 return 0, "", err 155 } 156 157 version := resp.Header.Get("X-Influxdb-Version") 158 return time.Since(now), version, nil 159 } 160 161 // Close releases the client's resources. 162 func (c *client) Close() error { 163 c.transport.CloseIdleConnections() 164 return nil 165 } 166 167 // client is safe for concurrent use as the fields are all read-only 168 // once the client is instantiated. 169 type client struct { 170 // N.B - if url.UserInfo is accessed in future modifications to the 171 // methods on client, you will need to syncronise access to url. 172 url url.URL 173 username string 174 password string 175 useragent string 176 httpClient *http.Client 177 transport *http.Transport 178 } 179 180 // BatchPoints is an interface into a batched grouping of points to write into 181 // InfluxDB together. BatchPoints is NOT thread-safe, you must create a separate 182 // batch for each goroutine. 183 type BatchPoints interface { 184 // AddPoint adds the given point to the Batch of points. 185 AddPoint(p *Point) 186 // AddPoints adds the given points to the Batch of points. 187 AddPoints(ps []*Point) 188 // Points lists the points in the Batch. 189 Points() []*Point 190 191 // Precision returns the currently set precision of this Batch. 192 Precision() string 193 // SetPrecision sets the precision of this batch. 194 SetPrecision(s string) error 195 196 // Database returns the currently set database of this Batch. 197 Database() string 198 // SetDatabase sets the database of this Batch. 199 SetDatabase(s string) 200 201 // WriteConsistency returns the currently set write consistency of this Batch. 202 WriteConsistency() string 203 // SetWriteConsistency sets the write consistency of this Batch. 204 SetWriteConsistency(s string) 205 206 // RetentionPolicy returns the currently set retention policy of this Batch. 207 RetentionPolicy() string 208 // SetRetentionPolicy sets the retention policy of this Batch. 209 SetRetentionPolicy(s string) 210 } 211 212 // NewBatchPoints returns a BatchPoints interface based on the given config. 213 func NewBatchPoints(conf BatchPointsConfig) (BatchPoints, error) { 214 if conf.Precision == "" { 215 conf.Precision = "ns" 216 } 217 if _, err := time.ParseDuration("1" + conf.Precision); err != nil { 218 return nil, err 219 } 220 bp := &batchpoints{ 221 database: conf.Database, 222 precision: conf.Precision, 223 retentionPolicy: conf.RetentionPolicy, 224 writeConsistency: conf.WriteConsistency, 225 } 226 return bp, nil 227 } 228 229 type batchpoints struct { 230 points []*Point 231 database string 232 precision string 233 retentionPolicy string 234 writeConsistency string 235 } 236 237 func (bp *batchpoints) AddPoint(p *Point) { 238 bp.points = append(bp.points, p) 239 } 240 241 func (bp *batchpoints) AddPoints(ps []*Point) { 242 bp.points = append(bp.points, ps...) 243 } 244 245 func (bp *batchpoints) Points() []*Point { 246 return bp.points 247 } 248 249 func (bp *batchpoints) Precision() string { 250 return bp.precision 251 } 252 253 func (bp *batchpoints) Database() string { 254 return bp.database 255 } 256 257 func (bp *batchpoints) WriteConsistency() string { 258 return bp.writeConsistency 259 } 260 261 func (bp *batchpoints) RetentionPolicy() string { 262 return bp.retentionPolicy 263 } 264 265 func (bp *batchpoints) SetPrecision(p string) error { 266 if _, err := time.ParseDuration("1" + p); err != nil { 267 return err 268 } 269 bp.precision = p 270 return nil 271 } 272 273 func (bp *batchpoints) SetDatabase(db string) { 274 bp.database = db 275 } 276 277 func (bp *batchpoints) SetWriteConsistency(wc string) { 278 bp.writeConsistency = wc 279 } 280 281 func (bp *batchpoints) SetRetentionPolicy(rp string) { 282 bp.retentionPolicy = rp 283 } 284 285 // Point represents a single data point. 286 type Point struct { 287 pt models.Point 288 } 289 290 // NewPoint returns a point with the given timestamp. If a timestamp is not 291 // given, then data is sent to the database without a timestamp, in which case 292 // the server will assign local time upon reception. NOTE: it is recommended to 293 // send data with a timestamp. 294 func NewPoint( 295 name string, 296 tags map[string]string, 297 fields map[string]interface{}, 298 t ...time.Time, 299 ) (*Point, error) { 300 var T time.Time 301 if len(t) > 0 { 302 T = t[0] 303 } 304 305 pt, err := models.NewPoint(name, models.NewTags(tags), fields, T) 306 if err != nil { 307 return nil, err 308 } 309 return &Point{ 310 pt: pt, 311 }, nil 312 } 313 314 // String returns a line-protocol string of the Point. 315 func (p *Point) String() string { 316 return p.pt.String() 317 } 318 319 // PrecisionString returns a line-protocol string of the Point, 320 // with the timestamp formatted for the given precision. 321 func (p *Point) PrecisionString(precison string) string { 322 return p.pt.PrecisionString(precison) 323 } 324 325 // Name returns the measurement name of the point. 326 func (p *Point) Name() string { 327 return p.pt.Name() 328 } 329 330 // Tags returns the tags associated with the point. 331 func (p *Point) Tags() map[string]string { 332 return p.pt.Tags().Map() 333 } 334 335 // Time return the timestamp for the point. 336 func (p *Point) Time() time.Time { 337 return p.pt.Time() 338 } 339 340 // UnixNano returns timestamp of the point in nanoseconds since Unix epoch. 341 func (p *Point) UnixNano() int64 { 342 return p.pt.UnixNano() 343 } 344 345 // Fields returns the fields for the point. 346 func (p *Point) Fields() (map[string]interface{}, error) { 347 return p.pt.Fields() 348 } 349 350 // NewPointFrom returns a point from the provided models.Point. 351 func NewPointFrom(pt models.Point) *Point { 352 return &Point{pt: pt} 353 } 354 355 func (c *client) Write(bp BatchPoints) error { 356 var b bytes.Buffer 357 358 for _, p := range bp.Points() { 359 if _, err := b.WriteString(p.pt.PrecisionString(bp.Precision())); err != nil { 360 return err 361 } 362 363 if err := b.WriteByte('\n'); err != nil { 364 return err 365 } 366 } 367 368 u := c.url 369 u.Path = "write" 370 req, err := http.NewRequest("POST", u.String(), &b) 371 if err != nil { 372 return err 373 } 374 req.Header.Set("Content-Type", "") 375 req.Header.Set("User-Agent", c.useragent) 376 if c.username != "" { 377 req.SetBasicAuth(c.username, c.password) 378 } 379 380 params := req.URL.Query() 381 params.Set("db", bp.Database()) 382 params.Set("rp", bp.RetentionPolicy()) 383 params.Set("precision", bp.Precision()) 384 params.Set("consistency", bp.WriteConsistency()) 385 req.URL.RawQuery = params.Encode() 386 387 resp, err := c.httpClient.Do(req) 388 if err != nil { 389 return err 390 } 391 defer resp.Body.Close() 392 393 body, err := ioutil.ReadAll(resp.Body) 394 if err != nil { 395 return err 396 } 397 398 if resp.StatusCode != http.StatusNoContent && resp.StatusCode != http.StatusOK { 399 var err = fmt.Errorf(string(body)) 400 return err 401 } 402 403 return nil 404 } 405 406 // Query defines a query to send to the server. 407 type Query struct { 408 Command string 409 Database string 410 Precision string 411 Chunked bool 412 ChunkSize int 413 Parameters map[string]interface{} 414 } 415 416 // NewQuery returns a query object. 417 // The database and precision arguments can be empty strings if they are not needed for the query. 418 func NewQuery(command, database, precision string) Query { 419 return Query{ 420 Command: command, 421 Database: database, 422 Precision: precision, 423 Parameters: make(map[string]interface{}), 424 } 425 } 426 427 // NewQueryWithParameters returns a query object. 428 // The database and precision arguments can be empty strings if they are not needed for the query. 429 // parameters is a map of the parameter names used in the command to their values. 430 func NewQueryWithParameters(command, database, precision string, parameters map[string]interface{}) Query { 431 return Query{ 432 Command: command, 433 Database: database, 434 Precision: precision, 435 Parameters: parameters, 436 } 437 } 438 439 // Response represents a list of statement results. 440 type Response struct { 441 Results []Result 442 Err string `json:"error,omitempty"` 443 } 444 445 // Error returns the first error from any statement. 446 // It returns nil if no errors occurred on any statements. 447 func (r *Response) Error() error { 448 if r.Err != "" { 449 return fmt.Errorf(r.Err) 450 } 451 for _, result := range r.Results { 452 if result.Err != "" { 453 return fmt.Errorf(result.Err) 454 } 455 } 456 return nil 457 } 458 459 // Message represents a user message. 460 type Message struct { 461 Level string 462 Text string 463 } 464 465 // Result represents a resultset returned from a single statement. 466 type Result struct { 467 Series []models.Row 468 Messages []*Message 469 Err string `json:"error,omitempty"` 470 } 471 472 // Query sends a command to the server and returns the Response. 473 func (c *client) Query(q Query) (*Response, error) { 474 u := c.url 475 u.Path = "query" 476 477 jsonParameters, err := json.Marshal(q.Parameters) 478 479 if err != nil { 480 return nil, err 481 } 482 483 req, err := http.NewRequest("POST", u.String(), nil) 484 if err != nil { 485 return nil, err 486 } 487 488 req.Header.Set("Content-Type", "") 489 req.Header.Set("User-Agent", c.useragent) 490 491 if c.username != "" { 492 req.SetBasicAuth(c.username, c.password) 493 } 494 495 params := req.URL.Query() 496 params.Set("q", q.Command) 497 params.Set("db", q.Database) 498 params.Set("params", string(jsonParameters)) 499 if q.Chunked { 500 params.Set("chunked", "true") 501 if q.ChunkSize > 0 { 502 params.Set("chunk_size", strconv.Itoa(q.ChunkSize)) 503 } 504 } 505 506 if q.Precision != "" { 507 params.Set("epoch", q.Precision) 508 } 509 req.URL.RawQuery = params.Encode() 510 511 resp, err := c.httpClient.Do(req) 512 if err != nil { 513 return nil, err 514 } 515 defer resp.Body.Close() 516 517 var response Response 518 if q.Chunked { 519 cr := NewChunkedResponse(resp.Body) 520 for { 521 r, err := cr.NextResponse() 522 if err != nil { 523 // If we got an error while decoding the response, send that back. 524 return nil, err 525 } 526 527 if r == nil { 528 break 529 } 530 531 response.Results = append(response.Results, r.Results...) 532 if r.Err != "" { 533 response.Err = r.Err 534 break 535 } 536 } 537 } else { 538 dec := json.NewDecoder(resp.Body) 539 dec.UseNumber() 540 decErr := dec.Decode(&response) 541 542 // ignore this error if we got an invalid status code 543 if decErr != nil && decErr.Error() == "EOF" && resp.StatusCode != http.StatusOK { 544 decErr = nil 545 } 546 // If we got a valid decode error, send that back 547 if decErr != nil { 548 return nil, fmt.Errorf("unable to decode json: received status code %d err: %s", resp.StatusCode, decErr) 549 } 550 } 551 // If we don't have an error in our json response, and didn't get statusOK 552 // then send back an error 553 if resp.StatusCode != http.StatusOK && response.Error() == nil { 554 return &response, fmt.Errorf("received status code %d from server", 555 resp.StatusCode) 556 } 557 return &response, nil 558 } 559 560 // duplexReader reads responses and writes it to another writer while 561 // satisfying the reader interface. 562 type duplexReader struct { 563 r io.Reader 564 w io.Writer 565 } 566 567 func (r *duplexReader) Read(p []byte) (n int, err error) { 568 n, err = r.r.Read(p) 569 if err == nil { 570 r.w.Write(p[:n]) 571 } 572 return n, err 573 } 574 575 // ChunkedResponse represents a response from the server that 576 // uses chunking to stream the output. 577 type ChunkedResponse struct { 578 dec *json.Decoder 579 duplex *duplexReader 580 buf bytes.Buffer 581 } 582 583 // NewChunkedResponse reads a stream and produces responses from the stream. 584 func NewChunkedResponse(r io.Reader) *ChunkedResponse { 585 resp := &ChunkedResponse{} 586 resp.duplex = &duplexReader{r: r, w: &resp.buf} 587 resp.dec = json.NewDecoder(resp.duplex) 588 resp.dec.UseNumber() 589 return resp 590 } 591 592 // NextResponse reads the next line of the stream and returns a response. 593 func (r *ChunkedResponse) NextResponse() (*Response, error) { 594 var response Response 595 596 if err := r.dec.Decode(&response); err != nil { 597 if err == io.EOF { 598 return nil, nil 599 } 600 // A decoding error happened. This probably means the server crashed 601 // and sent a last-ditch error message to us. Ensure we have read the 602 // entirety of the connection to get any remaining error text. 603 io.Copy(ioutil.Discard, r.duplex) 604 return nil, errors.New(strings.TrimSpace(r.buf.String())) 605 } 606 607 r.buf.Reset() 608 return &response, nil 609 }