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 }