github.com/pingcap/ticdc@v0.0.0-20220526033649-485a10ef2652/cdc/http_api_handler.go (about)

     1  // Copyright 2021 PingCAP, Inc.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // See the License for the specific language governing permissions and
    12  // limitations under the License.
    13  
    14  package cdc
    15  
    16  import (
    17  	"encoding/json"
    18  	"fmt"
    19  	"net/http"
    20  	"time"
    21  
    22  	"github.com/pingcap/errors"
    23  	"github.com/pingcap/log"
    24  	"github.com/pingcap/ticdc/cdc/model"
    25  	cerror "github.com/pingcap/ticdc/pkg/errors"
    26  	"github.com/pingcap/ticdc/pkg/httputil"
    27  	"github.com/pingcap/tidb/store/tikv/oracle"
    28  	"go.uber.org/zap"
    29  )
    30  
    31  const (
    32  	// apiOpVarChangefeeds is the key of list option in HTTP API
    33  	apiOpVarChangefeeds = "state"
    34  )
    35  
    36  // JSONTime used to wrap time into json format
    37  type JSONTime time.Time
    38  
    39  // MarshalJSON use to specify the time format
    40  func (t JSONTime) MarshalJSON() ([]byte, error) {
    41  	stamp := fmt.Sprintf("\"%s\"", time.Time(t).Format("2006-01-02 15:04:05.000"))
    42  	return []byte(stamp), nil
    43  }
    44  
    45  // err of cdc http api
    46  type httpError struct {
    47  	Error string `json:"error"`
    48  }
    49  
    50  // ChangefeedCommonInfo holds some common usage information of a changefeed and use by RESTful API only.
    51  type ChangefeedCommonInfo struct {
    52  	ID             string              `json:"id"`
    53  	FeedState      model.FeedState     `json:"state"`
    54  	CheckpointTSO  uint64              `json:"checkpoint-tso"`
    55  	CheckpointTime JSONTime            `json:"checkpoint-time"`
    56  	RunningError   *model.RunningError `json:"error"`
    57  }
    58  
    59  // handleHealth check if is this server is health
    60  func (s *Server) handleHealth(w http.ResponseWriter, r *http.Request) {
    61  	w.WriteHeader(http.StatusOK)
    62  }
    63  
    64  // handleChangefeeds dispatch the request to the specified handleFunc according to the request method.
    65  func (s *Server) handleChangefeeds(w http.ResponseWriter, req *http.Request) {
    66  	if req.Method == http.MethodGet {
    67  		s.handleChangefeedsList(w, req)
    68  		return
    69  	}
    70  	// Only the get method is allowed at this stage,
    71  	// if it is not the get method, an error will be returned directly
    72  	writeErrorJSON(w, http.StatusBadRequest, *cerror.ErrSupportGetOnly)
    73  }
    74  
    75  // handleChangefeedsList will only received request with Get method from dispatcher.
    76  func (s *Server) handleChangefeedsList(w http.ResponseWriter, req *http.Request) {
    77  	err := req.ParseForm()
    78  	if err != nil {
    79  		writeInternalServerErrorJSON(w, cerror.WrapError(cerror.ErrInternalServerError, err))
    80  		return
    81  	}
    82  	state := req.Form.Get(apiOpVarChangefeeds)
    83  
    84  	statuses, err := s.etcdClient.GetAllChangeFeedStatus(req.Context())
    85  	if err != nil {
    86  		writeInternalServerErrorJSON(w, err)
    87  		return
    88  	}
    89  
    90  	changefeedIDs := make(map[string]struct{}, len(statuses))
    91  	for cid := range statuses {
    92  		changefeedIDs[cid] = struct{}{}
    93  	}
    94  
    95  	resps := make([]*ChangefeedCommonInfo, 0)
    96  	for changefeedID := range changefeedIDs {
    97  		cfInfo, err := s.etcdClient.GetChangeFeedInfo(req.Context(), changefeedID)
    98  		if err != nil && cerror.ErrChangeFeedNotExists.NotEqual(err) {
    99  			writeInternalServerErrorJSON(w, err)
   100  			return
   101  		}
   102  		if !httputil.IsFiltered(state, cfInfo.State) {
   103  			continue
   104  		}
   105  		cfStatus, _, err := s.etcdClient.GetChangeFeedStatus(req.Context(), changefeedID)
   106  		if err != nil && cerror.ErrChangeFeedNotExists.NotEqual(err) {
   107  			writeInternalServerErrorJSON(w, err)
   108  			return
   109  		}
   110  		resp := &ChangefeedCommonInfo{
   111  			ID: changefeedID,
   112  		}
   113  
   114  		if cfInfo != nil {
   115  			resp.FeedState = cfInfo.State
   116  			resp.RunningError = cfInfo.Error
   117  		}
   118  
   119  		if cfStatus != nil {
   120  			resp.CheckpointTSO = cfStatus.CheckpointTs
   121  			tm := oracle.GetTimeFromTS(cfStatus.CheckpointTs)
   122  			resp.CheckpointTime = JSONTime(tm)
   123  		}
   124  		resps = append(resps, resp)
   125  	}
   126  	writeData(w, resps)
   127  }
   128  
   129  func writeInternalServerErrorJSON(w http.ResponseWriter, err error) {
   130  	writeErrorJSON(w, http.StatusInternalServerError, *cerror.ErrInternalServerError.Wrap(err))
   131  }
   132  
   133  func writeErrorJSON(w http.ResponseWriter, statusCode int, cerr errors.Error) {
   134  	httpErr := httpError{Error: cerr.Error()}
   135  	jsonStr, err := json.MarshalIndent(httpErr, "", " ")
   136  	if err != nil {
   137  		log.Error("invalid json data", zap.Reflect("data", err), zap.Error(err))
   138  		return
   139  	}
   140  	w.WriteHeader(statusCode)
   141  	w.Header().Set("Content-Type", "application/json")
   142  	_, err = w.Write(jsonStr)
   143  	if err != nil {
   144  		log.Error("fail to write data", zap.Error(err))
   145  	}
   146  }