<commit_before>// Copyright 2016 Jacob Taylor jacob.taylor@gmail.com
// License: Apache2
package main

import (
    "fmt"
    "net"
    "./utils"
    "bufio"
    "encoding/binary"
    "time"
    "os"
    "bytes"
    "io"
    "os/user"
    "log"
)

func send_export_list_item(output *bufio.Writer, export_name string) {
    data := make([]byte, 1024)
    length := len(export_name)
    offset := 0

    // length of export name
    binary.BigEndian.PutUint32(data[offset:], uint32(length))  // length of string
    offset += 4

    // export name
    copy(data[offset:], export_name)
    offset += length

    reply_type := uint32(2)     // reply_type: NBD_REP_SERVER
    send_message(output, reply_type, uint32(offset), data)
}

func send_ack(output *bufio.Writer) {
    send_message(output, utils.NBD_COMMAND_ACK, 0, nil)
}

func get_user_home_dir() (homedir string) {
    usr, err := user.Current()
    if err != nil {
        log.Fatal(err)
    }
    return usr.HomeDir
}

func export_name(output *bufio.Writer, conn net.Conn, payload_size int, payload []byte) {
    fmt.Printf("have request to bind to: %s\n", string(payload[:payload_size]))

    defer conn.Close()

    var filename bytes.Buffer
    filename.WriteString(get_user_home_dir() + "/sample_disks/")

    actual_filename := string(payload[:payload_size])

    if actual_filename == "export" {
        actual_filename = "test_fake_hdd"
    }

    filename.WriteString(actual_filename)
    fmt.Printf("Opening file: %s", filename.String())

    // attempt to open the file read only
    file, err := os.Open(filename.String())
    utils.ErrorCheck(err)

    buffer := make([]byte, 256)
    offset := 0

    fs, err := file.Stat()
    file_size := uint64(fs.Size())

    binary.BigEndian.PutUint64(buffer[offset:], file_size)  // size
    offset += 8

    binary.BigEndian.PutUint16(buffer[offset:], 1)  // flags
    offset += 2

    data_out, err := output.Write(buffer[:offset])
    output.Flush()
    utils.ErrorCheck(err)
    fmt.Printf("Wrote %d chars: %v\n", data_out, buffer[:offset])

    buffer = make([]byte, 512*1024)
    file_position := uint64(0)
    conn_reader := bufio.NewReader(conn)
    abort := false
    for {

        offset := 0
        waiting_for := 28       // wait for at least the minimum payload size

        for offset < waiting_for {
            length, err := conn_reader.Read(buffer[offset:waiting_for])
            offset += length
            utils.ErrorCheck(err)
            if err == io.EOF {
                abort = true
                break
            }
            utils.LogData("Reading instruction\n", offset, buffer)
            if offset < waiting_for {
                time.Sleep(5 * time.Millisecond)
            }
        }
        if abort {
            fmt.Printf("Abort detected, esaping processing loop\n")
            break
        }

        fmt.Printf("We read the buffer %v\n", buffer[:waiting_for])

        //magic := binary.BigEndian.Uint32(buffer)
        command := binary.BigEndian.Uint32(buffer[4:8])
        handle := binary.BigEndian.Uint64(buffer[8:16])
        from := binary.BigEndian.Uint64(buffer[16:24])
        length := binary.BigEndian.Uint32(buffer[24:28])

        switch command {
        case utils.NBD_COMMAND_READ:
            fmt.Printf("We have a request to read handle: %v, from: %v, length: %v, file_position: %v\n", handle, from, length, file_position)
            fmt.Printf("Read Resquest    Offset:%x length: %v     Handle %X\n", from, length, handle)
            if file_position != from {
                fmt.Printf("Seeking to %v\n", int64(from))
                file.Seek(int64(from), 0)     // seek to the requested position relative to the start of the file
                file_position = from
            }
            data_out, err = file.Read(buffer[16:16+length])
            file_position += uint64(length)
            fmt.Printf("new file position is: %v\n", file_position)
            utils.ErrorCheck(err)

            // Should not be big indian?
            binary.BigEndian.PutUint32(buffer[:4], utils.NBD_REPLY_MAGIC)
            binary.BigEndian.PutUint32(buffer[4:8], 0)                      // error bits

            utils.LogData("About to reply with", int(16+length), buffer)
            fmt.Printf("length of buffer: %v\n", len(buffer[:16+length]))
            fmt.Printf("tail of buffer: %v\n", buffer[length:16+length])

            conn.Write(buffer[:16+length])

            continue
        case utils.NBD_COMMAND_WRITE:
            fmt.Printf("We have a request to write handle: %v, from: %v, length: %v\n", handle, from, length)
            continue
        case utils.NBD_COMMAND_DISCONNECT:
            fmt.Printf("We have received a request to disconnect\n")
            // close the file and return
            return
        }
    }
}

func send_export_list(output *bufio.Writer) {
    export_name_list := []string{"happy_export", "very_happy_export", "third_export"}

    for index := range export_name_list {
        send_export_list_item(output, export_name_list[index])
    }

    send_ack(output)
}

func send_message(output *bufio.Writer, reply_type uint32, length uint32, data []byte ) {
    endian := binary.BigEndian
    buffer := make([]byte, 1024)
    offset := 0

    endian.PutUint64(buffer[offset:], utils.NBD_SERVER_SEND_REPLY_MAGIC)
    offset += 8

    endian.PutUint32(buffer[offset:], uint32(3))  // Flags (3 = supports list)
    offset += 4

    endian.PutUint32(buffer[offset:], reply_type)  // reply_type: NBD_REP_SERVER
    offset += 4

    endian.PutUint32(buffer[offset:], length)  // length of package
    offset += 4

    if data != nil {
        copy(buffer[offset:], data[0:length])
        offset += int(length)
    }

    data_to_send := buffer[:offset]
    output.Write(data_to_send)
    output.Flush()

    utils.LogData("Just sent:", offset, data_to_send)
}

func main() {

    if len(os.Args) <  3 {
        panic("missing arguments:  (ipaddress) (portnumber)")
        return
    }

    listener, err := net.Listen("tcp", os.Args[1] + ":" + os.Args[2])
    utils.ErrorCheck(err)

    fmt.Printf("Hello World, we have %v\n", listener)
    reply_magic := make([]byte, 4)
    binary.BigEndian.PutUint32(reply_magic, utils.NBD_REPLY_MAGIC)

    for {
        conn, err := listener.Accept()
        utils.ErrorCheck(err)

        fmt.Printf("We have a new connection from: %s\n", conn.RemoteAddr())
        output := bufio.NewWriter(conn)

        output.WriteString("NBDMAGIC")      // init password
        output.Flush()
        output.WriteString("IHAVEOPT")      // Magic
        output.Flush()
        output.Write([]byte{0, 3})          // Flags (3 = supports list)
        output.Flush()

        // Fetch the data until we get the initial options
        data := make([]byte, 1024)
        offset := 0
        waiting_for := 16       // wait for at least the minimum payload size

        for offset < waiting_for {
            length, err := conn.Read(data[offset:])
            offset += length
            utils.ErrorCheck(err)
            utils.LogData("Reading instruction", offset, data)
            if offset < waiting_for {
                time.Sleep(5 * time.Millisecond)
            }
        }

        // Skip the first 8 characters (options)
        command := binary.BigEndian.Uint32(data[12:])
        payload_size := int(binary.BigEndian.Uint32(data[16:]))

        fmt.Sprintf("command is: %d\npayload_size is: %d\n", command, payload_size)
        waiting_for += int(payload_size)
        for offset < waiting_for {
            length, err := conn.Read(data[offset:])
            offset += length
            utils.ErrorCheck(err)
            utils.LogData("Reading instruction", offset, data)
            if offset < waiting_for {
                time.Sleep(5 * time.Millisecond)
            }
        }
        payload := make([]byte, payload_size)

        if payload_size > 0{
            copy(payload, data[20:])
        }

        utils.LogData("Payload is:", payload_size, payload)
        fmt.Printf("command is: %v\n", command)

        // At this point, we have the command, payload size, and payload.
        switch command {
        case utils.NBD_COMMAND_LIST:
            send_export_list(output)
            conn.Close()
            break
        case utils.NBD_COMMAND_EXPORT_NAME:
            go export_name(output, conn, payload_size, payload)
            break
        }
    }

}
<commit_msg>Added nbd server directory listing.<commit_after>// Copyright 2016 Jacob Taylor jacob.taylor@gmail.com
// License: Apache2
package main

import (
    "fmt"
    "net"
    "./utils"
    "bufio"
    "encoding/binary"
    "time"
    "os"
    "bytes"
    "io"
    "io/ioutil"
    "os/user"
    "log"
)

const nbd_folder = "/sample_disks/"

func send_export_list_item(output *bufio.Writer, export_name string) {
    data := make([]byte, 1024)
    length := len(export_name)
    offset := 0

    // length of export name
    binary.BigEndian.PutUint32(data[offset:], uint32(length))  // length of string
    offset += 4

    // export name
    copy(data[offset:], export_name)
    offset += length

    reply_type := uint32(2)     // reply_type: NBD_REP_SERVER
    send_message(output, reply_type, uint32(offset), data)
}

func send_ack(output *bufio.Writer) {
    send_message(output, utils.NBD_COMMAND_ACK, 0, nil)
}

func get_user_home_dir() (homedir string) {
    usr, err := user.Current()
    if err != nil {
        log.Fatal(err)
    }
    return usr.HomeDir
}

func export_name(output *bufio.Writer, conn net.Conn, payload_size int, payload []byte) {
    fmt.Printf("have request to bind to: %s\n", string(payload[:payload_size]))

    defer conn.Close()

    var filename bytes.Buffer
    filename.WriteString(get_user_home_dir() + nbd_folder)

    actual_filename := string(payload[:payload_size])

    if actual_filename == "export" {
        actual_filename = "test_fake_hdd"
    }

    filename.WriteString(actual_filename)
    fmt.Printf("Opening file: %s", filename.String())

    // attempt to open the file read only
    file, err := os.Open(filename.String())
    utils.ErrorCheck(err)

    buffer := make([]byte, 256)
    offset := 0

    fs, err := file.Stat()
    file_size := uint64(fs.Size())

    binary.BigEndian.PutUint64(buffer[offset:], file_size)  // size
    offset += 8

    binary.BigEndian.PutUint16(buffer[offset:], 1)  // flags
    offset += 2

    data_out, err := output.Write(buffer[:offset])
    output.Flush()
    utils.ErrorCheck(err)
    fmt.Printf("Wrote %d chars: %v\n", data_out, buffer[:offset])

    buffer = make([]byte, 512*1024)
    file_position := uint64(0)
    conn_reader := bufio.NewReader(conn)
    abort := false
    for {

        offset := 0
        waiting_for := 28       // wait for at least the minimum payload size

        for offset < waiting_for {
            length, err := conn_reader.Read(buffer[offset:waiting_for])
            offset += length
            utils.ErrorCheck(err)
            if err == io.EOF {
                abort = true
                break
            }
            utils.LogData("Reading instruction\n", offset, buffer)
            if offset < waiting_for {
                time.Sleep(5 * time.Millisecond)
            }
        }
        if abort {
            fmt.Printf("Abort detected, esaping processing loop\n")
            break
        }

        fmt.Printf("We read the buffer %v\n", buffer[:waiting_for])

        //magic := binary.BigEndian.Uint32(buffer)
        command := binary.BigEndian.Uint32(buffer[4:8])
        handle := binary.BigEndian.Uint64(buffer[8:16])
        from := binary.BigEndian.Uint64(buffer[16:24])
        length := binary.BigEndian.Uint32(buffer[24:28])

        switch command {
        case utils.NBD_COMMAND_READ:
            fmt.Printf("We have a request to read handle: %v, from: %v, length: %v, file_position: %v\n", handle, from, length, file_position)
            fmt.Printf("Read Resquest    Offset:%x length: %v     Handle %X\n", from, length, handle)
            if file_position != from {
                fmt.Printf("Seeking to %v\n", int64(from))
                file.Seek(int64(from), 0)     // seek to the requested position relative to the start of the file
                file_position = from
            }
            data_out, err = file.Read(buffer[16:16+length])
            file_position += uint64(length)
            fmt.Printf("new file position is: %v\n", file_position)
            utils.ErrorCheck(err)

            // Should not be big indian?
            binary.BigEndian.PutUint32(buffer[:4], utils.NBD_REPLY_MAGIC)
            binary.BigEndian.PutUint32(buffer[4:8], 0)                      // error bits

            utils.LogData("About to reply with", int(16+length), buffer)
            fmt.Printf("length of buffer: %v\n", len(buffer[:16+length]))
            fmt.Printf("tail of buffer: %v\n", buffer[length:16+length])

            conn.Write(buffer[:16+length])

            continue
        case utils.NBD_COMMAND_WRITE:
            fmt.Printf("We have a request to write handle: %v, from: %v, length: %v\n", handle, from, length)
            continue
        case utils.NBD_COMMAND_DISCONNECT:
            fmt.Printf("We have received a request to disconnect\n")
            // close the file and return
            return
        }
    }
}

func send_export_list(output *bufio.Writer) {
    files, err := ioutil.ReadDir(get_user_home_dir() + nbd_folder)
    if err != nil {
        log.Fatal(err)
    }
    for _, file := range files {
        send_export_list_item(output, file.Name())
    }

    send_ack(output)
}

func send_message(output *bufio.Writer, reply_type uint32, length uint32, data []byte ) {
    endian := binary.BigEndian
    buffer := make([]byte, 1024)
    offset := 0

    endian.PutUint64(buffer[offset:], utils.NBD_SERVER_SEND_REPLY_MAGIC)
    offset += 8

    endian.PutUint32(buffer[offset:], uint32(3))  // Flags (3 = supports list)
    offset += 4

    endian.PutUint32(buffer[offset:], reply_type)  // reply_type: NBD_REP_SERVER
    offset += 4

    endian.PutUint32(buffer[offset:], length)  // length of package
    offset += 4

    if data != nil {
        copy(buffer[offset:], data[0:length])
        offset += int(length)
    }

    data_to_send := buffer[:offset]
    output.Write(data_to_send)
    output.Flush()

    utils.LogData("Just sent:", offset, data_to_send)
}

func main() {

    if len(os.Args) <  3 {
        panic("missing arguments:  (ipaddress) (portnumber)")
        return
    }

    listener, err := net.Listen("tcp", os.Args[1] + ":" + os.Args[2])
    utils.ErrorCheck(err)

    fmt.Printf("Hello World, we have %v\n", listener)
    reply_magic := make([]byte, 4)
    binary.BigEndian.PutUint32(reply_magic, utils.NBD_REPLY_MAGIC)

    for {
        conn, err := listener.Accept()
        utils.ErrorCheck(err)

        fmt.Printf("We have a new connection from: %s\n", conn.RemoteAddr())
        output := bufio.NewWriter(conn)

        output.WriteString("NBDMAGIC")      // init password
        output.Flush()
        output.WriteString("IHAVEOPT")      // Magic
        output.Flush()
        output.Write([]byte{0, 3})          // Flags (3 = supports list)
        output.Flush()

        // Fetch the data until we get the initial options
        data := make([]byte, 1024)
        offset := 0
        waiting_for := 16       // wait for at least the minimum payload size

        for offset < waiting_for {
            length, err := conn.Read(data[offset:])
            offset += length
            utils.ErrorCheck(err)
            utils.LogData("Reading instruction", offset, data)
            if offset < waiting_for {
                time.Sleep(5 * time.Millisecond)
            }
        }

        // Skip the first 8 characters (options)
        command := binary.BigEndian.Uint32(data[12:])
        payload_size := int(binary.BigEndian.Uint32(data[16:]))

        fmt.Sprintf("command is: %d\npayload_size is: %d\n", command, payload_size)
        waiting_for += int(payload_size)
        for offset < waiting_for {
            length, err := conn.Read(data[offset:])
            offset += length
            utils.ErrorCheck(err)
            utils.LogData("Reading instruction", offset, data)
            if offset < waiting_for {
                time.Sleep(5 * time.Millisecond)
            }
        }
        payload := make([]byte, payload_size)

        if payload_size > 0{
            copy(payload, data[20:])
        }

        utils.LogData("Payload is:", payload_size, payload)
        fmt.Printf("command is: %v\n", command)

        // At this point, we have the command, payload size, and payload.
        switch command {
        case utils.NBD_COMMAND_LIST:
            send_export_list(output)
            conn.Close()
            break
        case utils.NBD_COMMAND_EXPORT_NAME:
            go export_name(output, conn, payload_size, payload)
            break
        }
    }

}
<|endoftext|>


<commit_before>// spdy/server.go

package spdy

import (
	"bytes"
	"crypto/rand"
	"crypto/tls"
	"encoding/binary"
	"errors"
	"io"
	"net"
	"net/http"
	"net/url"
	"strconv"
	"time"
)

// ListenAndServe creates a new Server that serves on the given address.  If
// the handler is nil, then http.DefaultServeMux is used.
func ListenAndServe(addr string, handler http.Handler) error {
	srv := &Server{addr, handler}
	return srv.ListenAndServe()
}

// ListenAndServeTLS acts like ListenAndServe except it uses TLS.
func ListenAndServeTLS(addr string, certFile, keyFile string, handler http.Handler) (err error) {
	config := &tls.Config{
		Rand:         rand.Reader,
		Time:         time.Now,
		NextProtos:   []string{"spdy/2", "http/1.1"},
		Certificates: make([]tls.Certificate, 1),
	}
	config.Certificates[0], err = tls.LoadX509KeyPair(certFile, keyFile)
	if err != nil {
		return
	}

	conn, err := net.Listen("tcp", addr)
	if err != nil {
		return
	}
	tlsListener := tls.NewListener(conn, config)
	return (&Server{addr, handler}).Serve(tlsListener)
}

// A Server handles incoming SPDY connections with HTTP handlers.
type Server struct {
	Addr    string
	Handler http.Handler
}

// ListenAndServe services SPDY requests on the given address.
// If the handler is nil, then http.DefaultServeMux is used.
func (srv *Server) ListenAndServe() error {
	addr := srv.Addr
	if addr == "" {
		addr = ":http"
	}
	l, err := net.Listen("tcp", addr)
	if err != nil {
		return err
	}
	return srv.Serve(l)
}

// ListenAndServe services SPDY requests using the given listener.
// If the handler is nil, then http.DefaultServeMux is used.
func (srv *Server) Serve(l net.Listener) error {
	defer l.Close()
	handler := srv.Handler
	if handler == nil {
		handler = http.DefaultServeMux
	}
	for {
		c, err := l.Accept()
		if err != nil {
			return err
		}
		s, err := newSession(c, handler)
		if err != nil {
			return err
		}
		go s.serve()
	}
	return nil
}

// A session manages a single TCP connection to a client.
type session struct {
	c       net.Conn
	handler http.Handler
	out	chan Frame
	streams map[uint32]*serverStream // all access is done synchronously

	headerReader *HeaderReader
	headerWriter *HeaderWriter
}

func newSession(c net.Conn, h http.Handler) (s *session, err error) {
	s = &session{
		c:            c,
		handler:      h,
		headerReader: NewHeaderReader(),
		headerWriter: NewHeaderWriter(-1),
		out:          make(chan Frame),
		streams:      make(map[uint32]*serverStream),
	}
	return
}

func (sess *session) serve() {
	defer sess.c.Close()
	go sess.receiveFrames()

	for frame := range sess.out {

		if (frame == nil) {
			// EOF, signalling end of session
			// initiated by us (on errors, etc.)
			return
		}

		// TODO: Check for errors
		frame.WriteTo(sess.c)
	}
}

func (sess *session) handleControl(frame ControlFrame) {
	switch frame.Type {
	case TypeSynStream:
		if stream, err := newServerStream(sess, frame); err == nil {
			if _, exists := sess.streams[stream.id]; !exists {
				sess.streams[stream.id] = stream
				go func() {
					sess.handler.ServeHTTP(stream, stream.Request())
					stream.finish()
				}()
			}
		}
	case TypeRstStream:
		d := bytes.NewBuffer(frame.Data)
		var streamId, statusCode uint32
		readBinary(d, &streamId, &statusCode)
	case TypePing:
		d := bytes.NewBuffer(frame.Data)
		var pingId uint32
		readBinary(d, &pingId)
		sess.out <- ControlFrame{
			Type: TypePing,
			Data: []byte{
				byte(pingId & 0xff000000 >> 24),
				byte(pingId & 0x00ff0000 >> 16),
				byte(pingId & 0x0000ff00 >> 8),
				byte(pingId & 0x000000ff >> 0),
			},
		}
	}
}

func (sess *session) handleData(frame DataFrame) {
	st, found := sess.streams[frame.StreamID]
	if !found {
		// TODO: Error?
		return
	}
	if st.dataPipe != nil {
		st.dataPipe.write(frame.Data)
		if frame.Flags&FlagFin != 0 {
			st.dataPipe.wclose(nil)
		}
	}
}

func (sess *session) receiveFrames() {
	for {
		f, err := ReadFrame(sess.c)
		if err != nil {
			return
		}

		if (f == nil) {
			// EOF, signalling end of session
			return
		}
		switch frame := f.(type) {
		case ControlFrame:
			sess.handleControl(frame)
		case DataFrame:
			sess.handleData(frame)
		}
	}
}

// A serverStream is a logical data stream inside a session.  A serverStream
// services a single request.
type serverStream struct {
	id      uint32
	session *session
	closed  bool

	requestHeaders  http.Header
	responseHeaders http.Header
	wroteHeader     bool

	dataPipe *asyncPipe
}

func newServerStream(sess *session, frame ControlFrame) (st *serverStream, err error) {
	if frame.Type != TypeSynStream {
		err = errors.New("Server stream must be created from a SynStream frame")
		return
	}
	st = &serverStream{
		session:         sess,
		responseHeaders: make(http.Header),
	}
	if frame.Flags&FlagFin == 0 {
		// Request body will follow
		st.dataPipe = apipe()
	}
	// Read frame data
	data := bytes.NewBuffer(frame.Data)
	err = binary.Read(data, binary.BigEndian, &st.id)
	if err != nil {
		return
	}
	_, err = io.ReadFull(data, make([]byte, 6)) // skip associated stream ID and priority
	if err != nil {
		return
	}
	st.requestHeaders, err = sess.headerReader.Decode(data.Bytes())
	return
}

// Request returns the request data associated with the serverStream.
func (st *serverStream) Request() (req *http.Request) {
	// TODO: Add more info
	req = &http.Request{
		Method:     st.requestHeaders.Get("method"),
		Proto:      st.requestHeaders.Get("version"),
		Header:     st.requestHeaders,
		Body:       st,
		RemoteAddr: st.session.c.RemoteAddr().String(),
	}
	req.URL, _ = url.ParseRequestURI(st.requestHeaders.Get("url"))
	return
}

func (st *serverStream) Read(p []byte) (n int, err error) {
	return st.dataPipe.read(p)
}

// Header returns the current response headers.
func (st *serverStream) Header() http.Header { return st.responseHeaders }

func (st *serverStream) Write(p []byte) (n int, err error) {
	if st.closed {
		err = errors.New("Write on closed serverStream")
		return
	}
	if !st.wroteHeader {
		st.WriteHeader(http.StatusOK)
	}
	for len(p) > 0 {
		frame := DataFrame{
			StreamID: st.id,
		}
		if len(p) < MaxDataLength {
			frame.Data = make([]byte, len(p))
		} else {
			frame.Data = make([]byte, MaxDataLength)
		}
		copy(frame.Data, p)
		p = p[len(frame.Data):]
		st.session.out <- frame
		n += len(frame.Data)
	}
	return
}

// A synReplyFrame defers header compression until the server writes the frame.
// This is necessary to guarantee correctly ordered compression.
type synReplyFrame struct {
	stream *serverStream
	header http.Header
	flags  FrameFlags
}

func (frame synReplyFrame) GetFlags() FrameFlags {
	return frame.flags
}

func (frame synReplyFrame) GetData() []byte {
	buf := new(bytes.Buffer)
	binary.Write(buf, binary.BigEndian, frame.stream.id&0x7fffffff)
	buf.Write([]byte{0, 0})
	frame.stream.session.headerWriter.WriteHeader(buf, frame.stream.responseHeaders)
	return buf.Bytes()
}

func (frame synReplyFrame) WriteTo(w io.Writer) (n int64, err error) {
	cf := ControlFrame{Type: TypeSynReply, Data: frame.GetData()}
	return cf.WriteTo(w)
}

func (st *serverStream) WriteHeader(code int) {
	if st.wroteHeader {
		return
	}
	st.responseHeaders.Set("status", strconv.Itoa(code)+" "+http.StatusText(code))
	st.responseHeaders.Set("version", "HTTP/1.1")
	if st.responseHeaders.Get("Content-Type") == "" {
		st.responseHeaders.Set("Content-Type", "text/html; charset=utf-8")
	}
	if st.responseHeaders.Get("Date") == "" {
		st.responseHeaders.Set("Date", time.Now().UTC().Format(http.TimeFormat))
	}
	// Write the frame
	// TODO: Copy headers
	st.session.out <- synReplyFrame{stream: st, header: st.responseHeaders}
	st.wroteHeader = true
}

// Close sends a closing frame, thus preventing the server from sending more
// data over the stream.  The client may still send data.
func (st *serverStream) Close() (err error) {
	if st.closed {
		return
	}
	st.session.out <- DataFrame{
		StreamID: st.id,
		Flags:    FlagFin,
		Data:     []byte{},
	}
	st.closed = true
	return nil
}

func (st *serverStream) finish() (err error) {
	if !st.wroteHeader {
		st.WriteHeader(http.StatusOK)
	}
	return st.Close()
}
<commit_msg>Make it possible to orderly shut down a connection<commit_after>// spdy/server.go

package spdy

import (
	"bytes"
	"crypto/rand"
	"crypto/tls"
	"encoding/binary"
	"errors"
	"io"
	"net"
	"net/http"
	"net/url"
	"strconv"
	"time"
)

// ListenAndServe creates a new Server that serves on the given address.  If
// the handler is nil, then http.DefaultServeMux is used.
func ListenAndServe(addr string, handler http.Handler) error {
	srv := &Server{addr, handler}
	return srv.ListenAndServe()
}

// ListenAndServeTLS acts like ListenAndServe except it uses TLS.
func ListenAndServeTLS(addr string, certFile, keyFile string, handler http.Handler) (err error) {
	config := &tls.Config{
		Rand:         rand.Reader,
		Time:         time.Now,
		NextProtos:   []string{"spdy/2", "http/1.1"},
		Certificates: make([]tls.Certificate, 1),
	}
	config.Certificates[0], err = tls.LoadX509KeyPair(certFile, keyFile)
	if err != nil {
		return
	}

	conn, err := net.Listen("tcp", addr)
	if err != nil {
		return
	}
	tlsListener := tls.NewListener(conn, config)
	return (&Server{addr, handler}).Serve(tlsListener)
}

// A Server handles incoming SPDY connections with HTTP handlers.
type Server struct {
	Addr    string
	Handler http.Handler
}

// ListenAndServe services SPDY requests on the given address.
// If the handler is nil, then http.DefaultServeMux is used.
func (srv *Server) ListenAndServe() error {
	addr := srv.Addr
	if addr == "" {
		addr = ":http"
	}
	l, err := net.Listen("tcp", addr)
	if err != nil {
		return err
	}
	return srv.Serve(l)
}

// ListenAndServe services SPDY requests using the given listener.
// If the handler is nil, then http.DefaultServeMux is used.
func (srv *Server) Serve(l net.Listener) error {
	defer l.Close()
	handler := srv.Handler
	if handler == nil {
		handler = http.DefaultServeMux
	}
	for {
		c, err := l.Accept()
		if err != nil {
			return err
		}
		s, err := newSession(c, handler)
		if err != nil {
			return err
		}
		go s.serve()
	}
	return nil
}

// A session manages a single TCP connection to a client.
type session struct {
	c         net.Conn
	handler   http.Handler
	out       chan Frame
	streams   map[uint32]*serverStream // all access is done synchronously
	last_good uint32

	headerReader *HeaderReader
	headerWriter *HeaderWriter
}

func newSession(c net.Conn, h http.Handler) (s *session, err error) {
	s = &session{
		c:            c,
		handler:      h,
		headerReader: NewHeaderReader(),
		headerWriter: NewHeaderWriter(-1),
		out:          make(chan Frame),
		streams:      make(map[uint32]*serverStream),
		last_good:    0,
	}
	return
}

func (sess *session) serve() {
	defer sess.c.Close()
	go sess.receiveFrames()

	for frame := range sess.out {

		if frame == nil {
			// EOF, signalling end of session
			// initiated by us (on errors, etc.)
			return
		}

		// TODO: Check for errors
		frame.WriteTo(sess.c)
	}
}

func (sess *session) fail() {
	sess.out <- ControlFrame{
		Type: TypeGoaway,
		Data: []byte{
			byte(sess.last_good & 0x7f000000 >> 24),
			byte(sess.last_good & 0x00ff0000 >> 16),
			byte(sess.last_good & 0x0000ff00 >> 8),
			byte(sess.last_good & 0x000000ff >> 0),
		},
	}
	sess.out <- nil
}

func (sess *session) handleControl(frame ControlFrame) {
	switch frame.Type {
	case TypeSynStream:
		if stream, err := newServerStream(sess, frame); err == nil {
			if _, exists := sess.streams[stream.id]; !exists {
				sess.streams[stream.id] = stream
				sess.last_good = stream.id
				go func() {
					sess.handler.ServeHTTP(stream, stream.Request())
					stream.finish()
				}()
			}
		}
	case TypeRstStream:
		d := bytes.NewBuffer(frame.Data)
		var streamId, statusCode uint32
		readBinary(d, &streamId, &statusCode)
	case TypePing:
		d := bytes.NewBuffer(frame.Data)
		var pingId uint32
		readBinary(d, &pingId)
		sess.out <- ControlFrame{
			Type: TypePing,
			Data: []byte{
				byte(pingId & 0xff000000 >> 24),
				byte(pingId & 0x00ff0000 >> 16),
				byte(pingId & 0x0000ff00 >> 8),
				byte(pingId & 0x000000ff >> 0),
			},
		}
	}
}

func (sess *session) handleData(frame DataFrame) {
	st, found := sess.streams[frame.StreamID]
	if !found {
		// TODO: Error?
		return
	}
	if st.dataPipe != nil {
		st.dataPipe.write(frame.Data)
		if frame.Flags&FlagFin != 0 {
			st.dataPipe.wclose(nil)
		}
	}
}

func (sess *session) receiveFrames() {
	for {
		f, err := ReadFrame(sess.c)
		if err != nil {
			return
		}

		if f == nil {
			// EOF, signalling end of session
			return
		}
		switch frame := f.(type) {
		case ControlFrame:
			sess.handleControl(frame)
		case DataFrame:
			sess.handleData(frame)
		}
	}
}

// A serverStream is a logical data stream inside a session.  A serverStream
// services a single request.
type serverStream struct {
	id      uint32
	session *session
	closed  bool

	requestHeaders  http.Header
	responseHeaders http.Header
	wroteHeader     bool

	dataPipe *asyncPipe
}

func newServerStream(sess *session, frame ControlFrame) (st *serverStream, err error) {
	if frame.Type != TypeSynStream {
		err = errors.New("Server stream must be created from a SynStream frame")
		return
	}
	st = &serverStream{
		session:         sess,
		responseHeaders: make(http.Header),
	}
	if frame.Flags&FlagFin == 0 {
		// Request body will follow
		st.dataPipe = apipe()
	}
	// Read frame data
	data := bytes.NewBuffer(frame.Data)
	err = binary.Read(data, binary.BigEndian, &st.id)
	if err != nil {
		return
	}
	_, err = io.ReadFull(data, make([]byte, 6)) // skip associated stream ID and priority
	if err != nil {
		return
	}
	st.requestHeaders, err = sess.headerReader.Decode(data.Bytes())
	return
}

// Request returns the request data associated with the serverStream.
func (st *serverStream) Request() (req *http.Request) {
	// TODO: Add more info
	req = &http.Request{
		Method:     st.requestHeaders.Get("method"),
		Proto:      st.requestHeaders.Get("version"),
		Header:     st.requestHeaders,
		Body:       st,
		RemoteAddr: st.session.c.RemoteAddr().String(),
	}
	req.URL, _ = url.ParseRequestURI(st.requestHeaders.Get("url"))
	return
}

func (st *serverStream) Read(p []byte) (n int, err error) {
	return st.dataPipe.read(p)
}

// Header returns the current response headers.
func (st *serverStream) Header() http.Header { return st.responseHeaders }

func (st *serverStream) Write(p []byte) (n int, err error) {
	if st.closed {
		err = errors.New("Write on closed serverStream")
		return
	}
	if !st.wroteHeader {
		st.WriteHeader(http.StatusOK)
	}
	for len(p) > 0 {
		frame := DataFrame{
			StreamID: st.id,
		}
		if len(p) < MaxDataLength {
			frame.Data = make([]byte, len(p))
		} else {
			frame.Data = make([]byte, MaxDataLength)
		}
		copy(frame.Data, p)
		p = p[len(frame.Data):]
		st.session.out <- frame
		n += len(frame.Data)
	}
	return
}

// A synReplyFrame defers header compression until the server writes the frame.
// This is necessary to guarantee correctly ordered compression.
type synReplyFrame struct {
	stream *serverStream
	header http.Header
	flags  FrameFlags
}

func (frame synReplyFrame) GetFlags() FrameFlags {
	return frame.flags
}

func (frame synReplyFrame) GetData() []byte {
	buf := new(bytes.Buffer)
	binary.Write(buf, binary.BigEndian, frame.stream.id&0x7fffffff)
	buf.Write([]byte{0, 0})
	frame.stream.session.headerWriter.WriteHeader(buf, frame.stream.responseHeaders)
	return buf.Bytes()
}

func (frame synReplyFrame) WriteTo(w io.Writer) (n int64, err error) {
	cf := ControlFrame{Type: TypeSynReply, Data: frame.GetData()}
	return cf.WriteTo(w)
}

func (st *serverStream) WriteHeader(code int) {
	if st.wroteHeader {
		return
	}
	st.responseHeaders.Set("status", strconv.Itoa(code)+" "+http.StatusText(code))
	st.responseHeaders.Set("version", "HTTP/1.1")
	if st.responseHeaders.Get("Content-Type") == "" {
		st.responseHeaders.Set("Content-Type", "text/html; charset=utf-8")
	}
	if st.responseHeaders.Get("Date") == "" {
		st.responseHeaders.Set("Date", time.Now().UTC().Format(http.TimeFormat))
	}
	// Write the frame
	// TODO: Copy headers
	st.session.out <- synReplyFrame{stream: st, header: st.responseHeaders}
	st.wroteHeader = true
}

// Close sends a closing frame, thus preventing the server from sending more
// data over the stream.  The client may still send data.
func (st *serverStream) Close() (err error) {
	if st.closed {
		return
	}
	st.session.out <- DataFrame{
		StreamID: st.id,
		Flags:    FlagFin,
		Data:     []byte{},
	}
	st.closed = true
	return nil
}

func (st *serverStream) finish() (err error) {
	if !st.wroteHeader {
		st.WriteHeader(http.StatusOK)
	}
	return st.Close()
}
<|endoftext|>


<commit_before>package orivil

import (
	"fmt"
	"gopkg.in/orivil/event.v0"
	"gopkg.in/orivil/middle.v0"
	"gopkg.in/orivil/router.v0"
	"gopkg.in/orivil/service.v0"
	. "gopkg.in/orivil/session.v0"
	"gopkg.in/orivil/view.v0"
	"net/http"
	"path/filepath"
	"reflect"
	"strings"
)

const (
	SvcApp = "orivil.App"
)

var (
	// the unique key for server
	Key string
)

type FileHandler interface {
	// HandleFile to check if handle the url as static file
	HandleFile(url string) bool
	// ServeFile for serve static file
	ServeFile(w http.ResponseWriter, r *http.Request, fileName string)
}

type NotFoundHandler interface {
	NotFound(w http.ResponseWriter, r *http.Request)
}

type Server struct {
	SContainer      *service.Container
	MContainer      *middle.Container
	RContainer      *router.Container
	MiddleBag       *middle.Bag
	VContainer      *view.Container
	Dispatcher      *event.Dispatcher
	Registers       []Register
	fileHandler     FileHandler
	notFoundHandler NotFoundHandler
	timeOutHandler  http.Handler
	*http.Server
}

func NewServer(addr string) *Server {

	// public service container 公共服务容器 , 主要用于对 service
	// provider 的存储, 如果从此容器中获取 service, 则该 service
	// 存在数据竞争, 每次 http 请求还会产生一个私有容器, 用于获取
	// service, 从私有容器中获取的 service 是数据安全的, 不必担心私
	// 有容器和公共容器该用在什么场合, 当你存 service 的时候自动存入
	// public container 公共容器, 取 service 的时候自动去 private
	// container 中取
	sContainer := service.NewPublicContainer()

	// middleware bag 用于中间件的配置及匹配
	middleBag := middle.NewMiddlewareBag()

	// middleware container 中间件容器依赖于服务容器, 存中间件服务时用
	// 公共容器, 取中间件服务时用私有容器
	mContainer := middle.NewContainer(middleBag, sContainer)

	// view compiler
	compiler := view.NewContainer(CfgApp.Debug, CfgApp.View_file_ext)

	// route filter 排除 controller 的 action 被注册进路由
	routeFilter := NewRouteFilter()
	// 排除 controller 继承的方法, every controller should extend App struct
	routeFilter.AddStructs([]interface{}{
		&App{},
	})
	// 排除方法名
	routeFilter.AddActions([]string{
		"SetMiddle",
	})

	// route container collect all of the controller comment,
	// add the then to the router if possible
	rContainer := router.NewContainer(DirBundle, routeFilter)

	// server dispatcher, only dispatch server event when server start
	dispatcher := event.NewDispatcher()
	dispatcher.AddEvents(serverEvents)
	dispatcher.AddListener(
		new(ServerListener),
	)

	// new server
	server := &Server{
		SContainer: sContainer,
		MiddleBag:  middleBag,
		MContainer: mContainer,
		RContainer: rContainer,
		VContainer: compiler,
		Dispatcher: dispatcher,
	}

	// TODO:
	// time out handler
	//outTime := time.Duration(CfgApp.Timeout) * time.Second
	//timeOutHandler := http.TimeoutHandler(server, outTime, "")
	//server.Server = &http.Server{Addr: addr, Handler: timeOutHandler}
	server.Server = &http.Server{Addr: addr, Handler: server}

	// set default not found handler
	server.notFoundHandler = server

	// set default static file server handler
	server.fileHandler = server

	// register base service
	server.RegisterBundle(
		new(BaseRegister),
	)
	return server
}

func (s *Server) SetNotFoundHandler(h NotFoundHandler) {
	s.notFoundHandler = h
}

func (s *Server) SetFileHandler(h FileHandler) {
	s.fileHandler = h
}

func (s *Server) AddServerListener(ls ...event.Listener) {
	s.Dispatcher.AddListener(ls...)
}

func (s *Server) HandleFile(url string) bool {
	return filepath.Ext(url) != ""
}

func (s *Server) ServeFile(w http.ResponseWriter, r *http.Request, name string) {
	http.ServeFile(w, r, name)
}

// ServeHTTP the http serve handler, every request goes through the function
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	path := r.URL.Path
	// handle static file
	if s.fileHandler.HandleFile(path) {
		s.fileHandler.ServeFile(w, r, filepath.Join(DirStaticFile, path))
	} else {
		var app *App
		CoverError(w, r, func() {
			path = r.Method + path

			// match route
			if action, params, controller, ok := s.RContainer.Match(path); ok {

				// new private container
				privateContainer := service.NewPrivateContainer(s.SContainer)

				// new app
				app = &App{
					Params:    params,
					Action:    action,
					Response:  w,
					Request:   r,
					Container: privateContainer,
					viewData:  make(map[string]interface{}, 1),
				}
				app.SetInstance(SvcApp, app)

				// match middleware, new middleware and cache them in the
				// private service container
				middleNames := s.MContainer.Get(action)
				middles := make([]interface{}, len(middleNames))

				// get middleware instances from private container
				index := 0
				for _, service := range middleNames {
					middles[index] = privateContainer.Get(service)
					index++
				}

				// call middlewares
				s.callMiddles(middles, app)

				// call controller action
				value := reflect.ValueOf(controller())
				s.setControllerDependence(value, app)
				method := action[strings.LastIndex(action, ".")+1:]
				actionFun, _ := value.Type().MethodByName(method)
				actionFun.Func.Call([]reflect.Value{value})

				// send view file or api data
				s.send(app)

				// call "Terminate" middlewares
				s.callMiddlesTerminate(middles, app)
			} else {
				s.notFoundHandler.NotFound(w, r)
			}
		})

		if app != nil {
			s.storeSession(app)
		}
	}
}

// implement NotFoundHandler interface
func (s *Server) NotFound(w http.ResponseWriter, r *http.Request) {
	http.NotFound(w, r)
}

func (s *Server) send(a *App) {
	// send view file
	if len(a.viewFile) > 0 {
		bundle := a.Action[0:strings.Index(a.Action, ".")]
		// a.viewFile may contains sub dir like "/admin/login.tpl"
		dir := filepath.Join(DirBundle, bundle, "view", a.viewSubDir)
		err := s.VContainer.Display(a.Response, dir, a.viewFile, a.viewData)
		if err != nil {
			panic(err)
		}
	} else {
		// send api data
		if len(a.viewData) > 0 {
			a.JsonEncode(a.viewData)
		}
	}
}

func (s *Server) storeSession(a *App) {
	// if permanent session service was used, store it
	if inst, ok := a.HasGot(SvcPermanentSession); ok {
		session := inst.(*Session)
		StorePermanentSession(session)
	}
}

func (s *Server) setControllerDependence(controller reflect.Value, app *App) {
	v := controller.Elem()
	len := v.NumField()
	for i := 0; i < len; i++ {
		fi := v.Field(i)
		if fi.CanSet() && fi.Type().String() == "*orivil.App" {
			fi.Set(reflect.ValueOf(app))
			break
		}
	}
}

func (s *Server) callMiddles(middles []interface{}, app *App) {
	for _, middle := range middles {
		if requestHandler, ok := middle.(RequestHandler); ok {

			requestHandler.Handle(app)
		} else if call, ok := middle.(func(*App)); ok {

			call(app)
		}
	}
}

func (s *Server) callMiddlesTerminate(middles []interface{}, app *App) {
	for _, middle := range middles {
		if requestHandler, ok := middle.(TerminateHandler); ok {
			requestHandler.Terminate(app)
		}
	}
}

func (s *Server) PrintMsg() {
	routeMsg := router.GetAllRouteMsg(s.RContainer)
	fmt.Println()
	fmt.Println("route message:")
	for _, msg := range routeMsg {
		fmt.Println(msg)
	}

	actions := s.RContainer.GetActions()
	middleMsg := middle.GetMiddlesMsg(s.MContainer, actions)
	fmt.Println()
	fmt.Println("middleware message:")
	for _, msg := range middleMsg {
		fmt.Println(msg)
	}
}

func (s *Server) Run() {
	// add listeners from provider registered
	s.addServerListener(s.Registers)

	// register service
	s.Dispatcher.Trigger(EvtRegisterService, s)

	// register route
	s.Dispatcher.Trigger(EvtRegisterRoute, s)

	// register middleware
	s.Dispatcher.Trigger(EvtRegisterMiddle, s)

	// config provider
	s.Dispatcher.Trigger(EvtConfigProvider, s)

	// boot all provider
	s.Dispatcher.Trigger(EvtBootProvider, s)
}

func (s *Server) addServerListener(registers []Register) {
	for _, provider := range registers {
		if listenable, ok := provider.(ServerEventListener); ok {
			listenable.AddServerListener(s.Dispatcher)
		}
	}
}

func (s *Server) RegisterBundle(app ...Register) {
	s.Registers = append(s.Registers, app...)
}
<commit_msg>update grace http<commit_after>package orivil

import (
	"fmt"
	"gopkg.in/orivil/event.v0"
	"gopkg.in/orivil/middle.v0"
	"gopkg.in/orivil/router.v0"
	"gopkg.in/orivil/service.v0"
	. "gopkg.in/orivil/session.v0"
	"gopkg.in/orivil/view.v0"
	"net/http"
	"path/filepath"
	"reflect"
	"strings"
	"log"
	"github.com/orivil/gracehttp"
	"time"
)

const (
	SvcApp = "orivil.App"
)

var (
	// the unique key for server
	Key string
)

type FileHandler interface {
	// HandleFile to check if handle the url as static file
	HandleFile(url string) bool
	// ServeFile for serve static file
	ServeFile(w http.ResponseWriter, r *http.Request, fileName string)
}

type NotFoundHandler interface {
	NotFound(w http.ResponseWriter, r *http.Request)
}

// CloseAble
type CloseAble interface {

	Close()
}

type Server struct {
	SContainer      *service.Container
	MContainer      *middle.Container
	RContainer      *router.Container
	MiddleBag       *middle.Bag
	VContainer      *view.Container
	Dispatcher      *event.Dispatcher
	Registers       []Register
	fileHandler     FileHandler
	notFoundHandler NotFoundHandler
	timeOutHandler  http.Handler
	*gracehttp.Server
}

func NewServer(addr string) *Server {

	// public service container 公共服务容器 , 主要用于对 service
	// provider 的存储, 如果从此容器中获取 service, 则该 service
	// 存在数据竞争, 每次 http 请求还会产生一个私有容器, 用于获取
	// service, 从私有容器中获取的 service 是数据安全的, 不必担心私
	// 有容器和公共容器该用在什么场合, 当你存 service 的时候自动存入
	// public container 公共容器, 取 service 的时候自动去 private
	// container 中取
	sContainer := service.NewPublicContainer()

	// middleware bag 用于中间件的配置及匹配
	middleBag := middle.NewMiddlewareBag()

	// middleware container 中间件容器依赖于服务容器, 存中间件服务时用
	// 公共容器, 取中间件服务时用私有容器
	mContainer := middle.NewContainer(middleBag, sContainer)

	// view compiler
	compiler := view.NewContainer(CfgApp.Debug, CfgApp.View_file_ext)

	// route filter 排除 controller 的 action 被注册进路由
	routeFilter := NewRouteFilter()
	// 排除 controller 继承的方法, every controller should extend App struct
	routeFilter.AddStructs([]interface{}{
		&App{},
	})
	// 排除方法名
	routeFilter.AddActions([]string{
		"SetMiddle",
	})

	// route container collect all of the controller comment,
	// add the then to the router if possible
	rContainer := router.NewContainer(DirBundle, routeFilter)

	// server dispatcher, only dispatch server event when server start
	dispatcher := event.NewDispatcher()
	dispatcher.AddEvents(serverEvents)
	dispatcher.AddListener(
		new(ServerListener),
	)

	// new server
	server := &Server{
		SContainer: sContainer,
		MiddleBag:  middleBag,
		MContainer: mContainer,
		RContainer: rContainer,
		VContainer: compiler,
		Dispatcher: dispatcher,
	}

	timeOut := time.Second * time.Duration(CfgApp.Timeout)
	server.Server = gracehttp.NewServer(addr, server, timeOut, timeOut)
	server.Server.AddCloseListener(server)

	// set default not found handler
	server.notFoundHandler = server

	// set default static file server handler
	server.fileHandler = server

	// register base service
	server.RegisterBundle(
		new(BaseRegister),
	)
	return server
}

func (s *Server) SetNotFoundHandler(h NotFoundHandler) {
	s.notFoundHandler = h
}

func (s *Server) Close() {

	log.Println("closing bundle register...")
	for _, reg := range s.Registers {
		if clo, ok := reg.(CloseAble); ok {
			clo.Close()
		}
	}
}

func (s *Server) SetFileHandler(h FileHandler) {
	s.fileHandler = h
}

func (s *Server) AddServerListener(ls ...event.Listener) {
	s.Dispatcher.AddListener(ls...)
}

func (s *Server) HandleFile(url string) bool {
	return filepath.Ext(url) != ""
}

func (s *Server) ServeFile(w http.ResponseWriter, r *http.Request, name string) {
	http.ServeFile(w, r, name)
}

// ServeHTTP the http serve handler, every request goes through the function
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	path := r.URL.Path
	// handle static file
	if s.fileHandler.HandleFile(path) {
		s.fileHandler.ServeFile(w, r, filepath.Join(DirStaticFile, path))
	} else {
		var app *App
		CoverError(w, r, func() {
			path = r.Method + path

			// match route
			if action, params, controller, ok := s.RContainer.Match(path); ok {

				// new private container
				privateContainer := service.NewPrivateContainer(s.SContainer)

				// new app
				app = &App{
					Params:    params,
					Action:    action,
					Response:  w,
					Request:   r,
					Container: privateContainer,
					viewData:  make(map[string]interface{}, 1),
				}
				app.SetInstance(SvcApp, app)

				// match middleware, new middleware and cache them in the
				// private service container
				middleNames := s.MContainer.Get(action)
				middles := make([]interface{}, len(middleNames))

				// get middleware instances from private container
				index := 0
				for _, service := range middleNames {
					middles[index] = privateContainer.Get(service)
					index++
				}

				// call middlewares
				s.callMiddles(middles, app)

				// call controller action
				value := reflect.ValueOf(controller())
				s.setControllerDependence(value, app)
				method := action[strings.LastIndex(action, ".")+1:]
				actionFun, _ := value.Type().MethodByName(method)
				actionFun.Func.Call([]reflect.Value{value})

				// send view file or api data
				s.send(app)

				// call "Terminate" middlewares
				s.callMiddlesTerminate(middles, app)
			} else {
				s.notFoundHandler.NotFound(w, r)
			}
		})

		if app != nil {
			s.storeSession(app)
		}
	}
}

// implement NotFoundHandler interface
func (s *Server) NotFound(w http.ResponseWriter, r *http.Request) {
	http.NotFound(w, r)
}

func (s *Server) send(a *App) {
	// send view file
	if len(a.viewFile) > 0 {
		bundle := a.Action[0:strings.Index(a.Action, ".")]
		// a.viewFile may contains sub dir like "/admin/login.tpl"
		dir := filepath.Join(DirBundle, bundle, "view", a.viewSubDir)
		err := s.VContainer.Display(a.Response, dir, a.viewFile, a.viewData)
		if err != nil {
			panic(err)
		}
	} else {
		// send api data
		if len(a.viewData) > 0 {
			a.JsonEncode(a.viewData)
		}
	}
}

func (s *Server) storeSession(a *App) {
	// if permanent session service was used, store it
	if inst, ok := a.HasGot(SvcPermanentSession); ok {
		session := inst.(*Session)
		StorePermanentSession(session)
	}
}

func (s *Server) setControllerDependence(controller reflect.Value, app *App) {
	v := controller.Elem()
	len := v.NumField()
	for i := 0; i < len; i++ {
		fi := v.Field(i)
		if fi.CanSet() && fi.Type().String() == "*orivil.App" {
			fi.Set(reflect.ValueOf(app))
			break
		}
	}
}

func (s *Server) callMiddles(middles []interface{}, app *App) {
	for _, middle := range middles {
		if requestHandler, ok := middle.(RequestHandler); ok {

			requestHandler.Handle(app)
		} else if call, ok := middle.(func(*App)); ok {

			call(app)
		}
	}
}

func (s *Server) callMiddlesTerminate(middles []interface{}, app *App) {
	for _, middle := range middles {
		if requestHandler, ok := middle.(TerminateHandler); ok {
			requestHandler.Terminate(app)
		}
	}
}

func (s *Server) PrintMsg() {
	routeMsg := router.GetAllRouteMsg(s.RContainer)
	fmt.Println()
	fmt.Println("route message:")
	for _, msg := range routeMsg {
		fmt.Println(msg)
	}

	actions := s.RContainer.GetActions()
	middleMsg := middle.GetMiddlesMsg(s.MContainer, actions)
	fmt.Println()
	fmt.Println("middleware message:")
	for _, msg := range middleMsg {
		fmt.Println(msg)
	}
}

func (s *Server) Run() {
	// add listeners from provider registered
	s.addServerListener(s.Registers)

	// register service
	s.Dispatcher.Trigger(EvtRegisterService, s)

	// register route
	s.Dispatcher.Trigger(EvtRegisterRoute, s)

	// register middleware
	s.Dispatcher.Trigger(EvtRegisterMiddle, s)

	// config provider
	s.Dispatcher.Trigger(EvtConfigProvider, s)

	// boot all provider
	s.Dispatcher.Trigger(EvtBootProvider, s)
}

func (s *Server) addServerListener(registers []Register) {
	for _, provider := range registers {
		if listenable, ok := provider.(ServerEventListener); ok {
			listenable.AddServerListener(s.Dispatcher)
		}
	}
}

func (s *Server) RegisterBundle(app ...Register) {
	s.Registers = append(s.Registers, app...)
}
<|endoftext|>


<commit_before>package hawk

import (
	"encoding/base64"
	"errors"
	"math"
	"net/http"
	"net/url"
	"strconv"
	"strings"
	"time"
)

type Server struct {
	CredentialGetter CredentialGetter
	NonceValidator   NonceValidator
	TimeStampSkew    time.Duration
	LocaltimeOffset  time.Duration
	Payload          string
	AuthOption       *AuthOption
}

type AuthOption struct {
	CustomHostNameHeader string
	CustomHostPort       string
	CustomClock          Clock
}

type CredentialGetter interface {
	GetCredential(id string) (*Credential, error)
}

type NonceValidator interface {
	Validate(key, nonce string, ts int64) bool
}

// Authenticate authenticate the Hawk request from the HTTP request.
// Successful case returns credential information about requested user.
func (s *Server) Authenticate(req *http.Request) (*Credential, error) {
	// 0 is treated as empty. set to default value.
	if s.TimeStampSkew == 0 {
		s.TimeStampSkew = 60 * time.Second
	}

	clock := getClock(s.AuthOption)
	now := clock.Now(s.LocaltimeOffset)

	authzHeader := req.Header.Get("Authorization")
	authzAttributes := parseHawkHeader(authzHeader)

	ts, err := strconv.ParseInt(authzAttributes["ts"], 10, 64)
	if err != nil {
		return nil, errors.New("Invalid ts value.")
	}

	artifacts := &Option{
		TimeStamp: ts,
		Nonce:     authzAttributes["nonce"],
		Hash:      authzAttributes["hash"],
		Ext:       authzAttributes["ext"],
		App:       authzAttributes["app"],
		Dlg:       authzAttributes["dlg"],
	}

	cred, err := s.CredentialGetter.GetCredential(authzAttributes["id"])
	if err != nil {
		// FIXME: logging error
		return nil, errors.New("Failed to get Credential.")
	}
	if cred.Key == "" {
		return nil, errors.New("Invalid Credential.")
	}

	var host string
	if s.AuthOption != nil {
		// set to custom host(and port) value
		if s.AuthOption.CustomHostNameHeader != "" {
			host = req.Header.Get(s.AuthOption.CustomHostNameHeader)
		}
		if s.AuthOption.CustomHostPort != "" {
			// forces override a value.
			host = s.AuthOption.CustomHostPort
		}
	}

	m := &Mac{
		Type:       Header,
		Credential: cred,
		Uri:        req.URL.String(),
		Method:     req.Method,
		HostPort:   host,
		Option:     artifacts,
	}
	mac, err := m.String()
	if err != nil {
		//FIXME: logging error
		return nil, errors.New("Failed to calculate MAC.")
	}

	if !fixedTimeComparison(mac, authzAttributes["mac"]) {
		return nil, errors.New("Bad MAC")
	}

	if req.Method == "POST" || req.Method == "PUT" {
		if artifacts.Hash == "" {
			return nil, errors.New("Missing required payload hash.")
		}

		ph := &PayloadHash{
			ContentType: req.Header.Get("Content-Type"),
			Payload:     s.Payload,
			Alg:         cred.Alg,
		}
		if !fixedTimeComparison(ph.String(), artifacts.Hash) {
			return nil, errors.New("Bad payload hash.")
		}
	}

	if s.NonceValidator != nil {
		if !s.NonceValidator.Validate(cred.Key, artifacts.Nonce, artifacts.TimeStamp) {
			return nil, errors.New("Invalid nonce.")
		}
	}
	if math.Abs(float64((artifacts.TimeStamp)-(now))) > s.TimeStampSkew.Seconds() {
		//FIXME: logging timestamp
		return nil, errors.New("Stale timestamp")
	}

	return cred, nil
}

// AuthenticateBewit authenticate the Hawk bewit request from the HTTP request.
// Successful case returns credential information about requested user.
func (s *Server) AuthenticateBewit(req *http.Request) (*Credential, error) {
	clock := getClock(s.AuthOption)
	now := clock.Now(s.LocaltimeOffset)

	encodedBewit := req.URL.Query().Get("bewit")
	if encodedBewit == "" {
		return nil, errors.New("Empty bewit.")
	}

	if req.Method != "GET" && req.Method != "HEAD" {
		return nil, errors.New("Invalid method.")
	}

	if req.Header.Get("Authorization") != "" {
		return nil, errors.New("Multiple authentications")
	}

	rawBewit, err := base64.RawURLEncoding.DecodeString(encodedBewit)
	if err != nil {
		return nil, errors.New("Failed to decode bewit parameter.")
	}

	parsedBewit := strings.Split(string(rawBewit), "\\")
	if len(parsedBewit) != 4 {
		return nil, errors.New("Invalid bewit structure.")
	}

	bewit := map[string]string{
		"id":  parsedBewit[0],
		"exp": parsedBewit[1],
		"mac": parsedBewit[2],
		"ext": parsedBewit[3],
	}

	if bewit["id"] == "" || bewit["exp"] == "" || bewit["mac"] == "" {
		return nil, errors.New("Missing bewit attributes.")
	}

	ts, err := strconv.ParseInt(bewit["exp"], 10, 64)
	if err != nil {
		return nil, errors.New("Invalid ts value.")
	}

	if ts <= now {
		return nil, errors.New("Access expired.")
	}

	cred, err := s.CredentialGetter.GetCredential(bewit["id"])
	if err != nil {
		// FIXME: logging error
		return nil, errors.New("Failed to get Credential.")
	}
	if cred.Key == "" {
		return nil, errors.New("Invalid Credential.")
	}

	removedBewitURL := removeBewitParam(req.URL)

	var host string
	if s.AuthOption != nil {
		// set to custom host(and port) value
		if s.AuthOption.CustomHostNameHeader != "" {
			host = req.Header.Get(s.AuthOption.CustomHostNameHeader)
		}
		if s.AuthOption.CustomHostPort != "" {
			// forces override a value.
			host = s.AuthOption.CustomHostPort
		}
	}

	m := &Mac{
		Type:       Bewit,
		Credential: cred,
		Uri:        removedBewitURL.String(),
		Method:     req.Method,
		HostPort:   host,
		Option: &Option{
			TimeStamp: ts,
			Nonce:     "",
			Ext:       bewit["ext"],
		},
	}
	mac, err := m.String()
	if err != nil {
		//FIXME: logging error
		return nil, errors.New("Failed to calculate MAC.")
	}

	if !fixedTimeComparison(mac, bewit["mac"]) {
		return nil, errors.New("Bad mac.")
	}

	return cred, nil
}

// Header builds a value to be set in the Server-Authorization header.
func (s *Server) Header(req *http.Request, cred *Credential, opt *Option) (string, error) {
	authzHeader := req.Header.Get("Authorization")
	authzAttributes := parseHawkHeader(authzHeader)

	if opt.Hash == "" && (req.Method == "POST" || req.Method == "PUT") {
		ph := &PayloadHash{
			ContentType: opt.ContentType,
			Payload:     opt.Payload,
			Alg:         cred.Alg,
		}
		opt.Hash = ph.String()
	}

	ts, err := strconv.ParseInt(authzAttributes["ts"], 10, 64)
	if err != nil {
		return "", errors.New("Invalid ts value.")
	}
	artifacts := &Option{
		TimeStamp: ts,
		Nonce:     authzAttributes["nonce"],
		Hash:      opt.Hash,
		Ext:       opt.Ext,
		App:       authzAttributes["app"],
		Dlg:       authzAttributes["dlg"],
	}

	var host string
	if s.AuthOption != nil {
		// set to custom host(and port) value
		if s.AuthOption.CustomHostNameHeader != "" {
			host = req.Header.Get(s.AuthOption.CustomHostNameHeader)
		}
		if s.AuthOption.CustomHostPort != "" {
			// forces override a value.
			host = s.AuthOption.CustomHostPort
		}
	}

	m := &Mac{
		Type:       Response,
		Credential: cred,
		Uri:        req.URL.String(),
		Method:     req.Method,
		HostPort:   host,
		Option:     artifacts,
	}

	mac, err := m.String()
	if err != nil {
		//FIXME: logging error
		return "", errors.New("Failed to calculate MAC.")
	}

	header := "Hawk " + `mac="` + mac + `"`

	if opt.Hash != "" {
		header = header + ", " + `hash="` + opt.Hash + `"`
	}

	if opt.Ext != "" {
		header = header + ", " + `ext="` + opt.Ext + `"`
	}

	return header, nil
}

func getClock(authOption *AuthOption) Clock {
	var clock Clock
	if authOption == nil || authOption.CustomClock == nil {
		clock = &LocalClock{}
	} else {
		clock = authOption.CustomClock
	}
	return clock
}

func removeBewitParam(u *url.URL) url.URL {
	removedQuery := &url.Values{}
	for key, _ := range u.Query() {
		if key == "bewit" {
			continue
		}
		removedQuery.Add(key, u.Query().Get(key))
	}
	removedUrl := *u
	removedUrl.RawQuery = removedQuery.Encode()

	return removedUrl
}
<commit_msg>Check value of Authorization header<commit_after>package hawk

import (
	"encoding/base64"
	"errors"
	"math"
	"net/http"
	"net/url"
	"strconv"
	"strings"
	"time"
)

type Server struct {
	CredentialGetter CredentialGetter
	NonceValidator   NonceValidator
	TimeStampSkew    time.Duration
	LocaltimeOffset  time.Duration
	Payload          string
	AuthOption       *AuthOption
}

type AuthOption struct {
	CustomHostNameHeader string
	CustomHostPort       string
	CustomClock          Clock
}

type CredentialGetter interface {
	GetCredential(id string) (*Credential, error)
}

type NonceValidator interface {
	Validate(key, nonce string, ts int64) bool
}

// Authenticate authenticate the Hawk request from the HTTP request.
// Successful case returns credential information about requested user.
func (s *Server) Authenticate(req *http.Request) (*Credential, error) {
	// 0 is treated as empty. set to default value.
	if s.TimeStampSkew == 0 {
		s.TimeStampSkew = 60 * time.Second
	}

	clock := getClock(s.AuthOption)
	now := clock.Now(s.LocaltimeOffset)

	authzHeader := req.Header.Get("Authorization")
	if authzHeader == "" {
		return nil, errors.New("Authorization header not found.")
	}
	authzAttributes := parseHawkHeader(authzHeader)

	ts, err := strconv.ParseInt(authzAttributes["ts"], 10, 64)
	if err != nil {
		return nil, errors.New("Invalid ts value.")
	}

	artifacts := &Option{
		TimeStamp: ts,
		Nonce:     authzAttributes["nonce"],
		Hash:      authzAttributes["hash"],
		Ext:       authzAttributes["ext"],
		App:       authzAttributes["app"],
		Dlg:       authzAttributes["dlg"],
	}

	cred, err := s.CredentialGetter.GetCredential(authzAttributes["id"])
	if err != nil {
		// FIXME: logging error
		return nil, errors.New("Failed to get Credential.")
	}
	if cred.Key == "" {
		return nil, errors.New("Invalid Credential.")
	}

	var host string
	if s.AuthOption != nil {
		// set to custom host(and port) value
		if s.AuthOption.CustomHostNameHeader != "" {
			host = req.Header.Get(s.AuthOption.CustomHostNameHeader)
		}
		if s.AuthOption.CustomHostPort != "" {
			// forces override a value.
			host = s.AuthOption.CustomHostPort
		}
	}

	m := &Mac{
		Type:       Header,
		Credential: cred,
		Uri:        req.URL.String(),
		Method:     req.Method,
		HostPort:   host,
		Option:     artifacts,
	}
	mac, err := m.String()
	if err != nil {
		//FIXME: logging error
		return nil, errors.New("Failed to calculate MAC.")
	}

	if !fixedTimeComparison(mac, authzAttributes["mac"]) {
		return nil, errors.New("Bad MAC")
	}

	if req.Method == "POST" || req.Method == "PUT" {
		if artifacts.Hash == "" {
			return nil, errors.New("Missing required payload hash.")
		}

		ph := &PayloadHash{
			ContentType: req.Header.Get("Content-Type"),
			Payload:     s.Payload,
			Alg:         cred.Alg,
		}
		if !fixedTimeComparison(ph.String(), artifacts.Hash) {
			return nil, errors.New("Bad payload hash.")
		}
	}

	if s.NonceValidator != nil {
		if !s.NonceValidator.Validate(cred.Key, artifacts.Nonce, artifacts.TimeStamp) {
			return nil, errors.New("Invalid nonce.")
		}
	}
	if math.Abs(float64((artifacts.TimeStamp)-(now))) > s.TimeStampSkew.Seconds() {
		//FIXME: logging timestamp
		return nil, errors.New("Stale timestamp")
	}

	return cred, nil
}

// AuthenticateBewit authenticate the Hawk bewit request from the HTTP request.
// Successful case returns credential information about requested user.
func (s *Server) AuthenticateBewit(req *http.Request) (*Credential, error) {
	clock := getClock(s.AuthOption)
	now := clock.Now(s.LocaltimeOffset)

	encodedBewit := req.URL.Query().Get("bewit")
	if encodedBewit == "" {
		return nil, errors.New("Empty bewit.")
	}

	if req.Method != "GET" && req.Method != "HEAD" {
		return nil, errors.New("Invalid method.")
	}

	if req.Header.Get("Authorization") != "" {
		return nil, errors.New("Multiple authentications")
	}

	rawBewit, err := base64.RawURLEncoding.DecodeString(encodedBewit)
	if err != nil {
		return nil, errors.New("Failed to decode bewit parameter.")
	}

	parsedBewit := strings.Split(string(rawBewit), "\\")
	if len(parsedBewit) != 4 {
		return nil, errors.New("Invalid bewit structure.")
	}

	bewit := map[string]string{
		"id":  parsedBewit[0],
		"exp": parsedBewit[1],
		"mac": parsedBewit[2],
		"ext": parsedBewit[3],
	}

	if bewit["id"] == "" || bewit["exp"] == "" || bewit["mac"] == "" {
		return nil, errors.New("Missing bewit attributes.")
	}

	ts, err := strconv.ParseInt(bewit["exp"], 10, 64)
	if err != nil {
		return nil, errors.New("Invalid ts value.")
	}

	if ts <= now {
		return nil, errors.New("Access expired.")
	}

	cred, err := s.CredentialGetter.GetCredential(bewit["id"])
	if err != nil {
		// FIXME: logging error
		return nil, errors.New("Failed to get Credential.")
	}
	if cred.Key == "" {
		return nil, errors.New("Invalid Credential.")
	}

	removedBewitURL := removeBewitParam(req.URL)

	var host string
	if s.AuthOption != nil {
		// set to custom host(and port) value
		if s.AuthOption.CustomHostNameHeader != "" {
			host = req.Header.Get(s.AuthOption.CustomHostNameHeader)
		}
		if s.AuthOption.CustomHostPort != "" {
			// forces override a value.
			host = s.AuthOption.CustomHostPort
		}
	}

	m := &Mac{
		Type:       Bewit,
		Credential: cred,
		Uri:        removedBewitURL.String(),
		Method:     req.Method,
		HostPort:   host,
		Option: &Option{
			TimeStamp: ts,
			Nonce:     "",
			Ext:       bewit["ext"],
		},
	}
	mac, err := m.String()
	if err != nil {
		//FIXME: logging error
		return nil, errors.New("Failed to calculate MAC.")
	}

	if !fixedTimeComparison(mac, bewit["mac"]) {
		return nil, errors.New("Bad mac.")
	}

	return cred, nil
}

// Header builds a value to be set in the Server-Authorization header.
func (s *Server) Header(req *http.Request, cred *Credential, opt *Option) (string, error) {
	authzHeader := req.Header.Get("Authorization")
	authzAttributes := parseHawkHeader(authzHeader)

	if opt.Hash == "" && (req.Method == "POST" || req.Method == "PUT") {
		ph := &PayloadHash{
			ContentType: opt.ContentType,
			Payload:     opt.Payload,
			Alg:         cred.Alg,
		}
		opt.Hash = ph.String()
	}

	ts, err := strconv.ParseInt(authzAttributes["ts"], 10, 64)
	if err != nil {
		return "", errors.New("Invalid ts value.")
	}
	artifacts := &Option{
		TimeStamp: ts,
		Nonce:     authzAttributes["nonce"],
		Hash:      opt.Hash,
		Ext:       opt.Ext,
		App:       authzAttributes["app"],
		Dlg:       authzAttributes["dlg"],
	}

	var host string
	if s.AuthOption != nil {
		// set to custom host(and port) value
		if s.AuthOption.CustomHostNameHeader != "" {
			host = req.Header.Get(s.AuthOption.CustomHostNameHeader)
		}
		if s.AuthOption.CustomHostPort != "" {
			// forces override a value.
			host = s.AuthOption.CustomHostPort
		}
	}

	m := &Mac{
		Type:       Response,
		Credential: cred,
		Uri:        req.URL.String(),
		Method:     req.Method,
		HostPort:   host,
		Option:     artifacts,
	}

	mac, err := m.String()
	if err != nil {
		//FIXME: logging error
		return "", errors.New("Failed to calculate MAC.")
	}

	header := "Hawk " + `mac="` + mac + `"`

	if opt.Hash != "" {
		header = header + ", " + `hash="` + opt.Hash + `"`
	}

	if opt.Ext != "" {
		header = header + ", " + `ext="` + opt.Ext + `"`
	}

	return header, nil
}

func getClock(authOption *AuthOption) Clock {
	var clock Clock
	if authOption == nil || authOption.CustomClock == nil {
		clock = &LocalClock{}
	} else {
		clock = authOption.CustomClock
	}
	return clock
}

func removeBewitParam(u *url.URL) url.URL {
	removedQuery := &url.Values{}
	for key, _ := range u.Query() {
		if key == "bewit" {
			continue
		}
		removedQuery.Add(key, u.Query().Get(key))
	}
	removedUrl := *u
	removedUrl.RawQuery = removedQuery.Encode()

	return removedUrl
}
<|endoftext|>


<commit_before>// Copyright 2016 The LUCI Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package buildbucket

import (
	"encoding/hex"
	"fmt"
	"net/url"
	"strings"

	"github.com/golang/protobuf/ptypes"
	"golang.org/x/net/context"

	"go.chromium.org/gae/service/datastore"
	"go.chromium.org/luci/buildbucket"
	"go.chromium.org/luci/common/errors"
	"go.chromium.org/luci/milo/buildsource/swarming"
	"go.chromium.org/luci/milo/common"
	"go.chromium.org/luci/milo/common/model"
	"go.chromium.org/luci/milo/frontend/ui"
)

// BuildID implements buildsource.ID, and is the buildbucket notion of a build.
// It references a buildbucket build which may reference a swarming build.
type BuildID struct {
	// Project is the project which the build ID is supposed to reside in.
	Project string

	// Address is the Buildbucket's build address (required)
	Address string
}

// GetSwarmingID returns the swarming task ID of a buildbucket build.
func GetSwarmingID(c context.Context, buildAddress string) (*swarming.BuildID, *model.BuildSummary, error) {
	host, err := getHost(c)
	if err != nil {
		return nil, nil, err
	}

	bs := &model.BuildSummary{BuildKey: MakeBuildKey(c, host, buildAddress)}
	switch err := datastore.Get(c, bs); err {
	case nil:
		for _, ctx := range bs.ContextURI {
			u, err := url.Parse(ctx)
			if err != nil {
				continue
			}
			if u.Scheme == "swarming" && len(u.Path) > 1 {
				toks := strings.Split(u.Path[1:], "/")
				if toks[0] == "task" {
					return &swarming.BuildID{Host: u.Host, TaskID: toks[1]}, bs, nil
				}
			}
		}
		// continue to the fallback code below.

	case datastore.ErrNoSuchEntity:
		// continue to the fallback code below.

	default:
		return nil, nil, err
	}

	// DEPRECATED(2017-12-01) {{
	// This makes an RPC to buildbucket to obtain the swarming task ID.
	// Now that we include this data in the BuildSummary.ContextUI we should never
	// need to do this extra RPC. However, we have this codepath in place for old
	// builds.
	//
	// After the deprecation date, this code can be removed; the only effect will
	// be that buildbucket builds before 2017-11-03 will not render.
	client, err := newBuildbucketClient(c, host)
	if err != nil {
		return nil, nil, err
	}
	build, err := buildbucket.GetByAddress(c, client, buildAddress)
	switch {
	case err != nil:
		return nil, nil, errors.Annotate(err, "could not get build at %q", buildAddress).Err()
	case build == nil:
		return nil, nil, errors.Reason("build at %q not found", buildAddress).Tag(common.CodeNotFound).Err()
	}

	shost := build.Tags.Get("swarming_hostname")
	sid := build.Tags.Get("swarming_task_id")
	if shost == "" || sid == "" {
		return nil, nil, errors.New("not a valid LUCI build")
	}
	return &swarming.BuildID{Host: shost, TaskID: sid}, nil, nil
	// }}

}

// mixInSimplisticBlamelist populates the resp.Blame field from the
// commit/gitiles buildset (if any).
//
// HACK(iannucci) - Getting the frontend to render a proper blamelist will
// require some significant refactoring. To do this properly, we'll need:
//   * The frontend to get BuildSummary from the backend.
//   * BuildSummary to have a .PreviousBuild() API.
//   * The frontend to obtain the annotation streams itself (so it could see
//     the SourceManifest objects inside of them). Currently getRespBuild defers
//     to swarming's implementation of buildsource.ID.Get(), which only returns
//     the resp object.
func mixInSimplisticBlamelist(c context.Context, build *model.BuildSummary, rb *ui.MiloBuild) error {
	_, hist, err := build.PreviousByGitilesCommit(c)
	switch err {
	case nil:
	case model.ErrUnknownPreviousBuild:
		return nil
	default:
		return err
	}

	gc := build.GitilesCommit()
	rb.Blame = make([]*ui.Commit, len(hist.Commits))
	for i, c := range hist.Commits {
		rev := hex.EncodeToString(c.Hash)
		rb.Blame[i] = &ui.Commit{
			AuthorName:  c.AuthorName,
			AuthorEmail: c.AuthorEmail,
			Repo:        gc.RepoURL(),
			Description: c.Msg,
			// TODO(iannucci): also include the diffstat.

			// TODO(iannucci): this use of links is very sloppy; the frontend should
			// know how to render a Commit without having Links embedded in it.
			Revision: ui.NewLink(
				rev,
				gc.RepoURL()+"/+/"+rev, fmt.Sprintf("commit by %s", c.AuthorEmail)),
		}

		rb.Blame[i].CommitTime, _ = ptypes.Timestamp(c.CommitTime)
	}

	return nil
}

// getRespBuild fetches the full build state from Swarming and LogDog if
// available, otherwise returns an empty "pending build".
func getRespBuild(c context.Context, build *model.BuildSummary, sID *swarming.BuildID) (*ui.MiloBuild, error) {
	// TODO(nodir,hinoka): squash getRespBuild with toMiloBuild.

	// TODO(nodir,hinoka,iannucci): use annotations directly without fetching swarming task
	ret, err := sID.Get(c)
	if err != nil {
		return nil, err
	}

	if build != nil {
		if err := mixInSimplisticBlamelist(c, build, ret); err != nil {
			return nil, err
		}
	}

	return ret, nil
}

// Get returns a resp.MiloBuild based off of the buildbucket ID given by
// finding the coorisponding swarming build.
func (b *BuildID) Get(c context.Context) (*ui.MiloBuild, error) {
	sID, bs, err := GetSwarmingID(c, b.Address)
	if err != nil {
		return nil, err
	}
	return getRespBuild(c, bs, sID)
}

func (b *BuildID) GetLog(c context.Context, logname string) (text string, closed bool, err error) {
	return "", false, errors.New("buildbucket builds do not implement GetLog")
}
<commit_msg>[milo] Drop blamelist instead of failing build when gitiles is inaccessable.<commit_after>// Copyright 2016 The LUCI Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package buildbucket

import (
	"encoding/hex"
	"fmt"
	"net/http"
	"net/url"
	"strings"

	"github.com/golang/protobuf/ptypes"
	"golang.org/x/net/context"

	"go.chromium.org/gae/service/datastore"
	"go.chromium.org/luci/buildbucket"
	"go.chromium.org/luci/common/api/gitiles"
	"go.chromium.org/luci/common/errors"
	"go.chromium.org/luci/common/logging"
	"go.chromium.org/luci/milo/buildsource/swarming"
	"go.chromium.org/luci/milo/common"
	"go.chromium.org/luci/milo/common/model"
	"go.chromium.org/luci/milo/frontend/ui"
)

// BuildID implements buildsource.ID, and is the buildbucket notion of a build.
// It references a buildbucket build which may reference a swarming build.
type BuildID struct {
	// Project is the project which the build ID is supposed to reside in.
	Project string

	// Address is the Buildbucket's build address (required)
	Address string
}

// GetSwarmingID returns the swarming task ID of a buildbucket build.
func GetSwarmingID(c context.Context, buildAddress string) (*swarming.BuildID, *model.BuildSummary, error) {
	host, err := getHost(c)
	if err != nil {
		return nil, nil, err
	}

	bs := &model.BuildSummary{BuildKey: MakeBuildKey(c, host, buildAddress)}
	switch err := datastore.Get(c, bs); err {
	case nil:
		for _, ctx := range bs.ContextURI {
			u, err := url.Parse(ctx)
			if err != nil {
				continue
			}
			if u.Scheme == "swarming" && len(u.Path) > 1 {
				toks := strings.Split(u.Path[1:], "/")
				if toks[0] == "task" {
					return &swarming.BuildID{Host: u.Host, TaskID: toks[1]}, bs, nil
				}
			}
		}
		// continue to the fallback code below.

	case datastore.ErrNoSuchEntity:
		// continue to the fallback code below.

	default:
		return nil, nil, err
	}

	// DEPRECATED(2017-12-01) {{
	// This makes an RPC to buildbucket to obtain the swarming task ID.
	// Now that we include this data in the BuildSummary.ContextUI we should never
	// need to do this extra RPC. However, we have this codepath in place for old
	// builds.
	//
	// After the deprecation date, this code can be removed; the only effect will
	// be that buildbucket builds before 2017-11-03 will not render.
	client, err := newBuildbucketClient(c, host)
	if err != nil {
		return nil, nil, err
	}
	build, err := buildbucket.GetByAddress(c, client, buildAddress)
	switch {
	case err != nil:
		return nil, nil, errors.Annotate(err, "could not get build at %q", buildAddress).Err()
	case build == nil:
		return nil, nil, errors.Reason("build at %q not found", buildAddress).Tag(common.CodeNotFound).Err()
	}

	shost := build.Tags.Get("swarming_hostname")
	sid := build.Tags.Get("swarming_task_id")
	if shost == "" || sid == "" {
		return nil, nil, errors.New("not a valid LUCI build")
	}
	return &swarming.BuildID{Host: shost, TaskID: sid}, nil, nil
	// }}

}

// mixInSimplisticBlamelist populates the resp.Blame field from the
// commit/gitiles buildset (if any).
//
// HACK(iannucci) - Getting the frontend to render a proper blamelist will
// require some significant refactoring. To do this properly, we'll need:
//   * The frontend to get BuildSummary from the backend.
//   * BuildSummary to have a .PreviousBuild() API.
//   * The frontend to obtain the annotation streams itself (so it could see
//     the SourceManifest objects inside of them). Currently getRespBuild defers
//     to swarming's implementation of buildsource.ID.Get(), which only returns
//     the resp object.
func mixInSimplisticBlamelist(c context.Context, build *model.BuildSummary, rb *ui.MiloBuild) error {
	_, hist, err := build.PreviousByGitilesCommit(c)
	switch {
	case err == nil:
	case err == model.ErrUnknownPreviousBuild:
		return nil
	case gitiles.HTTPStatus(err) == http.StatusForbidden:
		return common.CodeUnauthorized.Tag().Apply(err)
	default:
		return err
	}

	gc := build.GitilesCommit()
	rb.Blame = make([]*ui.Commit, len(hist.Commits))
	for i, c := range hist.Commits {
		rev := hex.EncodeToString(c.Hash)
		rb.Blame[i] = &ui.Commit{
			AuthorName:  c.AuthorName,
			AuthorEmail: c.AuthorEmail,
			Repo:        gc.RepoURL(),
			Description: c.Msg,
			// TODO(iannucci): also include the diffstat.

			// TODO(iannucci): this use of links is very sloppy; the frontend should
			// know how to render a Commit without having Links embedded in it.
			Revision: ui.NewLink(
				rev,
				gc.RepoURL()+"/+/"+rev, fmt.Sprintf("commit by %s", c.AuthorEmail)),
		}

		rb.Blame[i].CommitTime, _ = ptypes.Timestamp(c.CommitTime)
	}

	return nil
}

// getRespBuild fetches the full build state from Swarming and LogDog if
// available, otherwise returns an empty "pending build".
func getRespBuild(c context.Context, build *model.BuildSummary, sID *swarming.BuildID) (*ui.MiloBuild, error) {
	// TODO(nodir,hinoka): squash getRespBuild with toMiloBuild.

	// TODO(nodir,hinoka,iannucci): use annotations directly without fetching swarming task
	ret, err := sID.Get(c)
	if err != nil {
		return nil, err
	}

	if build != nil {
		switch err := mixInSimplisticBlamelist(c, build, ret); {
		case common.ErrorTag.In(err) == common.CodeUnauthorized:
			logging.WithError(err).Warningf(c, "dropping blamelist; access is unauthorized")
		case err != nil:
			return nil, err
		}
	}

	return ret, nil
}

// Get returns a resp.MiloBuild based off of the buildbucket ID given by
// finding the coorisponding swarming build.
func (b *BuildID) Get(c context.Context) (*ui.MiloBuild, error) {
	sID, bs, err := GetSwarmingID(c, b.Address)
	if err != nil {
		return nil, err
	}
	return getRespBuild(c, bs, sID)
}

func (b *BuildID) GetLog(c context.Context, logname string) (text string, closed bool, err error) {
	return "", false, errors.New("buildbucket builds do not implement GetLog")
}
<|endoftext|>


<commit_before>package api

/*
#cgo CFLAGS: -I/usr/local/include
#cgo LDFLAGS: -L/usr/local/lib -lwkhtmltox -Wall -ansi -pedantic -ggdb
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <wkhtmltox/image.h>
extern void progress_changed_cb(void*, const int);
extern void error_cb(void*, char *msg);
extern void warning_cb(void*, char *msg);
extern void phase_changed_cb(void*);
extern void finished_cb(void*, const int);
static void setup_callbacks(wkhtmltoimage_converter * c) {
  wkhtmltoimage_set_progress_changed_callback(c, (wkhtmltoimage_int_callback)progress_changed_cb);
  wkhtmltoimage_set_error_callback(c, (wkhtmltoimage_str_callback)error_cb);
  wkhtmltoimage_set_warning_callback(c, (wkhtmltoimage_str_callback)warning_cb);
  wkhtmltoimage_set_phase_changed_callback(c, (wkhtmltoimage_void_callback)phase_changed_cb);
  wkhtmltoimage_set_finished_callback(c, (wkhtmltoimage_int_callback)finished_cb);
}
*/
import "C"

import (
	"unsafe"
)

type GlobalSettings struct {
	s *C.wkhtmltoimage_global_settings
}

type Converter struct {
	c               *C.wkhtmltoimage_converter
	ProgressChanged func(*Converter, int)
	Error           func(*Converter, string)
	Warning         func(*Converter, string)
	Phase           func(*Converter)
	Finished func(*Converter, int)
	quiet			bool
}

var converterMap map[unsafe.Pointer]*Converter

func wkInit() {
	converterMap = map[unsafe.Pointer]*Converter{}
	C.wkhtmltoimage_init(C.false)
}

func wkDeInit() {
	C.wkhtmltoimage_deinit()
}

func NewGlobalSettings() *GlobalSettings {
	wkInit()
	return &GlobalSettings{s: C.wkhtmltoimage_create_global_settings()}
}

func (gs *GlobalSettings) Set(name, value string) {
	c_name := C.CString(name)
	c_value := C.CString(value)
	defer C.free(unsafe.Pointer(c_name))
	defer C.free(unsafe.Pointer(c_value))
	C.wkhtmltoimage_set_global_setting(gs.s, c_name, c_value)
}

func (gs *GlobalSettings) NewConverter(html string, quiet bool) *Converter {
	cHtml := C.CString(html)
	defer C.free(unsafe.Pointer(cHtml))
	c := &Converter{c: C.wkhtmltoimage_create_converter(gs.s, cHtml), quiet: quiet}
	C.setup_callbacks(c.c)

	return c
}

//export progress_changed_cb
func progress_changed_cb(p unsafe.Pointer, i C.int) {
	conv := converterMap[p]
	if conv.ProgressChanged != nil && !conv.quiet {
		conv.ProgressChanged(conv, int(i))
	}
}

//export error_cb
func error_cb(p unsafe.Pointer, msg *C.char) {
	conv := converterMap[p]
	if conv.Error != nil && !conv.quiet {
		conv.Error(conv, C.GoString(msg))
	}
}

//export warning_cb
func warning_cb(p unsafe.Pointer, msg *C.char) {
	conv := converterMap[p]
	if conv.Warning != nil && !conv.quiet {
		conv.Warning(conv, C.GoString(msg))
	}
}

//export phase_changed_cb
func phase_changed_cb(p unsafe.Pointer) {
	conv := converterMap[p]
	if conv.Phase != nil && !conv.quiet {
		conv.Phase(conv)
	}
}

//export finished_cb
func finished_cb(c unsafe.Pointer, s C.int) {
	conv := converterMap[c]
	if conv.Finished != nil && !conv.quiet {
		conv.Finished(conv, int(s))
	}
}

func (converter *Converter) Convert() int {

	// To route callbacks right, we need to save a reference
	// to the converter object, base on the pointer.
	converterMap[unsafe.Pointer(converter.c)] = converter
	status := C.wkhtmltoimage_convert(converter.c)
	delete(converterMap, unsafe.Pointer(converter.c))
	if status != C.int(0) {
		return converter.ErrorCode()
	}
	return 0
}

func (converter *Converter) Output() (int64, string) {
	cc := C.CString("")
	ccc := (**C.uchar)(unsafe.Pointer(&cc))
	ll := C.wkhtmltoimage_get_output(converter.c, ccc)
	co := C.GoStringN((*C.char)(unsafe.Pointer(*ccc)), C.int(ll))
	return int64(ll), co
}

func (converter *Converter) ErrorCode() int {
	return int(C.wkhtmltoimage_http_error_code(converter.c))
}

func (converter *Converter) CurrentPhase() (int, string) {
	cpic := C.wkhtmltoimage_current_phase(converter.c)
	cpi := int(cpic)
	cps := C.GoString(C.wkhtmltoimage_phase_description(converter.c, cpic))
	return cpi, cps
}

func (converter *Converter) Destroy() {
	C.wkhtmltoimage_destroy_converter(converter.c)
	wkDeInit()
}
<commit_msg>deinit comment<commit_after>package api

/*
#cgo CFLAGS: -I/usr/local/include
#cgo LDFLAGS: -L/usr/local/lib -lwkhtmltox -Wall -ansi -pedantic -ggdb
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <wkhtmltox/image.h>
extern void progress_changed_cb(void*, const int);
extern void error_cb(void*, char *msg);
extern void warning_cb(void*, char *msg);
extern void phase_changed_cb(void*);
extern void finished_cb(void*, const int);
static void setup_callbacks(wkhtmltoimage_converter * c) {
  wkhtmltoimage_set_progress_changed_callback(c, (wkhtmltoimage_int_callback)progress_changed_cb);
  wkhtmltoimage_set_error_callback(c, (wkhtmltoimage_str_callback)error_cb);
  wkhtmltoimage_set_warning_callback(c, (wkhtmltoimage_str_callback)warning_cb);
  wkhtmltoimage_set_phase_changed_callback(c, (wkhtmltoimage_void_callback)phase_changed_cb);
  wkhtmltoimage_set_finished_callback(c, (wkhtmltoimage_int_callback)finished_cb);
}
*/
import "C"

import (
	"unsafe"
)

type GlobalSettings struct {
	s *C.wkhtmltoimage_global_settings
}

type Converter struct {
	c               *C.wkhtmltoimage_converter
	ProgressChanged func(*Converter, int)
	Error           func(*Converter, string)
	Warning         func(*Converter, string)
	Phase           func(*Converter)
	Finished func(*Converter, int)
	quiet			bool
}

var converterMap map[unsafe.Pointer]*Converter

func wkInit() {
	converterMap = map[unsafe.Pointer]*Converter{}
	C.wkhtmltoimage_init(C.false)
}

func wkDeInit() {
	C.wkhtmltoimage_deinit()
}

func NewGlobalSettings() *GlobalSettings {
	wkInit()
	return &GlobalSettings{s: C.wkhtmltoimage_create_global_settings()}
}

func (gs *GlobalSettings) Set(name, value string) {
	c_name := C.CString(name)
	c_value := C.CString(value)
	defer C.free(unsafe.Pointer(c_name))
	defer C.free(unsafe.Pointer(c_value))
	C.wkhtmltoimage_set_global_setting(gs.s, c_name, c_value)
}

func (gs *GlobalSettings) NewConverter(html string, quiet bool) *Converter {
	cHtml := C.CString(html)
	defer C.free(unsafe.Pointer(cHtml))
	c := &Converter{c: C.wkhtmltoimage_create_converter(gs.s, cHtml), quiet: quiet}
	C.setup_callbacks(c.c)

	return c
}

//export progress_changed_cb
func progress_changed_cb(p unsafe.Pointer, i C.int) {
	conv := converterMap[p]
	if conv.ProgressChanged != nil && !conv.quiet {
		conv.ProgressChanged(conv, int(i))
	}
}

//export error_cb
func error_cb(p unsafe.Pointer, msg *C.char) {
	conv := converterMap[p]
	if conv.Error != nil && !conv.quiet {
		conv.Error(conv, C.GoString(msg))
	}
}

//export warning_cb
func warning_cb(p unsafe.Pointer, msg *C.char) {
	conv := converterMap[p]
	if conv.Warning != nil && !conv.quiet {
		conv.Warning(conv, C.GoString(msg))
	}
}

//export phase_changed_cb
func phase_changed_cb(p unsafe.Pointer) {
	conv := converterMap[p]
	if conv.Phase != nil && !conv.quiet {
		conv.Phase(conv)
	}
}

//export finished_cb
func finished_cb(c unsafe.Pointer, s C.int) {
	conv := converterMap[c]
	if conv.Finished != nil && !conv.quiet {
		conv.Finished(conv, int(s))
	}
}

func (converter *Converter) Convert() int {

	// To route callbacks right, we need to save a reference
	// to the converter object, base on the pointer.
	converterMap[unsafe.Pointer(converter.c)] = converter
	status := C.wkhtmltoimage_convert(converter.c)
	delete(converterMap, unsafe.Pointer(converter.c))
	if status != C.int(0) {
		return converter.ErrorCode()
	}
	return 0
}

func (converter *Converter) Output() (int64, string) {
	cc := C.CString("")
	ccc := (**C.uchar)(unsafe.Pointer(&cc))
	ll := C.wkhtmltoimage_get_output(converter.c, ccc)
	co := C.GoStringN((*C.char)(unsafe.Pointer(*ccc)), C.int(ll))
	return int64(ll), co
}

func (converter *Converter) ErrorCode() int {
	return int(C.wkhtmltoimage_http_error_code(converter.c))
}

func (converter *Converter) CurrentPhase() (int, string) {
	cpic := C.wkhtmltoimage_current_phase(converter.c)
	cpi := int(cpic)
	cps := C.GoString(C.wkhtmltoimage_phase_description(converter.c, cpic))
	return cpi, cps
}

func (converter *Converter) Destroy() {
	C.wkhtmltoimage_destroy_converter(converter.c)
	//wkDeInit()
}
<|endoftext|>


<commit_before>package api

import (
	"sync"

	"github.com/micro/go-micro/broker"
	"github.com/micro/go-micro/client"
	"github.com/micro/go-micro/registry"
	"github.com/micro/go-micro/transport"

	"github.com/labstack/echo"
	"github.com/labstack/echo/engine"
	"github.com/labstack/echo/engine/fasthttp"
	"github.com/labstack/echo/engine/standard"
	"github.com/labstack/echo/middleware"
)

type microService struct {
	Service string
	Method  string
}

type PostAPI struct {
	Options Options

	httpSrv    *echo.Echo
	stopedChan chan struct{}

	apiService map[string]map[string]microService

	reglocker sync.Mutex
}

func NewPostAPI(opts ...Option) (srv *PostAPI, err error) {
	postAPI := PostAPI{
		Options: Options{
			Address:   ":8088",
			Path:      "/",
			BodyLimit: "2M",

			Client:    client.DefaultClient,
			Transport: transport.DefaultTransport,
			Registry:  registry.DefaultRegistry,
			Broker:    broker.DefaultBroker,
		},
		httpSrv:    nil,
		apiService: make(map[string]map[string]microService),
		stopedChan: make(chan struct{}),
	}

	for _, opt := range opts {
		opt(&postAPI.Options)
	}

	httpSrv := echo.New()

	groupRoot := httpSrv.Group("/")
	groupRoot.Get("ping", postAPI.pingHandle)
	groupRoot.Get("favicon.ico", postAPI.faviconICONHandle)

	groupAPI := httpSrv.Group(postAPI.Options.Path,
		middleware.BodyLimit(postAPI.Options.BodyLimit),
		postAPI.writeBasicHeaders,
		postAPI.cors)

	handlers := append([]echo.MiddlewareFunc{postAPI.parseAPIRequests}, postAPI.Options.BeforeHandlers...)

	groupAPI.Post("/:version", postAPI.rpcHandle, handlers...)
	groupAPI.Use(postAPI.Options.AfterHandlers...)

	httpSrv.SetHTTPErrorHandler(postAPI.errorHandle)

	postAPI.httpSrv = httpSrv

	srv = &postAPI

	return
}

func (p *PostAPI) Run() (err error) {

	var regWatcher registry.Watcher
	if regWatcher, err = p.Options.Registry.Watch(); err != nil {
		return
	}

	conf := engine.Config{
		Address:     p.Options.Address,
		TLSCertfile: p.Options.TLSCertFile,
		TLSKeyfile:  p.Options.TLSKeyFile,
	}

	var echoEngine engine.Server

	if p.Options.Engine == Fasthttp {
		echoEngine = fasthttp.WithConfig(conf)
	} else {
		echoEngine = standard.WithConfig(conf)
	}

	p.httpSrv.SetLogger(wrapperLogger(p.Options.Logger))

	go p.httpSrv.Run(echoEngine)

	if err = p.watch(regWatcher); err != nil {
		return
	}

	close(p.stopedChan)

	return
}
<commit_msg>set logger while initial<commit_after>package api

import (
	"sync"

	"github.com/micro/go-micro/broker"
	"github.com/micro/go-micro/client"
	"github.com/micro/go-micro/registry"
	"github.com/micro/go-micro/transport"

	"github.com/labstack/echo"
	"github.com/labstack/echo/engine"
	"github.com/labstack/echo/engine/fasthttp"
	"github.com/labstack/echo/engine/standard"
	"github.com/labstack/echo/middleware"
)

type microService struct {
	Service string
	Method  string
}

type PostAPI struct {
	Options Options

	httpSrv    *echo.Echo
	stopedChan chan struct{}

	apiService map[string]map[string]microService

	reglocker sync.Mutex
}

func NewPostAPI(opts ...Option) (srv *PostAPI, err error) {
	postAPI := PostAPI{
		Options: Options{
			Address:   ":8088",
			Path:      "/",
			BodyLimit: "2M",

			Client:    client.DefaultClient,
			Transport: transport.DefaultTransport,
			Registry:  registry.DefaultRegistry,
			Broker:    broker.DefaultBroker,
		},
		httpSrv:    nil,
		apiService: make(map[string]map[string]microService),
		stopedChan: make(chan struct{}),
	}

	for _, opt := range opts {
		opt(&postAPI.Options)
	}

	httpSrv := echo.New()

	groupRoot := httpSrv.Group("/")
	groupRoot.Get("ping", postAPI.pingHandle)
	groupRoot.Get("favicon.ico", postAPI.faviconICONHandle)

	groupAPI := httpSrv.Group(postAPI.Options.Path,
		middleware.BodyLimit(postAPI.Options.BodyLimit),
		postAPI.writeBasicHeaders,
		postAPI.cors)

	handlers := append([]echo.MiddlewareFunc{postAPI.parseAPIRequests}, postAPI.Options.BeforeHandlers...)

	groupAPI.Post("/:version", postAPI.rpcHandle, handlers...)
	groupAPI.Use(postAPI.Options.AfterHandlers...)

	httpSrv.SetHTTPErrorHandler(postAPI.errorHandle)
	httpSrv.SetLogger(wrapperLogger(postAPI.Options.Logger))

	postAPI.httpSrv = httpSrv

	srv = &postAPI

	return
}

func (p *PostAPI) Run() (err error) {

	var regWatcher registry.Watcher
	if regWatcher, err = p.Options.Registry.Watch(); err != nil {
		return
	}

	conf := engine.Config{
		Address:     p.Options.Address,
		TLSCertfile: p.Options.TLSCertFile,
		TLSKeyfile:  p.Options.TLSKeyFile,
	}

	var echoEngine engine.Server

	if p.Options.Engine == Fasthttp {
		echoEngine = fasthttp.WithConfig(conf)

	} else {
		echoEngine = standard.WithConfig(conf)
	}

	go p.httpSrv.Run(echoEngine)

	if err = p.watch(regWatcher); err != nil {
		return
	}

	close(p.stopedChan)

	return
}
<|endoftext|>