Bingo

First launched on Golang Chinese website

This is a thing I’ve been writing lately…

I have just transferred from PHP, so I am not familiar with the features of Go. I have applied GIN, and I think it is quite good, but some syntax is not as convenient as Laravel

So I thought I’d build a new wheel… en …. sauce

Bingo is a lightweight API framework based on the GO language that focuses on building restful apis

GitHub address: Silsuer/Bingo

I’ve made a lot of changes recently, and the gayHub doesn’t match this article, so I’ll post the commit link here and here

The directory structure

  • The app places code related to the website
  • Core places framework core code A
  • Vendor places third-party libraries and uses Glide to manage third-party libraries
  • Public places the HTML code

The development process

Go’s NET package is extremely useful, and developing frameworks with it is extremely fast (even faster than writing PHP frameworks…).

First, identify the main function, instantiate a structure in main, and then call the Run function within it

Hands-on operation:

func main(a) {
  // New a bingo object, then bingo.run ()
  // Specify a static directory, which by default points to index.html, to load the routing file
  // Load the env file
    bingo := new(core.Bingo)
    bingo.Run(": 12345")}Copy the code

Go ahead and write the bingo file:

 func (b *Bingo) Run(port string) {
     // Pass in a port number with no return value. Enable HTTP listening based on the port number
 
     // Resources are initialized, all routes, configuration files are loaded, and so on
     // Instantiate the Router class, which retrieves json files from all router directories and loads the data based on the configuration in JSON
     // instantiate all data in env and config folders according to the configuration
     // Start defining the route based on the route list and start the HTTP server based on the port number
     http.ListenAndServe(port, bin)
     // TODO listens for smooth upgrades and restarts
 }
Copy the code

The Run function is as simple as a single line of code that starts an Http server and listens for incoming ports,

Since we have to control the routing ourselves, we can’t use the HTTP service in the NET package. There are many principles on the web that are clear

We need to implement the ServeHTTP method ourselves to implement the Mux router interface, so we’ll write another ServeHTTP method

    func (b *Bingo) ServeHTTP(w http.ResponseWriter, r *http.Request) {
        flag := false   // This variable is used to mark whether a dynamic route has been found
        // Every HTTP request goes to this point, where it is assigned a method to call based on the requested URL
        params := []reflect.Value{reflect.ValueOf(w), reflect.ValueOf(r)}
        for _, v := range RoutesList {
            // Detect middleware, start middleware first according to middleware, and then register other routes
            // Check the route and point to the required data according to the route
            if r.URL.Path == v.path && r.Method == v.method {
                  flag = true   // The route is found, no static server is needed
                  
                //TODO calls a common middleware in which to find routes and call middleware endpoints and other functions
    
                // Check whether middleware exists in this route, if so, call sequentially
                for _, m := range v.middleware {
                    if mid, ok := MiddlewareMap[m]; ok { // Determine if the middleware is registered
                        rmid := reflect.ValueOf(mid)
                        params = rmid.MethodByName("Handle").Call(params) // Execute middleware, return values array
                        // Determine the result of the middleware execution and whether to continue down
                        str := rmid.Elem().FieldByName("ResString").String()
                        ifstr ! ="" {
                            status := rmid.Elem().FieldByName("Status").Int()
                            // The string is not empty, check the status code, default return 500 error
                            if status == 0 {
                                status = 500
                            }
                            w.WriteHeader(int(status))
                            fmt.Fprint(w,str)
                            return}}}// Check success, start calling method
                // Get a structure under a controller package
                if d, ok := ControllerMap[v.controller]; ok { // if c is a struct, call the method mounted on c
                    reflect.ValueOf(d).MethodByName(v.function).Call(params)
                }
                // Stop the backward execution
                return}}// If the routing list is still not there, go to the static server
        if! flag {// Go to the static directory
             http.ServeFile(w,r,GetPublicPath()+ r.URL.Path)
        }
        return
    }
Copy the code

As you can see, we have redefined the ServeHttp method, where we get different controller or middleware constructs by reflection, depending on the URL the browser visits, and

Call the corresponding method, if the URL we visit is not defined, will go to the static folder to look, if found, output static file, otherwise output 404 page

(P.S. Because we are implementing a stateless API rapid development framework, no template rendering is required and all data is transferred to the page via Ajax)

Notice that in this function I use MiddlewareMap[m] and ControllerMap[m], which is a map of the middleware and controller that is stored in memory when the program is initialized

Specific definitions are as follows:

    // All structures that should be registered are recorded here
    // Controller map
    var ControllerMap map[string]interface{}
    // Middleware map
    var MiddlewareMap map[string]interface{}
    
    func init(a)  {
        ControllerMap = make(map[string]interface{})
        MiddlewareMap = make(map[string]interface{})
        Each time a route or middleware is added, this is where the route or middleware is registered
        // Register middleware
        MiddlewareMap["WebMiddleware"] =&middleware.WebMiddleware{}
    
        // Register the route
        ControllerMap["Controller"] = &controller.Controller{}
    }

Copy the code

Here we use a structure in the App/Controller and Middleware package that maps the requested path to the map when the route is resolved. Now let’s look at the routing code in the Router:

type route struct {
	path       string   / / path
	target     string   // The controller path is Controller@index
	method     string   // The access type is get, post or whatever
	alias      string   // Alias of the route
	middleware []string // Middleware name
	controller string   // Controller name
	function   string   // The name of the method to mount to the controller
}

type route_group struct {
	root_path   string   / / path
	root_target string   // The controller path is Controller@index
	alias       string   // Alias of the route
	middleware  []string // Middleware name
	routes      []route  // The included route
}

var Routes []route             // A single set of routes
var RoutesGroups []route_group // Set of routing groups
var RoutesList []route         // List of all routes
var R interface{}

func init(a) {
	// Initialize the method to load the routing file
	// Get the routing path, get all routing files according to the routing path, then read all files, assign the value to the current member variable
	routes_path := GetRoutesPath()
	dir_list, err := ioutil.ReadDir(routes_path)
	Check(err)
	// Get all json files from dir list
	for _, v := range dir_list {
		fmt.Println("Loading routing file........" + v.Name())
		// Read the contents of the file, convert it to JSON, and add it to the array
		content, err := FileGetContents(routes_path + "/" + v.Name())
		Check(err)
		err = json.Unmarshal([]byte(content), &R)
		Check(err)
		// Start parsing R, categorizing it into global variables
		parse(R)
	}
}
Copy the code

During the compilation phase, we execute init to get a list of all the routes in the routing folder, organize the routes in JSON format, and store the parsed data in the RoutesList list

Here is the parsing code


func parse(r interface{}) {
	// After r is obtained, we need to parse into actual data
	m := r.(map[string]interface{})
	//newRoute := route{}
	for k, v := range m {
		if k == "Routes" {
			// Parse a single route
			parseRoutes(v)
		}
		if k == "RoutesGroups" {
			// Parse routing groups
			parseRoutesGroups(v)
		}
	}

}

// Parse a collection of single routes in a JSON file
func parseRoutes(r interface{}) {
	m := r.([]interface{})
	for _, v := range m {
		// v is a single route
		simpleRoute := v.(map[string]interface{})
		// Define a routing structure
		newRoute := route{}
		for kk, vv := range simpleRoute {
			switch kk {
			case "Route":
				newRoute.path = vv.(string)
				break
			case "Target":
				newRoute.target = vv.(string)
				break
			case "Method":
				newRoute.method = vv.(string)
				break
			case "Alias":
				newRoute.alias = vv.(string)
				break
			case "Middleware":
				//newRoute.middleware = vv.([])
				var mdw []string
				vvm := vv.([]interface{})
				for _, vvv := range vvm {
					mdw = append(mdw, vvv.(string))
				}
				newRoute.middleware = mdw
				break
			default:
				break}}// Split target into controllers and methods
		cf := strings.Split(newRoute.target,"@")
		if len(cf)==2 {
			newRoute.controller = cf[0]
			newRoute.function = cf[1]}else{
			fmt.Println("Target format error!"+newRoute.target)
			return
		}

		// Place the new route in a single route slice, also in the route list

		Routes = append(Routes, newRoute)
		RoutesList = append(RoutesList, newRoute)
	}
}

func parseRoutesGroups(r interface{}) {
	// Parse routing groups
	m := r.([]interface{})
	for _, v := range m {
		group := v.(map[string]interface{})
		for kk, vv := range group {
			// Create a routing group structure
			var newGroup route_group
			switch kk {
			case "RootRoute":
				newGroup.root_path = vv.(string)
				break
			case "RootTarget":
				newGroup.root_target = vv.(string)
				break
			case "Middleware":
				var mdw []string
				vvm := vv.([]interface{})
				for _, vvv := range vvm {
					mdw = append(mdw, vvv.(string))
				}
				newGroup.middleware = mdw
				break
			case "Routes":
				// We can't use the above parseRoutes method because it involves concepts like root routes. We need to write another method to parse the real route
				rs := parseRootRoute(group)
				newGroup.routes = rs
				break
			default:
				break
			}
			// Add this group to the routing group
			RoutesGroups  = append(RoutesGroups,newGroup)
		}
	}
}

// Parse the root route, pass in the root route, path, destination and path, pass in the route inteface list, return a complete route set
// Pass in only one routing group and return a complete set of routes
func parseRootRoute(group map[string]interface{}) []route {
	// Get the route root path and destination root path, as well as common middleware
	var tmpRoutes []route  // The route slice to return
	var route_root_path string
	var target_root_path string
	var public_middleware []string
	for k, v := range group {
		if k == "RootRoute" {
			route_root_path = v.(string)}if k == "RootTarget" {
			target_root_path = v.(string)}if k=="Middleware" {
			vvm := v.([]interface{})
			for _, vvv := range vvm {
				public_middleware = append(public_middleware, vvv.(string))}}}// Start to obtain the route
	for k, s := range group {
		if k == "Routes" {
			m := s.([]interface{})
			for _, v := range m {
				// v is a single route
				simpleRoute := v.(map[string]interface{})
				// Define a routing structure
				newRoute := route{}
				for kk, vv := range simpleRoute {
					switch kk {
					case "Route":
						newRoute.path = route_root_path+ vv.(string)
						break
					case "Target":
						newRoute.target = target_root_path+ vv.(string)
						break
					case "Method":
						newRoute.method = vv.(string)
						break
					case "Alias":
						newRoute.alias = vv.(string)
						break
					case "Middleware":
						vvm := vv.([]interface{})
						for _, vvv := range vvm {
							newRoute.middleware = append(public_middleware,vvv.(string))// The public and the new add up to total
						}

						break
					default:
						break}}// Split target into controllers and methods
				cf := strings.Split(newRoute.target,"@")
				if len(cf)==2 {
					newRoute.controller = cf[0]
					newRoute.function = cf[1]}else{
					fmt.Println("Target format error!"+newRoute.target)
					os.Exit(2)}// Add the new route to the route list and return it to the route collection as the return value
				RoutesList = append(RoutesList, newRoute)
				tmpRoutes = append(tmpRoutes,newRoute)
			}
		}
	}
   return tmpRoutes
}
Copy the code

Parse the JSON file to get the route list, which can then be compared to the route list in the ServeHttp file above.

At this point, we’ve implemented a simple Web framework that uses GO

Currently, you can only display static pages and API responses

The next thing we want to do is:

  1. A convenient ORM
  2. Create commands to quickly add controllers and middleware
  3. Implementing database migration
  4. Implement token-based API authentication
  5. Data cache
  6. The queue
  7. hook
  8. Convenient file upload/storage function

For star, welcome PR~ ha ha ha (Silsuer /bingo)

This thing was written a long time ago, now the framework has made a lot of updates, but there is still some reference value for native development, the first time to play nuggets, as the first published content, everyone hello, please care!