This commit is contained in:
crusader 2018-04-03 17:55:48 +09:00
commit 16c6290f63
9 changed files with 464 additions and 0 deletions

68
.gitignore vendored Normal file
View File

@ -0,0 +1,68 @@
# Created by .ignore support plugin (hsz.mobi)
### JetBrains template
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
# User-specific stuff:
.idea/**/workspace.xml
.idea/**/tasks.xml
.idea/dictionaries
# Sensitive or high-churn files:
.idea/**/dataSources/
.idea/**/dataSources.ids
.idea/**/dataSources.xml
.idea/**/dataSources.local.xml
.idea/**/sqlDataSources.xml
.idea/**/dynamic.xml
.idea/**/uiDesigner.xml
# Gradle:
.idea/**/gradle.xml
.idea/**/libraries
# Mongo Explorer plugin:
.idea/**/mongoSettings.xml
## File-based project format:
*.iws
## Plugin-specific files:
# IntelliJ
/out/
# mpeltonen/sbt-idea plugin
.idea_modules/
# JIRA plugin
atlassian-ide-plugin.xml
# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
fabric.properties
### Go template
# Binaries for programs and plugins
*.exe
*.dll
*.so
*.dylib
# Test binary, build with `go test -c`
*.test
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
# Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736
.glide/
.idea/
*.iml
vendor/
glide.lock
.DS_Store
dist/
debug

32
.vscode/launch.json vendored Normal file
View File

@ -0,0 +1,32 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "Debug",
"type": "go",
"request": "launch",
"mode": "debug",
"remotePath": "",
"port": 2345,
"host": "127.0.0.1",
"program": "${workspaceRoot}/main.go",
"env": {},
"args": [],
"showLog": true
},
{
"name": "File Debug",
"type": "go",
"request": "launch",
"mode": "debug",
"remotePath": "",
"port": 2345,
"host": "127.0.0.1",
"program": "${fileDirname}",
"env": {},
"args": [],
"showLog": true
}
]
}

87
client.go Normal file
View File

@ -0,0 +1,87 @@
package server
import (
"crypto/tls"
"fmt"
"net"
"time"
)
type Client struct {
Name string
Network string
Address string
TLSConfig *tls.Config
HandshakeTimeout time.Duration
KeepAlive time.Duration
LocalAddress net.Addr
MaxConnections int
}
func (c *Client) Dial() (net.Conn, error) {
if err := c.Validate(); nil != err {
return nil, err
}
var deadline time.Time
if 0 != c.HandshakeTimeout {
deadline = time.Now().Add(c.HandshakeTimeout)
}
d := &net.Dialer{
KeepAlive: c.KeepAlive,
Deadline: deadline,
LocalAddr: c.LocalAddress,
}
conn, err := d.Dial(c.Network, c.Address)
if nil != err {
return nil, err
}
if nil != c.TLSConfig {
cfg := c.TLSConfig.Clone()
tlsConn := tls.Client(conn, cfg)
if err := tlsConn.Handshake(); err != nil {
tlsConn.Close()
return nil, err
}
if !cfg.InsecureSkipVerify {
if err := tlsConn.VerifyHostname(cfg.ServerName); err != nil {
return nil, err
}
}
conn = tlsConn
}
return conn, nil
}
func (c *Client) Validate() error {
if "" == c.Name {
c.Name = "Client"
}
if "" == c.Network {
return fmt.Errorf("Client: Network is not valid")
}
if "" == c.Address {
return fmt.Errorf("Client: Address is not valid")
}
if 0 >= c.MaxConnections {
c.MaxConnections = 1
}
if 0 >= c.KeepAlive {
c.KeepAlive = DefaultKeepAlive
}
if 0 >= c.HandshakeTimeout {
c.HandshakeTimeout = DefaultHandshakeTimeout
}
return nil
}

7
const.go Normal file
View File

@ -0,0 +1,7 @@
package server
const (
DefaultHandshakeTimeout = 0
DefaultKeepAlive = 0
)

6
glide.yaml Normal file
View File

@ -0,0 +1,6 @@
package: git.loafle.net/commons/server-go
import:
- package: git.loafle.net/commons/logging-go
- package: git.loafle.net/commons/util-go
subpackages:
- context

19
server-context.go Normal file
View File

@ -0,0 +1,19 @@
package server
import (
cuc "git.loafle.net/commons/util-go/context"
)
type ServerContext interface {
cuc.Context
}
func NewServerContext(parent cuc.Context) ServerContext {
return &serverContext{
ServerContext: cuc.NewContext(parent),
}
}
type serverContext struct {
ServerContext
}

72
server-handler.go Normal file
View File

@ -0,0 +1,72 @@
package server
import (
"fmt"
"net"
)
type ServerHandler interface {
GetName() string
GetMaxConnections() int
ServerContext() ServerContext
Init(serverCTX ServerContext) error
OnStart(serverCTX ServerContext) error
OnError(serverCTX ServerContext, conn net.Conn, status int, reason error)
OnStop(serverCTX ServerContext)
Destroy(serverCTX ServerContext)
Listen(serverCTX ServerContext) (net.Listener, error)
Servlet() Servlet
Validate()
}
type ServerHandlers struct {
ServerHandler
Name string
MaxConnections int
}
func (sh *ServerHandlers) ServerContext() ServerContext {
return nil
}
func (sh *ServerHandlers) Init(serverCTX ServerContext) error {
return nil
}
func (sh *ServerHandlers) OnStart(serverCTX ServerContext) error {
return nil
}
func (sh *ServerHandlers) OnError(serverCTX ServerContext, conn net.Conn, status int, reason error) {
}
func (sh *ServerHandlers) OnStop(serverCTX ServerContext) {
}
func (sh *ServerHandlers) Destroy(serverCTX ServerContext) {
}
func (sh *ServerHandlers) Listen(serverCTX ServerContext) (net.Listener, error) {
return nil, fmt.Errorf("Server: Method[ServerHandler.Listen] is not implemented")
}
func (sh *ServerHandlers) Servlet() Servlet {
return nil
}
func (sh *ServerHandlers) Validate() {
if "" == sh.Name {
sh.Name = "Server"
}
if 0 >= sh.MaxConnections {
sh.MaxConnections = 0
}
}

166
server.go Normal file
View File

@ -0,0 +1,166 @@
package server
import (
"context"
"fmt"
"net"
"sync"
"sync/atomic"
"time"
"git.loafle.net/commons/logging-go"
)
type Server struct {
ServerHandler ServerHandler
ctx ServerContext
servlets sync.Map
stopChan chan struct{}
stopWg sync.WaitGroup
}
func (s *Server) ListenAndServe() error {
if s.stopChan != nil {
return fmt.Errorf("Server: server is already running. Stop it before starting it again")
}
var (
err error
listener net.Listener
)
if nil == s.ServerHandler {
panic("Server: server handler must be specified.")
}
s.ServerHandler.Validate()
s.ctx = s.ServerHandler.ServerContext()
if nil == s.ctx {
return fmt.Errorf("Server: ServerContext is nil")
}
if err = s.ServerHandler.Init(s.ctx); nil != err {
return fmt.Errorf("Server: Initialization of server has been failed %v", err)
}
if listener, err = s.ServerHandler.Listen(s.ctx); nil != err {
return err
}
s.stopChan = make(chan struct{})
s.stopWg.Add(1)
return s.handleLoop(listener)
}
func (s *Server) Shutdown(ctx context.Context) error {
if s.stopChan == nil {
return fmt.Errorf("Server: server must be started before stopping it")
}
close(s.stopChan)
s.stopWg.Wait()
s.ServerHandler.Destroy(s.ctx)
s.stopChan = nil
return nil
}
func (s *Server) ConnectionSize() int {
var sz int
s.servlets.Range(func(k, v interface{}) bool {
sz++
return true
})
return sz
}
func (s *Server) handleLoop(listener net.Listener) error {
var (
stopping atomic.Value
conn net.Conn
err error
)
defer func() {
s.ServerHandler.OnStop(s.ctx)
s.stopWg.Done()
}()
if err = s.ServerHandler.OnStart(s.ctx); nil != err {
return err
}
for {
acceptChan := make(chan struct{})
go func() {
if conn, err = listener.Accept(); err != nil {
if nil == stopping.Load() {
logging.Logger().Errorf("Server: Cannot accept new connection: [%s]", err)
}
}
close(acceptChan)
}()
select {
case <-s.stopChan:
stopping.Store(true)
listener.Close()
<-acceptChan
return nil
case <-acceptChan:
}
if nil != err {
select {
case <-s.stopChan:
return nil
case <-time.After(time.Second):
}
continue
}
if 0 < s.ServerHandler.GetMaxConnections() {
sz := s.ConnectionSize()
if sz >= s.ServerHandler.GetMaxConnections() {
logging.Logger().Warnf("max connections size %d, refuse\n", sz)
conn.Close()
continue
}
}
s.stopWg.Add(1)
go s.handleConnection(conn)
}
}
func (s *Server) handleConnection(conn net.Conn) {
servlet := s.ServerHandler.Servlet()
defer func() {
s.servlets.Delete(servlet)
s.stopWg.Done()
}()
if nil == servlet {
logging.Logger().Errorf("Server: Servlet is nil")
}
s.servlets.Store(servlet, true)
servletStopChan := make(chan struct{})
doneChan := make(chan struct{})
go servlet.Handle(s.ctx, conn, doneChan, servletStopChan)
select {
case <-doneChan:
close(servletStopChan)
conn.Close()
case <-s.stopChan:
close(servletStopChan)
conn.Close()
<-doneChan
}
}

7
servlet.go Normal file
View File

@ -0,0 +1,7 @@
package server
import "net"
type Servlet interface {
Handle(serverCTX ServerContext, conn net.Conn, doneChan chan<- struct{}, stopChan <-chan struct{})
}