github.com/google/cloudprober@v0.11.3/probes/external/serverutils/serverutils.go (about)

     1  // Copyright 2017 The Cloudprober Authors.
     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  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  // Package serverutils provides utilities to work with the cloudprober's external probe.
    16  package serverutils
    17  
    18  import (
    19  	"bufio"
    20  	"fmt"
    21  	"io"
    22  	"log"
    23  	"os"
    24  	"strconv"
    25  	"strings"
    26  	"time"
    27  
    28  	"github.com/golang/protobuf/proto"
    29  	serverpb "github.com/google/cloudprober/probes/external/proto"
    30  )
    31  
    32  func readPayload(r *bufio.Reader) ([]byte, error) {
    33  	// header format is: "\nContent-Length: %d\n\n"
    34  	const prefix = "Content-Length: "
    35  	var line string
    36  	var length int
    37  	var err error
    38  
    39  	// Read lines until header line is found
    40  	for {
    41  		line, err = r.ReadString('\n')
    42  		if err != nil {
    43  			return nil, err
    44  		}
    45  		if strings.HasPrefix(line, prefix) {
    46  			break
    47  		}
    48  	}
    49  
    50  	// Parse content length from the header
    51  	length, err = strconv.Atoi(line[len(prefix) : len(line)-1])
    52  	if err != nil {
    53  		return nil, err
    54  	}
    55  	// Consume the blank line following the header line
    56  	if _, err = r.ReadSlice('\n'); err != nil {
    57  		return nil, err
    58  	}
    59  
    60  	// Slurp in the payload
    61  	buf := make([]byte, length)
    62  	if _, err = io.ReadFull(r, buf); err != nil {
    63  		return nil, err
    64  	}
    65  	return buf, nil
    66  }
    67  
    68  // ReadProbeReply reads ProbeReply from the supplied bufio.Reader and returns it to
    69  // the caller.
    70  func ReadProbeReply(r *bufio.Reader) (*serverpb.ProbeReply, error) {
    71  	buf, err := readPayload(r)
    72  	if err != nil {
    73  		return nil, err
    74  	}
    75  	rep := new(serverpb.ProbeReply)
    76  	return rep, proto.Unmarshal(buf, rep)
    77  }
    78  
    79  // ReadProbeRequest reads and parses ProbeRequest protocol buffers from the given
    80  // bufio.Reader.
    81  func ReadProbeRequest(r *bufio.Reader) (*serverpb.ProbeRequest, error) {
    82  	buf, err := readPayload(r)
    83  	if err != nil {
    84  		return nil, err
    85  	}
    86  	req := new(serverpb.ProbeRequest)
    87  	return req, proto.Unmarshal(buf, req)
    88  }
    89  
    90  // WriteMessage marshals the a proto message and writes it to the writer "w"
    91  // with appropriate Content-Length header.
    92  func WriteMessage(pb proto.Message, w io.Writer) error {
    93  	buf, err := proto.Marshal(pb)
    94  	if err != nil {
    95  		return fmt.Errorf("Failed marshalling proto message: %v", err)
    96  	}
    97  	if _, err := fmt.Fprintf(w, "\nContent-Length: %d\n\n%s", len(buf), buf); err != nil {
    98  		return fmt.Errorf("Failed writing response: %v", err)
    99  	}
   100  	return nil
   101  }
   102  
   103  // Serve blocks indefinitely, servicing probe requests. Note that this function is
   104  // provided mainly to help external probe server implementations. Cloudprober doesn't
   105  // make use of it. Example usage:
   106  //	import (
   107  //		serverpb "github.com/google/cloudprober/probes/external/serverutils/server_proto"
   108  //		"github.com/google/cloudprober/probes/external/serverutils/serverutils"
   109  //	)
   110  //	func runProbe(opts []*cppb.ProbeRequest_Option) {
   111  //  	...
   112  //	}
   113  //	serverutils.Serve(func(req *serverpb.ProbeRequest, reply *serverpb.ProbeReply) {
   114  // 		payload, errMsg, _ := runProbe(req.GetOptions())
   115  //		reply.Payload = proto.String(payload)
   116  //		if errMsg != "" {
   117  //			reply.ErrorMessage = proto.String(errMsg)
   118  //		}
   119  //	})
   120  func Serve(probeFunc func(*serverpb.ProbeRequest, *serverpb.ProbeReply)) {
   121  	stdin := bufio.NewReader(os.Stdin)
   122  
   123  	repliesChan := make(chan *serverpb.ProbeReply)
   124  
   125  	// Write replies to stdout. These are not required to be in-order.
   126  	go func() {
   127  		for rep := range repliesChan {
   128  			if err := WriteMessage(rep, os.Stdout); err != nil {
   129  				log.Fatal(err)
   130  			}
   131  
   132  		}
   133  	}()
   134  
   135  	// Read requests from stdin, and dispatch probes to service them.
   136  	for {
   137  		request, err := ReadProbeRequest(stdin)
   138  		if err != nil {
   139  			log.Fatalf("Failed reading request: %v", err)
   140  		}
   141  		go func() {
   142  			reply := &serverpb.ProbeReply{
   143  				RequestId: request.RequestId,
   144  			}
   145  			done := make(chan bool, 1)
   146  			timeout := time.After(time.Duration(*request.TimeLimit) * time.Millisecond)
   147  			go func() {
   148  				probeFunc(request, reply)
   149  				done <- true
   150  			}()
   151  			select {
   152  			case <-done:
   153  				repliesChan <- reply
   154  			case <-timeout:
   155  				// drop the request on the floor.
   156  				fmt.Fprintf(os.Stderr, "Timeout for request %v\n", *reply.RequestId)
   157  			}
   158  		}()
   159  	}
   160  }