“Deno entry to give up” type of article has a lot of big guy to write, but also a more detailed description, so I do not rub this type of article.
I’m going to talk about how to build a Router wheel with Deno from the point of view of building a wheel. (In fact, I haven’t finished writing this set of Web Server, can only take the Router part which is more completed 😅)
Origin of routing
The earliest appearance of routing is originated from the server side. In the early Web system architecture, routing is used to describe the mapping between URL and processing function or resource. For example,.asp.aspx.jsp. SHTML, etc., is through SSR (Serve Side Render) to achieve rendering, and finally directly return to the display page. (It can be understood that some of the front-end concepts are leftover from the early years.)
In the early days of routing, the request URI and the framework’s custom file directory + file class + class function were used to map the route. Suppose the route is/XXX /user/get_user_info
So the file directory structure is:
Application ├── SRC │ ├─ user │ ├─ userController.csCopy the code
So there will be a function like this in userController.cs:
// asp.net MVC example code
// XXX is the project name -> / XXX
namespace xxx.Controllers
{
// Controller name -> / XXX /user
public class UserController : Controller // C -> Controller
{
// function -> / XXX /user/get_user_info
public GetUserInfo Index() {
return View(); // The View points to the View file to be returned. When called, the server will render it}}}Copy the code
It can be seen from this that the early Web application framework is through the project directory structure, file name, class name, function name and other ways of the Path of the unified constraints, and then realize the URI matching. Making a lot of system calls per URI request here is inefficient nowadays, but in the early days it was very advanced because it was probably the fastest way to implement an interface while avoiding defining routes.
Principles of routing
Now that you have learned about the past life of routing, you can learn about the present life of routing: modern routing
Currently, there are three route resolution modes:
- Folder path
- Regular match
- Dictionary tree/prefix tree
Folder path
The folder structure determines the route complexity and clarity, exposes the real project directory, and has limited processing capacity for dynamic routing parameters.
E.g. suppose the route is/XXX /user/info
Then the file structure might look like this:
├─ user │ ├─ info │ ├─ index.htmlCopy the code
In this case, it is very similar to the early routing.
Regular match
URI Path matching based on regular expressions can realize dynamic parameters. It is difficult to maintain complex paths or long string rules, so you need to be familiar with regular expression writing.
E.g. assume that the route is/XXX /user/123456789/info
Then the implementation in the code might be:
app.route('/xxx/user/{\d{9}}/info', getUserInfo);
Copy the code
Any REQUEST/XXX /user/ URI that matches a 9-digit /info will be processed by the getUserInfo function.
Regex matched routing URIs are only suitable for small and well-planned projects. If the project is large and the parameters are multipath deep, the regex is more difficult to maintain and write.
Dictionary tree/prefix tree
Dictionary/prefix trees are implemented by assembling the defined Path into a tree of nodes shred by “/”, then matching the URI through the tree node to find the node information, and then executing the function.
E.g. suppose the route is:
/xxx/user/123456789/info
/xxx/article/123456
A route is defined as:
/xxx/user/{\d{9}}/info
/xxx/user/repwd
/xxx/article/{\d{6}}
The final tree structure is as follows:
The prefix tree has better performance than the base dictionary tree at matching long strings, so it is more suitable for URI matching scenarios.
The dictionary tree matches all characters, that is to say, the current node needs to be traversed by breadth, while the prefix tree maintains the prefix list of the child nodes in the parent node, and performs index lookup before matching.
Routing implementation
OK, so with that much pushing you can start implementing a simple route, and in this case I’m using a dictionary tree. After continuous improvement and optimization, I will update the way of prefix tree, because from the difference of dictionary tree to prefix tree, the code change is not big.
To implement routes, first of all, you need to define routes, and each route needs a handler. Each route defined requires a URL attribute and a handle attribute. Then, according to the principle of dictionary tree routing, the Path is divided by “/”, and each Path is a node.
Deno natively supports TypeScript, so I’m writing in TypeScript.
Code implementation is as follows:
// Define the route list type
type RouteList = { url: string.handle: () = > void} [];// Divide the Route Path by "/"
function splitRoutePath (routeList: RouteList = []) {
routeList.forEach((item) = > {
const splitPath = item.url.split('/');
const splitPathLen = splitPath.length - 1;
// If it does not start with/or end with /, empty string entries need to be processed
if(! splitPath[splitPathLen]) { splitPath.splice(splitPathLen,1); }
if(! splitPath[0]) { splitPath.splice(0.1); }
// Call GenerateNodeTree to generate the node tree
GenerateNodeTree(splitPath, item.handle);
});
}
// Let's assume that the url is defined, and let's not consider using the regular match Path here
// The route list defined requires a URL attribute and a handle attribute
const rrl = [
{ url: '/user/repwd'.handle: () = > { console.log('/user/repwd'); }}, {url: '/user/:id/info'.handle: () = > { console.log('/user/:id/info'); }}, {url: '/article/:id'.handle: () = > { console.log('/article/:id'); }},];// Call splitRoutePath to split the route path
splitRoutePath(rrl);
Copy the code
SplitRoutePath will give you a list of three Route paths:
user -> repwd => handle
user -> :id -> info => handle
article -> :id => handle
Convert to a dictionary tree structure
Then we need to convert these lists into a tree structure, a dictionary tree of routes. So you need to implement a GenerateNodeTree function to handle it.
// Node type definition
type NodeType = { [key in string]: RouteNode };
// Route node interface definition
export interface RouteNode {
key: string;
name: string;
handler: handlerType | undefined;
children: NodeType | undefined;
}
// Route parameter extraction - regular expression
const regExpParamKey: RegExp = /^:(\w+)\?? $/;
// Map node tree - dictionary tree
export const mapNodeTree: NodeType = {};
// Generate the number of nodes
function GenerateNodeTree (pathArr: string[] = [], handle: handlerType) {
const len = pathArr.length; // The length of the path array
let tempNode: NodeType = mapNodeTree; //
pathArr.forEach((item: string, index: number) = > {
if(! tempNode[item]) {// Matches whether it is a route parameter
const regResult = regExpParamKey.exec(item);
// If it is a route parameter, the parameter name is used as the node key; otherwise, the Path is used as the node key
consttempKey = regResult ! = =null ? regResult[1] : item;
// The information needed to assemble the node
tempNode[item] = {
key: tempKey,
name: item,
handler: index === len - 1 ? handle : undefined.children: index ! == len -1 ? {} : undefined}}if(index ! == len -1) {
// Process the child node information
if (!tempNode[item].children) { tempNode[item].children = {}; }
tempNode = tempNode[item].children || {};
}
});
}
Copy the code
After GenerateNodeTree is called, the mapNodeTree object is the final dictionary tree.
The structure of the print tree is as follows:
// Omitted some other field information
mapNodeTree = {
user: {
key: "user".children: {
repwd: {
key: "repwd".handler: [Function: handle]
},
:id: {
key: "id".children: {
info: {
key: "info".handler: [Function: handle]
}
}
}
}
},
article: {
key: "article".children: {
:id: {
key: "id".handler: [Function: handle]
}
}
}
}
Copy the code
From there, you are done generating the dictionary tree of the route by defining the route path.
Heigh-ho ~ you only have to generate the routing tree to pinch, then I request in how to deal with these routes and call the corresponding function to pinch?
Traverse the matching tree nodes
Don’t worry, the road is step by step, fat is not a bite to eat. Here, of course, we need to implement a function that can traverse the dictionary tree matches.
// Traversal - Matches the dictionary tree node
export const IterateNodeTree = (pathArr: string[] = []) = > {
const pathParams = new Map(a);const len = pathArr.length;
let tempIndex = 0;
let tempNode: NodeType = mapNodeTree;
// Match node recursive check
const recursiveCheck = (nodItem: NodeType, nodeKey: string, isEnd: boolean) = > {
const currentNode = nodItem[nodeKey];
const nodeChild = currentNode.children;
const nodeHandler = currentNode.handler;
if (isEnd) { // If it is the end of the path and there is an execute function, execute, otherwise exit
// currentNode.key
nodeHandler && pathParams.size > 0 ? nodeHandler(pathParams) : undefined;
} else {
if (nodeChild) {
tempIndex++;
tempNode = nodeChild;
return true; }}return false;
}
// Traverse the matching dictionary tree
while (tempIndex < len) {
// Get all keynames of the current node entry
const tempNodeKeys = Object.keys(tempNode);
// Whether the path ends
const isEnd = tempIndex === len - 1;
const isPathParam = tempNodeKeys.find(item= > { return regExpParamKey.exec(item); });
if(tempNodeKeys.indexOf(pathArr[tempIndex]) ! = = -1) {if(! recursiveCheck(tempNode, pathArr[tempIndex], isEnd)) {return; }}else if (isPathParam) {
pathParams.set(tempNode[isPathParam].key, pathArr[tempIndex]);
if(! recursiveCheck(tempNode, isPathParam, isEnd)) {return; }}else { return; }}}Copy the code
With IterateNodeTree implemented, we can simply test the matching results of the dictionary tree.
// Assume the requested URL
const rl: string[] = [
'/user/repwd'.'/user/123/info'.'/user/456/other/'.'/article/123'.'/article/type'.'/article/type/info',];Copy the code
Let’s run the results:
deno run troute.ts
Copy the code
As you can see from the run results, only URI matches will trigger function execution, while missing URI matches will not trigger function execution.
Hiya ~ you are all local string pinched, I said is a request! Request!!)
Take it one step at a time. If the local string test doesn’t pass, there’s no point in processing the request.
Deno Web Server
Now let’s implement a simple HTTP service
import { serve } from "https://deno.land/std/http/server.ts";
console.log("http://localhost:8000/");
const webServe = serve({ port: 8000 });
for await (const request of webServe) {
const splitPath = req.url.split('/');
const splitPathLen = splitPath.length - 1;
// If the string does not start or end with a slash (/), you need to clear the empty string item
if(! splitPath[splitPathLen]) { splitPath.splice(splitPathLen,1); }
if(! splitPath[0]) { splitPath.splice(0.1); }
console.log('Split URL hierarchy:', splitPath);
IterateNodeTree(splitPath);
}
Copy the code
Just run, and this simple HTTP is already started. Of course, some of you may not be familiar with it or have just contacted with it, so I will repeat:
By default, Deno is secure. So the Deno module has no access to files, networks, or environments unless you authorize it. You must authorize the Deno process in the command line to access security-sensitive functions.
Since we need access to the network, don’t forget to add the –allow-net flag to the command, and be careful not to write the wrong location after run.
deno run --allow-net troute.ts
Copy the code
(This is a false demonstration)
(Correct password)
At this point we can see that the HTTP service is started and the contents of console.log are printed on the command line.
Initiates a request through fetch
Let’s make a few more requests:
fetch('http://localhost:8000/user/123/info', { method: 'GET' });
fetch('http://localhost:8000/user/repwd', { method: 'POST' });
fetch('http://localhost:8000/user/456/other/', { method: 'POST' });
fetch('http://localhost:8000/article/123', { method: 'GET' });
fetch('http://localhost:8000/article/type', { method: 'POST' });
fetch('http://localhost:8000/article/type/info', { method: 'POST' });
// Remember the route just defined
// The route list defined requires a URL attribute and a handle attribute
const rrl = [
{ url: '/user/repwd'.handle: () = > { console.log('/user/repwd'); }}, {url: '/user/:id/info'.handle: (data: any) = > { console.log('/user/:id/info', data); }}, {url: '/article/:id'.handle: () = > { console.log('/article/:id'); }},];Copy the code
After that we start a command line and run the following command:
deno run --allow-net aa.ts
Copy the code
Of course, don’t forget to add the allow-net permission identifier for access to the network
(I name here more casual diagram convenient, everyone in writing the project time thousands! Wan! No! Want to! Learn from me, this is a bad habit)
From there the matching logic of a routing core is complete, but the implementation of this Router is not capable of achieving the business capability, if anything.
Because here I just describe the origin and concept of routing, as well as the current implementation, but interested in the above code can be modified to achieve simple Web Server function.
Of course, you can try to build it:
deno bundle [OPTIONS] <source_file> [out_file]
deno bundle trouter.ts Router.js
Copy the code
Why did I study Deno
I first mentioned Deno through Ryan Dahl at the JS Conference in Berlin in June 2018. I wasn’t a big fan of Node.js myself at the time but I was looking forward to Deno. Because I want to know what happens when a person thinks he or she failed and tries the same product again. Does he or she learn from it, or does he or she fall into the same hole?
But over time, I’ve seen Deno take a lot of good implementations, and what he blames for failing is not repeated and has more capabilities.
My first experience with Deno was the same as my first experience with Golang. It was a little weird, but very comfortable to write.
If I had to find a reason, it would be this: It is strong and light, just like Golang.
🏆 technology project phase I | talk about Deno some thing…
Copyright notice: the copyright of this article belongs to the author Lin Xiaoshuai, shall not be reproduced without authorization and second modification. Reprint or cooperation please leave a message and contact information below.