func doChunkedResponse(w http.ResponseWriter, resp *http.Response, client *httputil.ClientConn) { wf, ok := w.(writeFlusher) if !ok { http.Error(w, "Error forwarding chunked response body: flush not available", http.StatusInternalServerError) return } w.WriteHeader(resp.StatusCode) up, remaining := client.Hijack() defer up.Close() var err error chunks := bufio.NewScanner(io.MultiReader(remaining, up)) chunks.Split(splitChunks) for chunks.Scan() && err == nil { _, err = wf.Write(chunks.Bytes()) wf.Flush() } if err == nil { err = chunks.Err() } if err != nil { Log.Errorf("Error forwarding chunked response body: %s", err) } }
func requestExecute(conn net.Conn, client *httputil.ClientConn, req *http.Request) (*http.Response, error) { if Verbose { fmt.Printf("> %s %s %s\n", req.Method, req.URL.Path, req.Proto) fmt.Printf("> Socket: %s\n", conn.RemoteAddr()) for k, v := range req.Header { fmt.Printf("> %s: %s\n", k, v) } fmt.Println("> Content-Length:", req.ContentLength) } return client.Do(req) }
func (ctx *runContext) connectToRunServer() (*http.Response, net.Conn, error) { if ctx.attachURL == "" { return nil, nil, errgo.New("No attach URL to connect to") } req, err := http.NewRequest("CONNECT", ctx.attachURL, nil) if err != nil { return nil, nil, errgo.Mask(err, errgo.Any) } req.SetBasicAuth("", config.AuthenticatedUser.AuthenticationToken) url, err := url.Parse(ctx.attachURL) if err != nil { return nil, nil, errgo.Mask(err, errgo.Any) } dial, err := net.Dial("tcp", url.Host) if err != nil { return nil, nil, errgo.Mask(err, errgo.Any) } var conn *httputil.ClientConn if url.Scheme == "https" { host := strings.Split(url.Host, ":")[0] config := *config.TlsConfig config.ServerName = host tls_conn := tls.Client(dial, &config) conn = httputil.NewClientConn(tls_conn, nil) } else if url.Scheme == "http" { conn = httputil.NewClientConn(dial, nil) } else { return nil, nil, errgo.Newf("Invalid scheme format %s", url.Scheme) } res, err := conn.Do(req) if err != httputil.ErrPersistEOF && err != nil { if err, ok := err.(*net.OpError); ok { if err.Err.Error() == "record overflow" { return nil, nil, errgo.Newf( "Fail to create a secure connection to Scalingo server\n"+ "The encountered error is: %v (ID: CLI-1001)\n"+ "Your firewall or proxy may block the connection to %s", err, url.Host, ) } } return nil, nil, errgo.Mask(err, errgo.Any) } connection, _ := conn.Hijack() return res, connection, nil }
func hijack(w http.ResponseWriter, client *httputil.ClientConn) (down net.Conn, downBuf *bufio.ReadWriter, up net.Conn, remaining io.Reader, err error) { hj, ok := w.(http.Hijacker) if !ok { err = errors.New("Unable to cast to Hijack") return } down, downBuf, err = hj.Hijack() if err != nil { return } up, remaining = client.Hijack() return }
func (p *Peer) enterLoop(con *httputil.ClientConn) { c, rw := con.Hijack() reader := json.NewDecoder(rw) writer := json.NewEncoder(c) log.Printf("################################################################### Entering peer loop for peer: %s\n", p.Name) for { /* switch { case <-p.stopForward: log.Printf("Peer %s received a stop request\n", p.Name) o := make(chan bool) go func(){ p.stopForward <- true o <- true }() <-o return default: */ execCmd := &ExecuteCommand{} execCmdReply := &ExecuteCommandReply{} err := reader.Decode(execCmd) if err != nil { log.Printf("############## Peer %s Decoding incoming message failed: %v\n", p.Name, err) return } else { log.Printf("############ Peer %s Decoding incoming message success: %v\n", p.Name, execCmd) iface, err := p.server.Do(execCmd) log.Printf("############# Peer %s Executed command. Result: %v, Error: %v\n", p.Name, iface, err) execCmdReply.Error = err out, ok := iface.(*sql.Output) if ok { execCmdReply.Output = *out } } err = writer.Encode(execCmdReply) if err != nil { return } // } } }
func assertServerResponse(client *httputil.ClientConn, req *http.Request) { var resp *http.Response var err error for i := 0; i < 3; i++ { resp, err = client.Do(req) Expect(err).ToNot(HaveOccurred()) Expect(resp).ToNot(BeNil()) resp.Body.Close() if resp.StatusCode == http.StatusOK { break } time.Sleep(10 * time.Millisecond) } Expect(resp.StatusCode).To(Equal(http.StatusOK)) }
func (s *httpService) handle(req *http.Request, sc *httputil.ServerConn, tls, sticky bool) { for { req.Header.Set("X-Request-Start", strconv.FormatInt(time.Now().UnixNano()/int64(time.Millisecond), 10)) var backend *httputil.ClientConn var stickyCookie *http.Cookie if sticky { backend, stickyCookie = s.getBackendSticky(req) } else { backend = s.getBackend() } if backend == nil { log.Println("no backend found") fail(sc, req, 503, "Service Unavailable") return } if req.Method != "GET" && req.Method != "POST" && req.Method != "HEAD" && req.Method != "OPTIONS" && req.Method != "PUT" && req.Method != "DELETE" && req.Method != "TRACE" { fail(sc, req, 405, "Method not allowed") return } req.Proto = "HTTP/1.1" req.ProtoMajor = 1 req.ProtoMinor = 1 delete(req.Header, "Te") delete(req.Header, "Transfer-Encoding") if clientIP, _, err := net.SplitHostPort(req.RemoteAddr); err == nil { // If we aren't the first proxy retain prior // X-Forwarded-For information as a comma+space // separated list and fold multiple headers into one. if prior, ok := req.Header["X-Forwarded-For"]; ok { clientIP = strings.Join(prior, ", ") + ", " + clientIP } req.Header.Set("X-Forwarded-For", clientIP) } if tls { req.Header.Set("X-Forwarded-Proto", "https") } else { req.Header.Set("X-Forwarded-Proto", "http") } // TODO: Set X-Forwarded-Port if err := backend.Write(req); err != nil { log.Println("server write err:", err) return } res, err := backend.Read(req) if res != nil { if stickyCookie != nil { res.Header.Add("Set-Cookie", stickyCookie.String()) } if res.StatusCode == http.StatusSwitchingProtocols { res.Body = nil } if err := sc.Write(req, res); err != nil { if err != io.EOF && err != httputil.ErrPersistEOF { log.Println("client write err:", err) // TODO: log error } return } } if err != nil { if err != io.EOF && err != httputil.ErrPersistEOF { log.Println("server read err:", err) // TODO: log error fail(sc, req, 502, "Bad Gateway") } return } // TODO: Proxy HTTP CONNECT? (example: Go RPC over HTTP) if res.StatusCode == http.StatusSwitchingProtocols { serverW, serverR := backend.Hijack() clientW, clientR := sc.Hijack() defer serverW.Close() done := make(chan struct{}) go func() { serverR.WriteTo(clientW) if cw, ok := clientW.(writeCloser); ok { cw.CloseWrite() } close(done) }() clientR.WriteTo(serverW) serverW.(writeCloser).CloseWrite() <-done return } // close the backend connection, so we don't accidently send to // a closed socket on the backend backend.Close() // TODO: http pipelining req, err = sc.Read() if err != nil { if err != io.EOF && err != httputil.ErrPersistEOF { log.Println("client read err:", err) } return } } }
func (s *httpService) handle(req *http.Request, sc *httputil.ServerConn, tls, sticky bool) (done bool) { req.Header.Set("X-Request-Start", strconv.FormatInt(time.Now().UnixNano()/int64(time.Millisecond), 10)) req.Header.Set("X-Request-Id", random.UUID()) var backend *httputil.ClientConn var stickyCookie *http.Cookie if sticky { backend, stickyCookie = s.getBackendSticky(req) } else { backend = s.getBackend() } if backend == nil { log.Println("no backend found") fail(sc, req, 503, "Service Unavailable") return } defer backend.Close() req.Proto = "HTTP/1.1" req.ProtoMajor = 1 req.ProtoMinor = 1 delete(req.Header, "Te") delete(req.Header, "Transfer-Encoding") if clientIP, _, err := net.SplitHostPort(req.RemoteAddr); err == nil { // If we aren't the first proxy retain prior // X-Forwarded-For information as a comma+space // separated list and fold multiple headers into one. if prior, ok := req.Header["X-Forwarded-For"]; ok { clientIP = strings.Join(prior, ", ") + ", " + clientIP } req.Header.Set("X-Forwarded-For", clientIP) } if tls { req.Header.Set("X-Forwarded-Proto", "https") } else { req.Header.Set("X-Forwarded-Proto", "http") } // TODO: Set X-Forwarded-Port // Pass the Request-URI verbatim without any modifications req.URL.Opaque = strings.Split(strings.TrimPrefix(req.RequestURI, req.URL.Scheme+":"), "?")[0] if err := backend.Write(req); err != nil { log.Println("server write err:", err) // TODO: return error to client here return true } res, err := backend.Read(req) if res != nil { if stickyCookie != nil { res.Header.Add("Set-Cookie", stickyCookie.String()) } if res.StatusCode == http.StatusSwitchingProtocols { res.Body = nil } if err := sc.Write(req, res); err != nil { if err != io.EOF && err != httputil.ErrPersistEOF { log.Println("client write err:", err) // TODO: log error } return true } } if err != nil { if err != io.EOF && err != httputil.ErrPersistEOF { log.Println("server read err:", err) // TODO: log error fail(sc, req, 502, "Bad Gateway") } return } // TODO: Proxy HTTP CONNECT? (example: Go RPC over HTTP) if res.StatusCode == http.StatusSwitchingProtocols { serverW, serverR := backend.Hijack() clientW, clientR := sc.Hijack() defer serverW.Close() done := make(chan struct{}) go func() { serverR.WriteTo(clientW) if cw, ok := clientW.(writeCloser); ok { cw.CloseWrite() } close(done) }() clientR.WriteTo(serverW) serverW.(writeCloser).CloseWrite() <-done return true } return }
// function to do the actual testing and output the necessary bits // we don't really care about ordering here otherwise we'd need to do a better job of organizing the output func (w *Worker) testHost(work *Work) { // more debug w.dbg(3, "Ok.. prepping to test %s\n", work.target) // pre-define myConn/tlsConn/client var myConn net.Conn var tlsConn *tls.Conn var client *httputil.ClientConn // need a couple of vars for holding ip addresses and default them to being empty var ip4 string = "" var ip6 string = "" // and a var to stash our full host:port combo in var address string = "" // create a buffer we'll use later for reading client body buffer := make([]byte, 1024) // build our request request, err := http.NewRequest("GET", work.url, nil) if err != nil { w.dbgError("Failed to build request for %s : %s\n", work.url, err) return } if config.ua == "" { request.Header.Set("User-Agent", version) } else { request.Header.Set("User-Agent", config.ua) } // debug w.dbg(3, "Request: %+v\n", request) // set a timer for the connection timestart := time.Now() ipaddr, ierr := net.LookupHost(work.target) if ierr != nil { w.dbgError("Failed to lookup %s : %s", work.target, ierr) return } for _, x := range ipaddr { if len(x) > 16 { if ip6 == "" { ip6 = x } } else if ip4 == "" { ip4 = x } } timestop := time.Now() iplookup := (timestop.Sub(timestart)) // build our address string if config.tcpfour { address = fmt.Sprintf("%s:%d", ip4, work.port) } else { address = fmt.Sprintf("[%s]:%d", ip6, work.port) } // debug w.dbg(1, "Attempting to connect to %s\n", address) // since we want some very low level access to bits and pieces, we're going to have to use tcp dial vs the native http client // create a net.conn w.dbg(1, "Connecting to %s\n", address) if config.tcpfour { myConn, err = net.DialTimeout("tcp4", address, time.Duration(config.timeout)*time.Second) } else if config.tcpsix { myConn, err = net.DialTimeout("tcp6", address, time.Duration(config.timeout)*time.Second) } else { myConn, err = net.DialTimeout("tcp", address, time.Duration(config.timeout)*time.Second) } if err != nil { w.dbgError("Could not connect to %s : %s\n", address, err) return } w.dbg(2, "Connected to %s\n", address) // get a time reading on how long it took to connect to the socket timestop = time.Now() tcpConnect := (timestop.Sub(timestart)) // defer close defer myConn.Close() // need to add some deadlines so we don't sit around indefintely - 5s is more than sufficient myConn.SetDeadline(time.Now().Add(time.Duration(5 * time.Second))) // if we're an ssl connection, we need a few extra steps here if work.ssl { w.dbg(1, "Starting SSL procedures...\n") // default to allowing insecure ssl tlsConfig := tls.Config{InsecureSkipVerify: true} // create a real tls connection tlsConn = tls.Client(myConn, &tlsConfig) // do our SSL negotiation err = tlsConn.Handshake() if err != nil { w.dbgError("Could not negotiate tls handshake on %s : %s\n", address, err) return } // defer closing this connection as well defer tlsConn.Close() } // get a time reading on how long it took to negotiate ssl timestop = time.Now() sslHandshake := (timestop.Sub(timestart)) // get our state if work.ssl { state := tlsConn.ConnectionState() w.dbg(2, "Handshake Complete: %t\n", state.HandshakeComplete) w.dbg(2, "Mutual: %t\n", state.NegotiatedProtocolIsMutual) } // debug w.dbg(3, "Converting to an HTTP client connection...\n") // convert to an http connection if work.ssl { client = httputil.NewProxyClientConn(tlsConn, nil) } else { client = httputil.NewProxyClientConn(myConn, nil) } // debug w.dbg(1, "Making GET request\n") // write our request to the socket err = client.Write(request) if err != nil { w.dbgError("Error writing request : %s\n", err) return } // read our response headers response, err := client.Read(request) if err != nil { // did we get a 400? if response.StatusCode == 400 { w.dbgError("400 response received.. \n") return } // did we get a 404? if response.StatusCode == 404 { w.dbgError("404 response received.. \n") return } // any other error, exit out w.dbgError("Error reading response : %s\n", err) return } w.dbg(1, "Status: %s\n", response.Status) // did we get a response? if len(response.Header) == 0 { w.dbgError("0 length response, something probably broke") return } // measure response header time timestop = time.Now() respTime := (timestop.Sub(timestart)) // defer close since we still want to read the body of the object defer response.Body.Close() // build a reader br := bufio.NewReader(response.Body) // now read the first byte c, err := br.ReadByte() if err != nil { w.dbgError("Could not read data: %s\n", err) return } // measure our first byte time, this is normally 0ms however longer periods could be indicative of a problem timestop = time.Now() byteTime := (timestop.Sub(timestart)) // ok, read the rest of the response n, err := br.Read(buffer) count := n for err != io.EOF { n, err = br.Read(buffer) count += n } // did we fail to read everything? if err != nil && err != io.EOF { w.dbgError("Error on data read, continuing with only %d bytes of %s read\n", count+1, response.Header.Get("Content-Length")) } // measure our overall time to proccess the entire transaction timestop = time.Now() totalTime := (timestop.Sub(timestart)) w.dbg(2, "Received %d bytes total\n", count+1) w.dbg(3, "Response: %s%s\n", string(c), string(buffer)) // shut down the client client.Close() // properly close out our other connections myConn.Close() if work.ssl { tlsConn.Close() } if work.gph != nil { // Graphite selected w.dbg(2, "Graphite selected...\n") gname := strings.Replace(work.target, ".", "_", -1) prefix := "" if config.prefix != "" { prefix = fmt.Sprintf("%s/", config.prefix) } if work.ssl { work.gph.PostOne(fmt.Sprintf("%s%s/ssl_port_%d/dns_time", prefix, gname, work.port), float64(iplookup/1000000)) work.gph.PostOne(fmt.Sprintf("%s%s/ssl_port_%d/connect_time", prefix, gname, work.port), float64(tcpConnect/1000000)) work.gph.PostOne(fmt.Sprintf("%s%s/ssl_port_%d/ssl_time", prefix, gname, work.port), float64(sslHandshake/1000000)) work.gph.PostOne(fmt.Sprintf("%s%s/ssl_port_%d/response_time", prefix, gname, work.port), float64(respTime/1000000)) work.gph.PostOne(fmt.Sprintf("%s%s/ssl_port_%d/byte_time", prefix, gname, work.port), float64(byteTime/1000000)) work.gph.PostOne(fmt.Sprintf("%s%s/ssl_port_%d/total_time", prefix, gname, work.port), float64(totalTime/1000000)) } else { work.gph.PostOne(fmt.Sprintf("%s%s/http_port_%d/dns_time", prefix, gname, work.port), float64(iplookup/1000000)) work.gph.PostOne(fmt.Sprintf("%s%s/http_port_%d/connect_time", prefix, gname, work.port), float64(tcpConnect/1000000)) work.gph.PostOne(fmt.Sprintf("%s%s/http_port_%d/response_time", prefix, gname, work.port), float64(respTime/1000000)) work.gph.PostOne(fmt.Sprintf("%s%s/http_port_%d/byte_time", prefix, gname, work.port), float64(byteTime/1000000)) work.gph.PostOne(fmt.Sprintf("%s%s/http_port_%d/total_time", prefix, gname, work.port), float64(totalTime/1000000)) } // if we've defined multiple, output in all defined formats + stdout if !config.multiple { return } } // PublishMetric(host string, instance string, key string, value int64) (error Error) { // add tsdb writing if config.tval != "" { w.dbg(2, "Writing to tsdb server...\n") var METRIC []interface{} var tags map[string]interface{} var metric map[string]interface{} tstamp := int64(time.Now().Unix()) if work.ssl { tags = map[string]interface{}{"host": work.target, "port": work.port, "type": "ssl"} metric = map[string]interface{}{"metric": "handshake_time", "value": int64(sslHandshake / 1000000), "timestamp": tstamp, "tags": tags} METRIC = append(METRIC, metric) } else { tags = map[string]interface{}{"Host": work.target, "Port": work.port, "Type": "http"} } metric = map[string]interface{}{"metric": "dns_lookup", "value": int64(iplookup / 1000000), "timestamp": tstamp, "tags": tags} METRIC = append(METRIC, metric) metric = map[string]interface{}{"metric": "connect_time", "value": int64(tcpConnect / 1000000), "timestamp": tstamp, "tags": tags} METRIC = append(METRIC, metric) metric = map[string]interface{}{"metric": "response_time", "value": int64(respTime / 1000000), "timestamp": tstamp, "tags": tags} METRIC = append(METRIC, metric) metric = map[string]interface{}{"metric": "firstbyte_time", "value": int64(byteTime / 1000000), "timestamp": tstamp, "tags": tags} METRIC = append(METRIC, metric) metric = map[string]interface{}{"metric": "total_time", "value": int64(totalTime / 1000000), "timestamp": tstamp, "tags": tags} METRIC = append(METRIC, metric) b, _ := json.Marshal(METRIC) err = writeTSDB(b) if err != nil { dbg(1, "Error writing to tsdb server: %s\n", err) } // if we've defined multiple, output in all defined formats + stdout if !config.multiple { return } } // add collectd style output if config.collectd { if work.ssl { fmt.Fprintf(os.Stdout, "PUTVAL %s/ssl_port_%d/milliseconds-dns_time interval=%d N:%d\n", work.target, work.port, config.interval, iplookup/1000000) fmt.Fprintf(os.Stdout, "PUTVAL %s/ssl_port_%d/milliseconds-connect_time interval=%d N:%d\n", work.target, work.port, config.interval, tcpConnect/1000000) fmt.Fprintf(os.Stdout, "PUTVAL %s/ssl_port_%d/milliseconds-ssl_time interval=%d N:%d\n", work.target, work.port, config.interval, sslHandshake/1000000) fmt.Fprintf(os.Stdout, "PUTVAL %s/ssl_port_%d/milliseconds-response_time interval=%d N:%d\n", work.target, work.port, config.interval, respTime/1000000) fmt.Fprintf(os.Stdout, "PUTVAL %s/ssl_port_%d/milliseconds-byte_time interval=%d N:%d\n", work.target, work.port, config.interval, byteTime/1000000) fmt.Fprintf(os.Stdout, "PUTVAL %s/ssl_port_%d/milliseconds-total_time interval=%d N:%d\n", work.target, work.port, config.interval, totalTime/1000000) } else { fmt.Fprintf(os.Stdout, "PUTVAL %s/http_port_%d/milliseconds-dns_time interval=%d N:%d\n", work.target, work.port, config.interval, iplookup/1000000) fmt.Fprintf(os.Stdout, "PUTVAL %s/http_port_%d/milliseconds-connect_time interval=%d N:%d\n", work.target, work.port, config.interval, tcpConnect/1000000) fmt.Fprintf(os.Stdout, "PUTVAL %s/http_port_%d/milliseconds-response_time interval=%d N:%d\n", work.target, work.port, config.interval, respTime/1000000) fmt.Fprintf(os.Stdout, "PUTVAL %s/http_port_%d/milliseconds-byte_time interval=%d N:%d\n", work.target, work.port, config.interval, byteTime/1000000) fmt.Fprintf(os.Stdout, "PUTVAL %s/http_port_%d/milliseconds-total_time interval=%d N:%d\n", work.target, work.port, config.interval, totalTime/1000000) } // if we've defined multiple, output in all defined formats + stdout if !config.multiple { return } } // add function for zabbix if config.zabbix { if work.ssl { fmt.Fprintf(os.Stdout, "https_host=%s, port=%d, dns_lookup=%dms, socket_connect=%dms, ssl_negotiation=%dms, response_time=%dms, first_byte=%dms, total_time=%dms ", work.target, work.port, iplookup/1000000, tcpConnect/1000000, sslHandshake/1000000, respTime/1000000, byteTime/1000000, totalTime/1000000) if config.expires { i := 0 state := tlsConn.ConnectionState() for _, v := range state.PeerCertificates { if i == 0 { myT := time.Now() myExpires := v.NotAfter.Sub(myT) fmt.Fprintf(os.Stdout, "expires=%dd\n", myExpires/8.64e13) i++ } } } else { fmt.Fprintf(os.Stdout, "\n") } } else { fmt.Fprintf(os.Stdout, "host=%s, port=%d, dns_lookup=%dms, socket_connect=%dms, response_time=%dms, first_byte=%dms, total_time=%dms\n", work.target, work.port, iplookup/1000000, tcpConnect/1000000, respTime/1000000, byteTime/1000000, totalTime/1000000) } // if we've defined multiple, output in all defined formats + stdout if !config.multiple { return } } // print out our values if (!config.multiple) || (config.verbose) { if work.ssl { log.Printf("Host: %s -> DNS Lookup: %dms, Socket Connect: %dms, SSL Negotiation: %dms, Response Time: %dms, 1st Byte: %dms, Total Time: %dms\n", work.target, iplookup/1000000, tcpConnect/1000000, sslHandshake/1000000, respTime/1000000, byteTime/1000000, totalTime/1000000) } else { log.Printf("Host: %s -> DNS Lookup: %dms, Socket Connect: %dms, Response Time: %dms, 1st Byte: %dms, Total Time: %dms\n", work.target, iplookup/1000000, tcpConnect/1000000, respTime/1000000, byteTime/1000000, totalTime/1000000) } if work.ssl { if config.expires == true { i := 0 state := tlsConn.ConnectionState() for _, v := range state.PeerCertificates { if i == 0 { myT := time.Now() myExpires := v.NotAfter.Sub(myT) fmt.Fprintf(os.Stdout, "Cert expires in: %d days\n", myExpires/8.64e13) i++ } } } } } if config.verbose { log.Printf("StatusCode: %d\n", response.StatusCode) log.Printf("ProtoCol: %s\n", response.Proto) for k, v := range response.Header { log.Printf("%s: %v\n", k, v) } if work.ssl { if config.printcert == true { i := 0 state := tlsConn.ConnectionState() for _, v := range state.PeerCertificates { if i == 0 { sslFrom := v.NotBefore sslTo := v.NotAfter log.Printf("Server key information:") log.Printf("\tCN:\t%v\n\tOU:\t%v\n\tOrg:\t%v\n", v.Subject.CommonName, v.Subject.OrganizationalUnit, v.Subject.Organization) log.Printf("\tCity:\t%v\n\tState:\t%v\n\tCountry:%v\n", v.Subject.Locality, v.Subject.Province, v.Subject.Country) log.Printf("SSL Certificate Valid:\n\tFrom: %v\n\tTo: %v\n", sslFrom, sslTo) log.Printf("Valid Certificate DNS:\n") if len(v.DNSNames) >= 1 { for dns := range v.DNSNames { log.Printf("\t%v\n", v.DNSNames[dns]) } } else { log.Printf("\t%v\n", v.Subject.CommonName) } i++ } else if i == 1 { log.Printf("Issued by:\n\t%v\n\t%v\n\t%v\n", v.Subject.CommonName, v.Subject.OrganizationalUnit, v.Subject.Organization) i++ } else { // we're done here, lets move on break } } } } // throw in a new line to pretty it up log.Printf("") } return }