git clone https://github.com/m0sth8/skimmer ./skimmer
go get -d ./src/
go run ./src/main.go
git checkout step-1
// Martini represents the top level web application. inject.Injector methods can be invoked to map services on a global level. type Martini struct { inject.Injector handlers []Handler action Handler logger *log.Logger }
// Classic creates a classic Martini with some basic default middleware - martini.Logger, martini.Recovery, and martini.Static. func Classic() *ClassicMartini { r := NewRouter() m := New() m.Use(Logger()) m.Use(Recovery()) m.Use(Static("public")) m.Action(r.Handle) return &ClassicMartini{m, r} }
Any
method from the router, intercepting any URLs and methods. The router interface is described in Martini like this: type Router interface { // Get adds a route for a HTTP GET request to the specified matching pattern. Get(string, ...Handler) Route // Patch adds a route for a HTTP PATCH request to the specified matching pattern. Patch(string, ...Handler) Route // Post adds a route for a HTTP POST request to the specified matching pattern. Post(string, ...Handler) Route // Put adds a route for a HTTP PUT request to the specified matching pattern. Put(string, ...Handler) Route // Delete adds a route for a HTTP DELETE request to the specified matching pattern. Delete(string, ...Handler) Route // Options adds a route for a HTTP OPTIONS request to the specified matching pattern. Options(string, ...Handler) Route // Any adds a route for any HTTP method request to the specified matching pattern. Any(string, ...Handler) Route // NotFound sets the handlers that are called when a no route matches a request. Throws a basic 404 by default. NotFound(...Handler) // Handle is the entry point for routing. This is used as a martini.Handler Handle(http.ResponseWriter, *http.Request, Context) }
":param"
, regular expressions, as well as glob . The second parameter and the following ones take the function that will handle the request. Since Martini supports a chain of handlers, you can add various auxiliary handlers here, such as checking access rights. We still have nothing to do with it, so we will add only one handler with an interface that is processed by the usual Go web handler (an example of development on it can be found in the documentation ). Here is the code of our handler: func main() { api := martini.Classic() api.Any("/", func(res http.ResponseWriter, req *http.Request,) { if dumped, err := httputil.DumpRequest(req, true); err == nil { res.WriteHeader(200) res.Write(dumped) } else { res.WriteHeader(500) fmt.Fprintf(res, "Error: %v", err) } }) api.Run() }
go run ./src/main.go
> curl -X POST -d "fizz=buzz" http://127.0.0.1:3000 POST / HTTP/1.1 Host: 127.0.0.1:3000 Accept: */* Content-Type: application/x-www-form-urlencoded User-Agent: curl/7.24.0 (x86_64-apple-darwin12.0) libcurl/7.24.0 OpenSSL/0.9.8y zlib/1.2.5 fizz=buzz
git checkout step-2
The order of the fields in the structure is quite important, but we will not think about it, but those who want to know how order affects the size of the structure in memory can read these articles here - www.goinggo.net/2013/07/understanding-type-in -go.html and www.geeksforgeeks.org/structure-member-alignment-padding-and-data-packing .
Tags are normal lines that do not affect the program as a whole, but you can read them using the package of reflection while the program is running (so-called introspection), and based on this, change your behavior (about how to work with tags through reflection ). In our example, the json package takes the tag value into account when encoding / decoding, something like this:
package main import ( "reflect" "fmt" ) type Bin struct { Name string `json:"name"` } func main() { bin := Bin{} bt := reflect.TypeOf(bin) field := bt.Field(0) fmt.Printf("Field's '%s' json name is '%s'", field.Name, field.Tag.Get("json")) }
Will outputField's 'Name' json name is 'name'
The encoding / json package supports various options when generating tags:
// Field int `json:"-"` // json myName Field int `json:"myName"`
The second parameter may be, for example, the omitempty option - if the value in json is omitted, the field is not filled. So for example, if the field is a link, we can find out if it is in the json object by comparing it with nil. You can read more about json serialization in the documentation.
type Bin struct { Name string `json:"name"` Created int64 `json:"created"` Updated int64 `json:"updated"` RequestCount int `json:"requestCount"` } func NewBin() *Bin { now := time.Now().Unix() bin := Bin{ Created: now, Updated: now, Name: rs.Generate(6), } return &bin }
Structures in Go can be initialized in two ways:
1) Mandatory listing of all fields in order:
Bin{rs.Generate(6), now, now, 0}
2) Indicating the fields for which values are assigned:
Bin{ Created: now, Updated: now, Name: rs.Generate(6), }
Fields that are not specified accept default values. For example, for integers it will be 0, for strings - the empty string "", for links, channels, arrays, slices and dictionaries - this will be nil. Read more in the documentation . The main thing to remember is that you cannot mix these two types of initialization.
var rs = NewRandomString("0123456789abcdefghijklmnopqrstuvwxyz")
type RandomString struct { pool string rg *rand.Rand } func NewRandomString(pool string) *RandomString { return &RandomString{ pool, rand.New(rand.NewSource(time.Now().Unix())), } } func (rs *RandomString) Generate(length int) (r string) { if length < 1 { return } b := make([]byte, length) for i, _ := range b { b[i] = rs.pool[rs.rg.Intn(len(rs.pool))] } r = string(b) return }
api.Post("/api/v1/bins/", func(r render.Render){ bin := NewBin() bins[bin.Name] = bin history = append(history, bin.Name) r.JSON(http.StatusCreated, bin) })
api.Get("/api/v1/bins/", func(r render.Render){ filteredBins := []*Bin{} for _, name := range(history) { if bin, ok := bins[name]; ok { filteredBins = append(filteredBins, bin) } } r.JSON(http.StatusOK, filteredBins) })
api.Get("/api/v1/bins/:bin", func(r render.Render, params martini.Params){ if bin, ok := bins[params["bin"]]; ok{ r.JSON(http.StatusOK, bin) } else { r.Error(http.StatusNotFound) } })
In Go, we can access the dictionary element in two ways:
- By requesting the value of the key
a := m[key]
, in this case either the key value in the dictionary, if any, or the default value of the initialization of the value type is returned. Thus, for example for numbers, it is difficult to understand whether the key contains 0 or just the value of this key does not exist. Therefore, the second option is provided for in go.- In this way, by requesting by key and get its value by the first parameter and the indicator of the existence of this key by the second parameter -
a, ok := m[key]
go run ./src/main.go
> curl -i -X POST "127.0.0.1:3000/api/v1/bins/" HTTP/1.1 201 Created Content-Type: application/json; charset=UTF-8 Date: Mon, 03 Mar 2014 04:10:38 GMT Content-Length: 76 {"name":"7xpogf","created":1393819838,"updated":1393819838,"requestCount":0}
> curl -i "127.0.0.1:3000/api/v1/bins/" HTTP/1.1 200 OK Content-Type: application/json; charset=UTF-8 Date: Mon, 03 Mar 2014 04:11:18 GMT Content-Length: 78 [{"name":"7xpogf","created":1393819838,"updated":1393819838,"requestCount":0}]
curl -i "127.0.0.1:3000/api/v1/bins/7xpogf" HTTP/1.1 200 OK Content-Type: application/json; charset=UTF-8 Date: Mon, 03 Mar 2014 04:12:13 GMT Content-Length: 76 {"name":"7xpogf","created":1393819838,"updated":1393819838,"requestCount":0}
git checkout step-3
type Request struct { Id string `json:"id"` Created int64 `json:"created"` Method string `json:"method"` // GET, POST, PUT, etc. Proto string `json:"proto"` // "HTTP/1.0" Header http.Header `json:"header"` ContentLength int64 `json:"contentLength"` RemoteAddr string `json:"remoteAddr"` Host string `json:"host"` RequestURI string `json:"requestURI"` Body string `json:"body"` FormValue map[string][]string `json:"formValue"` FormFile []string `json:"formFile"` }
func NewRequest(httpRequest *http.Request, maxBodySize int) *Request { var ( bodyValue string formValue map[string][]string formFile []string ) // if body, err := ioutil.ReadAll(httpRequest.Body); err == nil { if len(body) > 0 && maxBodySize != 0 { if maxBodySize == -1 || httpRequest.ContentLength < int64(maxBodySize) { bodyValue = string(body) } else { bodyValue = fmt.Sprintf("%s\n<<<TRUNCATED , %d of %d", string(body[0:maxBodySize]), maxBodySize, httpRequest.ContentLength) } } httpRequest.Body = ioutil.NopCloser(bytes.NewBuffer(body)) defer httpRequest.Body.Close() } httpRequest.ParseMultipartForm(0) if httpRequest.MultipartForm != nil { formValue = httpRequest.MultipartForm.Value for key := range httpRequest.MultipartForm.File { formFile = append(formFile, key) } } else { formValue = httpRequest.PostForm } request := Request{ Id: rs.Generate(12), Created: time.Now().Unix(), Method: httpRequest.Method, Proto: httpRequest.Proto, Host: httpRequest.Host, Header: httpRequest.Header, ContentLength: httpRequest.ContentLength, RemoteAddr: httpRequest.RemoteAddr, RequestURI: httpRequest.RequestURI, FormValue: formValue, FormFile: formFile, Body: bodyValue, } return &request }
type Storage interface { LookupBin(name string) (*Bin, error) // get one bin element by name LookupBins(names []string) ([]*Bin, error) // get slice of bin elements LookupRequest(binName, id string) (*Request, error) // get request from bin by id LookupRequests(binName string, from, to int) ([]*Request, error) // get slice of requests from bin by position CreateBin(bin *Bin) error // create bin in memory storage UpdateBin(bin *Bin) error // save CreateRequest(bin *Bin, req *Request) error }
The interfaces in Go are a contract linking the expected functionality and the actual implementation. In our case, we described the storage interface, which we will use later in the program, but depending on the settings, the implementation may be completely different (for example, it may be Redis or Mongo). Learn more about interfaces .
type BaseStorage struct { maxRequests int }
type MemoryStorage struct { BaseStorage sync.RWMutex binRecords map[string]*BinRecord }
Anonymous fields enable us to call methods and fields of anonymous structures directly. For example, if we have a variable obj of type MemoryStorage, we can access the maxRequests field directly obj.BaseStorage.maxRequests, or as if they are members of the MemoryStorage obj.maxRequests itself. More information about anonymous fields in data structures can be found in the documentation .
type BinRecord struct { bin *Bin requests []*Request requestMap map[string]*Request }
Dictionaries in Go in the current implementation are a hash of the table, so searching for an element in the dictionary has a constant value. More information about the internal device can be found in this excellent article .
func (binRecord *BinRecord) ShrinkRequests(size int) { if size > 0 && len(binRecord.requests) > size { requests := binRecord.requests lenDiff := len(requests) - size removed := requests[:lenDiff] for _, removedReq := range removed { delete(binRecord.requestMap, removedReq.Id) } requests = requests[lenDiff:] binRecord.requests = requests } }
package skimmer import ( "errors" "sync" ) type MemoryStorage struct { BaseStorage sync.RWMutex binRecords map[string]*BinRecord } type BinRecord struct { bin *Bin requests []*Request requestMap map[string]*Request } func (binRecord *BinRecord) ShrinkRequests(size int) { if size > 0 && len(binRecord.requests) > size { requests := binRecord.requests lenDiff := len(requests) - size removed := requests[:lenDiff] for _, removedReq := range removed { delete(binRecord.requestMap, removedReq.Id) } requests = requests[lenDiff:] binRecord.requests = requests } } func NewMemoryStorage(maxRequests int) *MemoryStorage { return &MemoryStorage{ BaseStorage{ maxRequests: maxRequests, }, sync.RWMutex{}, map[string]*BinRecord{}, } } func (storage *MemoryStorage) getBinRecord(name string) (*BinRecord, error) { storage.RLock() defer storage.RUnlock() if binRecord, ok := storage.binRecords[name]; ok { return binRecord, nil } return nil, errors.New("Bin not found") } func (storage *MemoryStorage) LookupBin(name string) (*Bin, error) { if binRecord, err := storage.getBinRecord(name); err == nil { return binRecord.bin, nil } else { return nil, err } } func (storage *MemoryStorage) LookupBins(names []string) ([]*Bin, error) { bins := []*Bin{} for _, name := range names { if binRecord, err := storage.getBinRecord(name); err == nil { bins = append(bins, binRecord.bin) } } return bins, nil } func (storage *MemoryStorage) CreateBin(bin *Bin) error { storage.Lock() defer storage.Unlock() binRec := BinRecord{bin, []*Request{}, map[string]*Request{}} storage.binRecords[bin.Name] = &binRec return nil } func (storage *MemoryStorage) UpdateBin(_ *Bin) error { return nil } func (storage *MemoryStorage) LookupRequest(binName, id string) (*Request, error) { if binRecord, err := storage.getBinRecord(binName); err == nil { if request, ok := binRecord.requestMap[id]; ok { return request, nil } else { return nil, errors.New("Request not found") } } else { return nil, err } } func (storage *MemoryStorage) LookupRequests(binName string, from int, to int) ([]*Request, error) { if binRecord, err := storage.getBinRecord(binName); err == nil { requestLen := len(binRecord.requests) if to >= requestLen { to = requestLen } if to < 0 { to = 0 } if from < 0 { from = 0 } if from > to { from = to } reversedLen := to - from reversed := make([]*Request, reversedLen) for i, request := range binRecord.requests[from:to] { reversed[reversedLen-i-1] = request } return reversed, nil } else { return nil, err } } func (storage *MemoryStorage) CreateRequest(bin *Bin, req *Request) error { if binRecord, err := storage.getBinRecord(bin.Name); err == nil { storage.Lock() defer storage.Unlock() binRecord.requests = append(binRecord.requests, req) binRecord.requestMap[req.Id] = req binRecord.ShrinkRequests(storage.maxRequests) binRecord.bin.RequestCount = len(binRecord.requests) return nil } else { return err } }
memoryStorage := NewMemoryStorage(MAX_REQUEST_COUNT) api.MapTo(memoryStorage, (*Storage)(nil))
api.Post("/api/v1/bins/", func(r render.Render, storage Storage){ bin := NewBin() if err := storage.CreateBin(bin); err == nil { history = append(history, bin.Name) r.JSON(http.StatusCreated, bin) } else { r.JSON(http.StatusInternalServerError, ErrorMsg{err.Error()}) } }) api.Get("/api/v1/bins/", func(r render.Render, storage Storage){ if bins, err := storage.LookupBins(history); err == nil { r.JSON(http.StatusOK, bins) } else { r.JSON(http.StatusInternalServerError, ErrorMsg{err.Error()}) } }) api.Get("/api/v1/bins/:bin", func(r render.Render, params martini.Params, storage Storage){ if bin, err := storage.LookupBin(params["bin"]); err == nil{ r.JSON(http.StatusOK, bin) } else { r.JSON(http.StatusNotFound, ErrorMsg{err.Error()}) } })
// api.Get("/api/v1/bins/:bin/requests/", func(r render.Render, storage Storage, params martini.Params, req *http.Request){ if bin, error := storage.LookupBin(params["bin"]); error == nil { from := 0 to := 20 if fromVal, err := strconv.Atoi(req.FormValue("from")); err == nil { from = fromVal } if toVal, err := strconv.Atoi(req.FormValue("to")); err == nil { to = toVal } if requests, err := storage.LookupRequests(bin.Name, from, to); err == nil { r.JSON(http.StatusOK, requests) } else { r.JSON(http.StatusInternalServerError, ErrorMsg{err.Error()}) } } else { r.Error(http.StatusNotFound) } }) // Request api.Get("/api/v1/bins/:bin/requests/:request", func(r render.Render, storage Storage, params martini.Params){ if request, err := storage.LookupRequest(params["bin"], params["request"]); err == nil { r.JSON(http.StatusOK, request) } else { r.JSON(http.StatusNotFound, ErrorMsg{err.Error()}) } }) // http Request Bin(name) api.Any("/bins/:name", func(r render.Render, storage Storage, params martini.Params, req *http.Request){ if bin, error := storage.LookupBin(params["name"]); error == nil { request := NewRequest(req, REQUEST_BODY_SIZE) if err := storage.CreateRequest(bin, request); err == nil { r.JSON(http.StatusOK, request) } else { r.JSON(http.StatusInternalServerError, ErrorMsg{err.Error()}) } } else { r.Error(http.StatusNotFound) } })
> curl -i -X POST "127.0.0.1:3000/api/v1/bins/" HTTP/1.1 201 Created Content-Type: application/json; charset=UTF-8 Date: Mon, 03 Mar 2014 12:19:28 GMT Content-Length: 76 {"name":"ws87ui","created":1393849168,"updated":1393849168,"requestCount":0}
> curl -X POST -d "fizz=buzz" http://127.0.0.1:3000/bins/ws87ui {"id":"i0aigrrc1b40","created":1393849284,...}
> curl http://127.0.0.1:3000/api/v1/bins/ws87ui/requests/ [{"id":"i0aigrrc1b40","created":1393849284,...}]
Source: https://habr.com/ru/post/208680/
All Articles