Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
branch: master
Fetching contributors…

Cannot retrieve contributors at this time

135 lines (118 sloc) 3.327 kb
package main
import (
"bufio"
"bytes"
"github.com/temoto/oakmole/oakmole"
"io"
"log"
"net"
"net/http"
"runtime"
"time"
)
const IOTimeout time.Duration = 10 * time.Second
const ReadBufferSize = 16 << 10
var robotsDisallowBytes = []byte("HTTP/1.0 200 OK\r\nContent-Length: 26\r\n\r\nUser-agent: *\nDisallow: /\n")
func acceptLoop(listen *net.TCPListener, out chan *oakmole.Record) {
// TODO: try runtime.LockOSThread()
// TODO: create buffers
for {
conn, err := listen.AcceptTCP()
if err != nil {
log.Println(err)
return
}
go connectionHandler(conn, out)
}
}
func connectionHandler(conn *net.TCPConn, out chan *oakmole.Record) {
defer conn.Close()
timeBegin := time.Now()
conn.SetKeepAlive(false)
conn.SetLinger(0)
conn.SetDeadline(timeBegin.Add(IOTimeout))
addrLocal := conn.LocalAddr().(*net.TCPAddr)
addrRemote := conn.RemoteAddr().(*net.TCPAddr)
log.Println("New connection from", addrRemote)
// TODO: reuse existing buffers
buffer := make([]byte, ReadBufferSize)
var httpRequest *http.Request
totalSize := 0
for i := 1; i <= 3 && totalSize < ReadBufferSize; i++ {
size, err := conn.Read(buffer[totalSize:])
totalSize += size
if err == io.EOF {
break
}
if err != nil {
// Only log IO timeout on first Read().
// Later it just means that client already sent everything.
if netErr, ok := err.(net.Error); i == 1 || !ok || !netErr.Timeout() {
log.Println("Read: try:", i, "local:", addrLocal, "remote:", addrRemote, "error:", err)
}
if i == 1 {
return
}
break
}
// Try to parse HTTP request.
// This allows to stop reading from socket early.
// TODO: reuse existing bufio.Reader
bufReader := bufio.NewReader(bytes.NewReader(buffer))
httpRequest, err = http.ReadRequest(bufReader)
httpRequest.Body.Close()
if err == nil {
break
} else {
httpRequest = nil
}
}
buffer = buffer[:totalSize]
record := &oakmole.Record{
Timestamp: uint64(timeBegin.UnixNano() / 1000),
LocalIP: addrLocal.IP,
RemoteIP: addrRemote.IP,
Body: buffer,
}
if httpRequest != nil {
record.HttpHost = []byte(httpRequest.Host)
} else {
record.HttpHost = readHost(buffer)
}
// log.Println("Read: success local:", addrLocal, "remote:", addrRemote, "size:", totalSize, "first bytes:", string(buffer[:20]))
// t1 := time.Now()
out <- record
// outSendTime := time.Now().Sub(t1)
// log.Println("connectionHandler: out<- time:", outSendTime)
if httpRequest != nil && httpRequest.Method == "GET" && httpRequest.RequestURI == "/robots.txt" {
conn.Write(robotsDisallowBytes)
}
}
func startListener(bind *string, out chan *oakmole.Record) *net.TCPListener {
addr, err := net.ResolveTCPAddr("tcp", *bind)
if err != nil {
log.Fatalln(err)
}
log.Println("Listening on", addr)
listen, err := net.ListenTCP("tcp", addr)
if err != nil {
log.Fatalln(err)
}
for i := 1; i <= runtime.NumCPU(); i++ {
go acceptLoop(listen, out)
}
return listen
}
func readHost(b []byte) []byte {
beginBytes := []byte("\nHost: ")
const beginBytesLength = 7
indexBegin := bytes.Index(b, beginBytes)
if indexBegin == -1 {
return nil
}
indexEnd := bytes.IndexByte(b[indexBegin+beginBytesLength:], '\r')
if indexEnd == -1 {
return nil
}
return b[indexBegin+beginBytesLength : indexBegin+beginBytesLength+indexEnd]
}
Jump to Line
Something went wrong with that request. Please try again.