同环境下部署多个webman需要修改的配置
config/server.php
listen、name、pid_file、status_file
config/database.php
database、username、password
config/redis.php
password
config/session.php
auth、prefix、session_name
config/server.php
listen、name、pid_file、status_file
config/database.php
database、username、password
config/redis.php
password
config/session.php
auth、prefix、session_name
直接上js
代码,如下:
function Push(options) {
this.doNotConnect = 0;
options = options || {};
options.heartbeat = options.heartbeat || 25000;
options.pingTimeout = options.pingTimeout || 10000;
this.config = options;
this.uid = 0;
this.channels = {};
this.connection = null;
this.pingTimeoutTimer = 0;
Push.instances.push(this);
this.createConnection();
}
Push.prototype.checkoutPing = function() {
var _this = this;
setTimeout(function () {
if (_this.connection.state === 'connected') {
_this.connection.send('{"event":"pusher:ping","data":{}}');
if (_this.pingTimeoutTimer) {
clearTimeout(_this.pingTimeoutTimer);
_this.pingTimeoutTimer = 0;
}
_this.pingTimeoutTimer = setTimeout(function () {
_this.connection.closeAndClean();
if (!_this.connection.doNotConnect) {
_this.connection.waitReconnect();
}
}, _this.config.pingTimeout);
}
}, this.config.heartbeat);
};
Push.prototype.channel = function (name) {
return this.channels.find(name);
};
Push.prototype.allChannels = function () {
return this.channels.all();
};
Push.prototype.createConnection = function () {
if (this.connection) {
throw Error('Connection already exist');
}
var _this = this;
var url = this.config.url;
function updateSubscribed () {
for (var i in _this.channels) {
_this.channels[i].subscribed = false;
}
}
this.connection = new Connection({
url: url,
app_key: this.config.app_key,
onOpen: function () {
_this.connection.state = 'connecting';
_this.checkoutPing();
},
onMessage: function(params) {
if(_this.pingTimeoutTimer) {
clearTimeout(_this.pingTimeoutTimer);
_this.pingTimeoutTimer = 0;
}
params = JSON.parse(params.data);
var event = params.event;
var channel_name = params.channel;
if (event === 'pusher:pong') {
_this.checkoutPing();
return;
}
if (event === 'pusher:error') {
throw Error(params.data.message);
}
var data = JSON.parse(params.data), channel;
if (event === 'pusher_internal:subscription_succeeded') {
channel = _this.channels[channel_name];
channel.subscribed = true;
channel.processQueue();
channel.emit('pusher:subscription_succeeded');
return;
}
if (event === 'pusher:connection_established') {
_this.connection.socket_id = data.socket_id;
_this.connection.state = 'connected';
_this.subscribeAll();
}
if (event.indexOf('pusher_internal') !== -1) {
console.log("Event '"+event+"' not implement");
return;
}
channel = _this.channels[channel_name];
if (channel) {
channel.emit(event, data);
}
},
onClose: function () {
updateSubscribed();
},
onError: function () {
updateSubscribed();
}
});
};
Push.prototype.disconnect = function () {
this.connection.doNotConnect = 1;
this.connection.close();
};
Push.prototype.subscribeAll = function () {
if (this.connection.state !== 'connected') {
return;
}
for (var channel_name in this.channels) {
//this.connection.send(JSON.stringify({event:"pusher:subscribe", data:{channel:channel_name}}));
this.channels[channel_name].processSubscribe();
}
};
Push.prototype.unsubscribe = function (channel_name) {
if (this.channels[channel_name]) {
delete this.channels[channel_name];
if (this.connection.state === 'connected') {
this.connection.send(JSON.stringify({event:"pusher:unsubscribe", data:{channel:channel_name}}));
}
}
};
Push.prototype.unsubscribeAll = function () {
var channels = Object.keys(this.channels);
if (channels.length) {
if (this.connection.state === 'connected') {
for (var channel_name in this.channels) {
this.unsubscribe(channel_name);
}
}
}
this.channels = {};
};
Push.prototype.subscribe = function (channel_name) {
if (this.channels[channel_name]) {
return this.channels[channel_name];
}
if (channel_name.indexOf('private-') === 0) {
return createPrivateChannel(channel_name, this);
}
if (channel_name.indexOf('presence-') === 0) {
return createPresenceChannel(channel_name, this);
}
return createChannel(channel_name, this);
};
Push.instances = [];
function createChannel(channel_name, push)
{
var channel = new Channel(push.connection, channel_name);
push.channels[channel_name] = channel;
channel.subscribeCb = function () {
push.connection.send(JSON.stringify({event:"pusher:subscribe", data:{channel:channel_name}}));
}
return channel;
}
function createPrivateChannel(channel_name, push)
{
var channel = new Channel(push.connection, channel_name);
push.channels[channel_name] = channel;
channel.subscribeCb = function () {
__ajax({
url: push.config.auth,
type: 'POST',
data: {channel_name: channel_name, socket_id: push.connection.socket_id},
success: function (data) {
data = JSON.parse(data);
data.channel = channel_name;
push.connection.send(JSON.stringify({event:"pusher:subscribe", data:data}));
},
error: function (e) {
throw Error(e);
}
});
};
channel.processSubscribe();
return channel;
}
function createPresenceChannel(channel_name, push)
{
return createPrivateChannel(channel_name, push);
}
/*window.addEventListener('online', function(){
var con;
for (var i in Push.instances) {
con = Push.instances[i].connection;
con.reconnectInterval = 1;
if (con.state === 'connecting') {
con.connect();
}
}
});*/
function Connection(options) {
this.dispatcher = new Dispatcher();
__extends(this, this.dispatcher);
var properies = ['on', 'off', 'emit'];
for (var i in properies) {
this[properies[i]] = this.dispatcher[properies[i]];
}
this.options = options;
this.state = 'initialized'; //initialized connecting connected disconnected
this.doNotConnect = 0;
this.reconnectInterval = 1;
this.connection = null;
this.reconnectTimer = 0;
this.connect();
}
Connection.prototype.updateNetworkState = function(state){
var old_state = this.state;
this.state = state;
if (old_state !== state) {
this.emit('state_change', { previous: old_state, current: state });
}
};
Connection.prototype.connect = function () {
this.doNotConnect = 0;
if (this.networkState == 'connecting' || this.networkState == 'established') {
console.log('networkState is ' + this.networkState + ' and do not need connect');
return;
}
if (this.reconnectTimer) {
clearTimeout(this.reconnectTimer);
this.reconnectTimer = 0;
}
this.closeAndClean();
var options = this.options;
var _this = this;
_this.updateNetworkState('connecting');
var cb = function(){
wx.onSocketOpen(function (res) {
_this.reconnectInterval = 1;
if (_this.doNotConnect) {
_this.updateNetworkState('closing');
wx.closeSocket();
return;
}
_this.updateNetworkState('established');
if (options.onOpen) {
options.onOpen(res);
}
});
if (options.onMessage) {
wx.onSocketMessage(options.onMessage);
}
wx.onSocketClose(function (res) {
_this.updateNetworkState('disconnected');
if (!_this.doNotConnect) {
_this.waitReconnect();
}
if (options.onClose) {
options.onClose(res);
}
});
wx.onSocketError(function (res) {
_this.close();
if (!_this.doNotConnect) {
_this.waitReconnect();
}
if (options.onError) {
options.onError(res);
}
});
};
wx.connectSocket({
url: options.url,
fail: function (res) {
console.log('wx.connectSocket fail');
console.log(res);
_this.updateNetworkState('disconnected');
_this.waitReconnect();
},
success: function() {
}
});
cb();
}
Connection.prototype.connect = function () {
this.doNotConnect = 0;
if (this.state === 'connected') {
console.log('networkState is "' + this.state + '" and do not need connect');
return;
}
if (this.reconnectTimer) {
clearTimeout(this.reconnectTimer);
this.reconnectTimer = 0;
}
this.closeAndClean();
var options = this.options;
this.updateNetworkState('connecting');
var _this = this;
var cb = function(){
wx.onSocketOpen(function (res) {
_this.reconnectInterval = 1;
if (_this.doNotConnect) {
_this.updateNetworkState('disconnected');
wx.closeSocket();
return;
}
if (options.onOpen) {
options.onOpen(res);
}
});
if (options.onMessage) {
wx.onSocketMessage(options.onMessage);
}
wx.onSocketClose(function (res) {
_this.updateNetworkState('disconnected');
if (!_this.doNotConnect) {
_this.waitReconnect();
}
if (options.onClose) {
options.onClose(res);
}
});
wx.onSocketError(function (res) {
_this.close();
if (!_this.doNotConnect) {
_this.waitReconnect();
}
if (options.onError) {
options.onError(res);
}
});
};
wx.connectSocket({
url: options.url+'/app/'+options.app_key,
fail: function (res) {
console.log('wx.connectSocket fail');
console.log(res);
_this.updateNetworkState('disconnected');
_this.waitReconnect();
},
success: function() {
}
});
cb();
}
Connection.prototype.closeAndClean = function () {
if (this.state === 'connected') {
wx.closeSocket();
}
this.updateNetworkState('disconnected');
};
Connection.prototype.waitReconnect = function () {
if (this.state === 'connected' || this.state === 'connecting') {
return;
}
if (!this.doNotConnect) {
this.updateNetworkState('connecting');
var _this = this;
if (this.reconnectTimer) {
clearTimeout(this.reconnectTimer);
}
this.reconnectTimer = setTimeout(function(){
_this.connect();
}, this.reconnectInterval);
if (this.reconnectInterval < 1000) {
this.reconnectInterval = 1000;
} else {
// 每次重连间隔增大一倍
this.reconnectInterval = this.reconnectInterval * 2;
}
let online = false;
wx.getNetworkType({
success (res) {
console.log(res)
if(res.networkType !== 'none'){
online = true;
}
}
})
// 有网络的状态下,重连间隔最大2秒
if (this.reconnectInterval > 2000 && online) {
_this.reconnectInterval = 2000;
}
}
}
Connection.prototype.send = function(data) {
if (this.state !== 'connected') {
console.trace('networkState is "' + this.state + '", can not send ' + data);
return;
}
wx.sendSocketMessage({
data: data
});
}
Connection.prototype.close = function(){
this.updateNetworkState('disconnected');
wx.closeSocket();
}
var __extends = (this && this.__extends) || function (d, b) {
for (var p in b) if (b.hasOwnProperty(p)) {d[p] = b[p];}
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
function Channel(connection, channel_name) {
this.subscribed = false;
this.dispatcher = new Dispatcher();
this.connection = connection;
this.channelName = channel_name;
this.subscribeCb = null;
this.queue = [];
__extends(this, this.dispatcher);
var properies = ['on', 'off', 'emit'];
for (var i in properies) {
this[properies[i]] = this.dispatcher[properies[i]];
}
}
Channel.prototype.processSubscribe = function () {
if (this.connection.state !== 'connected') {
return;
}
this.subscribeCb();
};
Channel.prototype.processQueue = function () {
if (this.connection.state !== 'connected' || !this.subscribed) {
return;
}
for (var i in this.queue) {
this.queue[i]();
}
this.queue = [];
};
Channel.prototype.trigger = function (event, data) {
if (event.indexOf('client-') !== 0) {
throw new Error("Event '" + event + "' should start with 'client-'");
}
var _this = this;
this.queue.push(function () {
_this.connection.send(JSON.stringify({ event: event, data: data, channel: _this.channelName }));
});
this.processQueue();
};
////////////////
var Collections = (function () {
var exports = {};
function extend(target) {
var sources = [];
for (var _i = 1; _i < arguments.length; _i++) {
sources[_i - 1] = arguments[_i];
}
for (var i = 0; i < sources.length; i++) {
var extensions = sources[i];
for (var property in extensions) {
if (extensions[property] && extensions[property].constructor &&
extensions[property].constructor === Object) {
target[property] = extend(target[property] || {}, extensions[property]);
}
else {
target[property] = extensions[property];
}
}
}
return target;
}
exports.extend = extend;
function stringify() {
var m = ["Push"];
for (var i = 0; i < arguments.length; i++) {
if (typeof arguments[i] === "string") {
m.push(arguments[i]);
}
else {
m.push(safeJSONStringify(arguments[i]));
}
}
return m.join(" : ");
}
exports.stringify = stringify;
function arrayIndexOf(array, item) {
var nativeIndexOf = Array.prototype.indexOf;
if (array === null) {
return -1;
}
if (nativeIndexOf && array.indexOf === nativeIndexOf) {
return array.indexOf(item);
}
for (var i = 0, l = array.length; i < l; i++) {
if (array[i] === item) {
return i;
}
}
return -1;
}
exports.arrayIndexOf = arrayIndexOf;
function objectApply(object, f) {
for (var key in object) {
if (Object.prototype.hasOwnProperty.call(object, key)) {
f(object[key], key, object);
}
}
}
exports.objectApply = objectApply;
function keys(object) {
var keys = [];
objectApply(object, function (_, key) {
keys.push(key);
});
return keys;
}
exports.keys = keys;
function values(object) {
var values = [];
objectApply(object, function (value) {
values.push(value);
});
return values;
}
exports.values = values;
function apply(array, f, context) {
for (var i = 0; i < array.length; i++) {
f.call(context || (window), array[i], i, array);
}
}
exports.apply = apply;
function map(array, f) {
var result = [];
for (var i = 0; i < array.length; i++) {
result.push(f(array[i], i, array, result));
}
return result;
}
exports.map = map;
function mapObject(object, f) {
var result = {};
objectApply(object, function (value, key) {
result[key] = f(value);
});
return result;
}
exports.mapObject = mapObject;
function filter(array, test) {
test = test || function (value) {
return !!value;
};
var result = [];
for (var i = 0; i < array.length; i++) {
if (test(array[i], i, array, result)) {
result.push(array[i]);
}
}
return result;
}
exports.filter = filter;
function filterObject(object, test) {
var result = {};
objectApply(object, function (value, key) {
if ((test && test(value, key, object, result)) || Boolean(value)) {
result[key] = value;
}
});
return result;
}
exports.filterObject = filterObject;
function flatten(object) {
var result = [];
objectApply(object, function (value, key) {
result.push([key, value]);
});
return result;
}
exports.flatten = flatten;
function any(array, test) {
for (var i = 0; i < array.length; i++) {
if (test(array[i], i, array)) {
return true;
}
}
return false;
}
exports.any = any;
function all(array, test) {
for (var i = 0; i < array.length; i++) {
if (!test(array[i], i, array)) {
return false;
}
}
return true;
}
exports.all = all;
function encodeParamsObject(data) {
return mapObject(data, function (value) {
if (typeof value === "object") {
value = safeJSONStringify(value);
}
return encodeURIComponent(base64_1["default"](value.toString()));
});
}
exports.encodeParamsObject = encodeParamsObject;
function buildQueryString(data) {
var params = filterObject(data, function (value) {
return value !== undefined;
});
return map(flatten(encodeParamsObject(params)), util_1["default"].method("join", "=")).join("&");
}
exports.buildQueryString = buildQueryString;
function decycleObject(object) {
var objects = [], paths = [];
return (function derez(value, path) {
var i, name, nu;
switch (typeof value) {
case 'object':
if (!value) {
return null;
}
for (i = 0; i < objects.length; i += 1) {
if (objects[i] === value) {
return {$ref: paths[i]};
}
}
objects.push(value);
paths.push(path);
if (Object.prototype.toString.apply(value) === '[object Array]') {
nu = [];
for (i = 0; i < value.length; i += 1) {
nu[i] = derez(value[i], path + '[' + i + ']');
}
}
else {
nu = {};
for (name in value) {
if (Object.prototype.hasOwnProperty.call(value, name)) {
nu[name] = derez(value[name], path + '[' + JSON.stringify(name) + ']');
}
}
}
return nu;
case 'number':
case 'string':
case 'boolean':
return value;
}
}(object, '$'));
}
exports.decycleObject = decycleObject;
function safeJSONStringify(source) {
try {
return JSON.stringify(source);
}
catch (e) {
return JSON.stringify(decycleObject(source));
}
}
exports.safeJSONStringify = safeJSONStringify;
return exports;
})();
var Dispatcher = (function () {
function Dispatcher(failThrough) {
this.callbacks = new CallbackRegistry();
this.global_callbacks = [];
this.failThrough = failThrough;
}
Dispatcher.prototype.on = function (eventName, callback, context) {
this.callbacks.add(eventName, callback, context);
return this;
};
Dispatcher.prototype.on_global = function (callback) {
this.global_callbacks.push(callback);
return this;
};
Dispatcher.prototype.off = function (eventName, callback, context) {
this.callbacks.remove(eventName, callback, context);
return this;
};
Dispatcher.prototype.emit = function (eventName, data) {
var i;
for (i = 0; i < this.global_callbacks.length; i++) {
this.global_callbacks[i](eventName, data);
}
var callbacks = this.callbacks.get(eventName);
if (callbacks && callbacks.length > 0) {
for (i = 0; i < callbacks.length; i++) {
callbacks[i].fn.call(callbacks[i].context || (window), data);
}
}
else if (this.failThrough) {
this.failThrough(eventName, data);
}
return this;
};
return Dispatcher;
}());
var CallbackRegistry = (function () {
function CallbackRegistry() {
this._callbacks = {};
}
CallbackRegistry.prototype.get = function (name) {
return this._callbacks[prefix(name)];
};
CallbackRegistry.prototype.add = function (name, callback, context) {
var prefixedEventName = prefix(name);
this._callbacks[prefixedEventName] = this._callbacks[prefixedEventName] || [];
this._callbacks[prefixedEventName].push({
fn: callback,
context: context
});
};
CallbackRegistry.prototype.remove = function (name, callback, context) {
if (!name && !callback && !context) {
this._callbacks = {};
return;
}
var names = name ? [prefix(name)] : Collections.keys(this._callbacks);
if (callback || context) {
this.removeCallback(names, callback, context);
}
else {
this.removeAllCallbacks(names);
}
};
CallbackRegistry.prototype.removeCallback = function (names, callback, context) {
Collections.apply(names, function (name) {
this._callbacks[name] = Collections.filter(this._callbacks[name] || [], function (oning) {
return (callback && callback !== oning.fn) ||
(context && context !== oning.context);
});
if (this._callbacks[name].length === 0) {
delete this._callbacks[name];
}
}, this);
};
CallbackRegistry.prototype.removeAllCallbacks = function (names) {
Collections.apply(names, function (name) {
delete this._callbacks[name];
}, this);
};
return CallbackRegistry;
}());
function prefix(name) {
return "_" + name;
}
function __ajax(options){
options=options||{};
options.type=(options.type||'GET').toUpperCase();
options.dataType=options.dataType||'json';
params=formatParams(options.data);
var xhr;
if(window.XMLHttpRequest){
xhr=new XMLHttpRequest();
}else{
xhr=ActiveXObject('Microsoft.XMLHTTP');
}
xhr.onreadystatechange=function(){
if(xhr.readyState === 4){
var status=xhr.status;
if(status>=200 && status<300){
options.success&&options.success(xhr.responseText,xhr.responseXML);
}else{
options.error&&options.error(status);
}
}
}
if(options.type==='GET'){
xhr.open('GET',options.url+'?'+params,true);
xhr.send(null);
}else if(options.type==='POST'){
xhr.open('POST',options.url,true);
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
xhr.send(params);
}
}
function formatParams(data){
var arr=[];
for(var name in data){
arr.push(encodeURIComponent(name)+'='+encodeURIComponent(data[name]));
}
return arr.join('&');
}
export default Push
感谢:
https://www.workerman.net/plugin/2
https://github.com/ljnchn/push/blob/main/src/push-wx.js
QueryList是一套用于内容DOM解析的PHP工具,它使用更加现代化的开发思想,语法简洁、优雅,可扩展性强。相比传统的使用晦涩的正则表达式来做DOM解析,QueryList使用了更加强大而优雅的CSS选择器来做DOM解析,大大降低了PHP做DOM解析的门槛,同时也让DOM解析代码易读易维护,让你从此告别晦涩难懂且不易维护的正则表达式😀。
15个协程
package main
import (
"fmt"
)
func inputNum(inChan chan int, n int) {
for i := 1; i <= n; i++ {
inChan <- i
}
fmt.Println("inChan 写入完成")
close(inChan)
}
func countNum(inChan chan int, primeChan chan int, exitChan chan bool) {
for {
//time.Sleep(time.Millisecond * 10)
num, ok := <-inChan
if !ok {
break
}
flag := true
for i := 2; i < num; i++ {
if num%i == 0 {
flag = false
break
}
}
if flag {
primeChan <- num
}
}
fmt.Println("有一个countNum被关闭了")
exitChan <- true
}
func main() {
inChan := make(chan int, 1000)
primeChan := make(chan int, 1000)
exitChan := make(chan bool, 15)
go inputNum(inChan, 80000)
for i := 0; i < 15; i++ {
go countNum(inChan, primeChan, exitChan)
}
go func() {
for i := 0; i < 4; i++ {
<-exitChan
}
close(primeChan)
}()
for {
v, ok := <-primeChan
if !ok {
break
}
fmt.Println(v)
}
}
输入流(读):从文件读到内存的流向。
输出流(写):从内存写入文件的方向。
HTTP 报文头也叫报文首部。
HTTP 头部字段是构成 HTTP 报文的要素之一。在客户端与服务器之间以 HTTP 协议进行通信的过程中,无论是请求还是响应都会使用头部字段,它能起到传递额外重要信息的作用。
HTTP 首部字段是由首部字段名和字段值构成的,中间用冒号:
分隔,下面是一个 HTTP 报文头的例子:
GET / HTTP/1.1
Host: hackr.jp
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:13.0) Ge
Accept: text/html,application/xhtml+xml,application/xml;q=0
Accept-Language: ja,en-us;q=0.7,en;q=0.3
Accept-Encoding: gzip, deflate
Connection: keep-alive
If-Modified-Since: Fri, 31 Aug 2007 02:02:20 GMT
If-None-Match: "45bae1-16a-46d776ac"
Cache-Control: max-age=0
看不懂这些字段代表什么意思对吧?读完本文就全弄懂了。
通用首部字段是指:请求报文和响应报文双方都会使用的字段。
Cache-Control 通过设置不同的指令可以控制缓存的行为,指令格式为:
Cache-Control: no-cache
Cache-Control: no-store
Cache-Control: max-age=<seconds>
指令参数大概可以分为三类:
缓存时间<max-age
的数据。资源的过期时间<max-stale的值
。HTTP/1.1 版本的缓存服务器遇到同时存在 Expires 首部字段的情况时,会优先处理 max-age 指令,而忽略掉 Expires 首部字段。而 HTTP/1.0 版本的缓存服务器的情况却相反,max-age 指令会被忽略
禁止缓存:Cache-Control: no-store
缓存静态资源,例如图像,CSS 文件和 JavaScript 文件:
Cache-Control:public, max-age=31536000
需要重新验证
指定 no-cache 或 max-age=0, must-revalidate 表示客户端可以缓存资源,每次使用缓存资源前都必须重新验证其有效性。这意味着每次都会发起 HTTP 请求,但当缓存内容仍有效时可以跳过 HTTP 响应体的下载。
Cache-Control: no-cache 0
Cache-Control: max-age=0, must-revalidate
Connection 头(header)决定当前的事务完成后,是否会关闭网络连接。如果该值是 keep-alive
,网络连接就是持久的,不会关闭,使得对同一个服务器的请求可以继续在该连接上完成。
从 HTTP/1.1 版本开始,所有连接默认为持久连接。
表明 HTTP 报文的创建日期和时间。日期时间格式有好几种,这里列出一种常见格式:Mon, 06 Feb 2023 01:19:14 GMT
Pragma 是一个在 HTTP/1.0 中规定的通用首部,在 HTTP/1.1 协议被 Cache-Control 代替。现在它用来向后兼容只支持 HTTP/1.0 协议的缓存服务器。
Trailer 在分块传输编码时会被用到,它用来在消息块后面添加额外的元信息。
举个例子:下面的报文中 Trailer 指定了 Expires 字段,在消息块后面出现了 Expires 字段。
HTTP/1.1 200 OK
Date: Tue, 03 Jul 2012 04:40:56 GMT
Content-Type: text/html
...
Transfer-Encoding: chunked
Trailer: Expires
...(报文主体)...
Expires: Tue, 28 Sep 2004 23:59:59 GMT
规定传输报文时采用的编码方式,可选的编码方式如下:
数据以一系列分块的形式进行发送。在每一个分块的开头需要添加当前分块的长度(十六进制),后面紧跟着 ‘\r\n’ ,之后是分块本身,后面也是’\r\n’ 。终止块是一个常规的分块,不同之处在于其长度为 0。
HTTP/1.1 200 OK
Date: Tue, 03 Jul 2012 04:40:56 GMT
Cache-Control: public, max-age=604800
Content-Type: text/javascript; charset=utf-8
Expires: Tue, 10 Jul 2012 04:40:56 GMT
X-Frame-Options: DENY
X-XSS-Protection: 1; mode=block
Content-Encoding: gzip
Transfer-Encoding: chunked
Connection: keep-alive
cf0\r\n ←16进制(10进制为3312)
3312字节数据\r\n
392\r\n ←16进制(10进制为914)
914字节数据\r\n
0\r\n
\r\n
采用 Lempel-Ziv-Welch (LZW) 压缩算法。这种内容编码方式已经被大部分浏览器弃用。
表示采用 Lempel-Ziv coding (LZ77) 压缩算法,以及 32 位 CRC 校验的编码方式。这个编码方式最初由 UNIX 平台上的 gzip 程序采用。处于兼容性的考虑,HTTP/1.1 标准提议支持这种编码方式的服务器应该识别作为别名的 x-gzip 指令。
用于表明自身未经过压缩和修改。
Transfer-Encoding 是一个逐跳传输消息首部,即仅应用于两个节点之间的消息传递。如果想要将压缩后的数据应用于整个连接,那么应该使用端到端传输消息首部 Content-Encoding 。
用于检测是否有可用的、更高版本的 HTTP 协议。
如果使用了 Upgrade 字段,Connection 字段的值会被指定为 Upgrade,比如:
Upgrade:TLS/1.0
Connection:Upgrade
Via 用于追踪客户端和服务器之间报文的传输路径,也可用于防止循环请求。格式:
Via: [ <protocol-name> "/" ] <protocol-version> <host> [ ":" <port> ]
or
Via: [ <protocol-name> "/" ] <protocol-version> <pseudonym>
警告报文出现了问题,格式:Warning: <warn-code> <warn-agent> <warn-text> [<warn-date>]
用来告知服务器可以处理的内容类型,例如:
Accept: <MIME_type>/<MIME_subtype>
Accept: <MIME_type>/*
Accept: */*
Accept: text/html, application/xhtml+xml, application/xml;q=0.9, */*;q=0.8
*
代表任意类型,比如 image/* 可以用来指代 image/png、image/svg、image/gif 以及任何其他的图片类型。用于声明客户端可以处理的字符集类型,例如:
Accept-Charset: <charset>
Accept-Charset: utf-8, iso-8859-1;q=0.5, *;q=0.1
用于声明客户端能够处理的编码方式,例如:
Accept-Encoding: deflate, gzip;q=1.0, *;q=0.5
用于声明客户端可以理解的语言,比如:
Accept-Language: <language>
Accept-Language: fr-CH, fr;q=0.9, en;q=0.8, de;q=0.7, *;q=0.5
用于提供给服务器验证身份的凭据,允许其访问受保护的资源,比如:
Authorization: <auth-scheme> <authorization-parameters>
Authorization: Basic YWxhZGRpbjpvcGVuc2VzYW1l
Basic 身份验证:首先将用户名和密码使用一个冒号拼接(username:password),然后将生成的字符串进行 base64 编码。
除了 Basic 编码方式,还有 Digest、Negotiate 等方式。
客户端发送带有 Expect 消息头的请求,等服务器回复后再发送消息体,例如:
Expect: 100-continue
服务器检查请求消息头,可能会返回一个状态码为 100 (Continue) 的回复来告知客户端继续发送消息体,也可能会返回一个状态码为 417 (Expectation Failed) 的回复来告知对方要求不能得到满足。
附带一个电子邮箱地址,例如:
From: webmaster@example.org
指明本次请求的目标服务器主机名和端口号。
形如 If-xxx 这种样式的请求首部字段,都可称为条件请求。服务器接收到附带条件的请求后,只有判断指定条件为真时,才会执行请求。 例如:
If-Match: "bfc13a64729c4290ef5b2c2730249c88ca92d82d"
服务器会比对 If-Match 的字段值和资源的 ETag 值,仅当两者一致时,才会执行请求。反之,则返回状态码 412 Precondition Failed 的响应。
If-Modified-Since 用于确认客户端拥有的本地资源的有效性,例如:
If-Modified-Since: Wed, 21 Oct 2015 07:28:00 GMT
服务器在收到带有 If-Modified-Since 字段的请求后,会将该字段值和资源更新时间做比较,若资源没有更新,则返回 304 状态码(Not Modified)。
与 If-Match 的作用相反。
If-Range HTTP 请求头字段用来使得 Range 头字段在一定条件下起作用:当字段值中的条件得到满足时,Range 头字段才会起作用,例如:
If-Range: Wed, 21 Oct 2015 07:28:00 GMT
字段值中既可以用 Last-Modified 时间值用作验证,也可以用 ETag 标记作为验证,但不能将两者同时使用。
与 If-Modified-Since 作用相反。
用于限制 TRACE 方法可经过的服务器(通常指代理服务器)数目。
发送包含首部字段 Max- Forwards 的请求时,该字段以十进制整数形式指定可经过的服务器最大数目。每经过一个服务器,Max-Forwards 的值减 1。当服务器接收到 Max-Forwards 值为 0 的请求时,则不再进行转发,而是直接返回响应。
用于客户端和代理服务器之间的认证,例如:
Proxy-Authorization: Basic dGlwOjkpNLAGfFY5
Range 字段用于分批请求资源,下面的示例表示请求获取从第 5001 字节至第 10000 字节的资源。
Range: bytes=5001-10000
接收到附带 Range 首部字段请求的服务器,会在处理请求之后返回状态码为 206 Partial Content 的响应。无法处理该范围请求时,则会返回状态码 200 OK 的响应及全部资源。
Referer 请求头包含了当前请求页面的来源页面的地址,即表示当前页面是通过此来源页面里的链接进入的。服务端一般使用 Referer 请求头识别访问来源,可能会以此进行统计分析、日志记录以及缓存优化等。
Referer 请求头可能暴露用户的浏览历史,涉及到用户的隐私问题,所以一般用于 HTTPS 协议。
表明客户端能够处理的传输编码方式及相对优先级,例如:
TE: gzip, deflate;q=0.5
User-Agent 首部包含了一个特征字符串,用来让网络协议的对端来识别发起请求的用户代理软件的应用类型、操作系统、软件开发商以及版本号。比如 Google 的 UA 字符串:
Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.103 Safari/537.36
表明服务器是否支持范围请求,可以处理范围请求时指定字段值为 bytes,反之则为 none,指令格式:
Accept-Ranges: bytes
表明资源在代理服务器缓存了多久,以秒为单位,指令格式:
Age: 24
ETag 是资源的唯一标识符,使用 ETag 能快速确定资源是否发生过变化。(可以理解为资源的 “指纹”)
ETag 有强弱之分,资源即使发生了一丁点的变化也会改变强ETag值
,对应的,只有资源发生较大变化才会改变弱ETag值
,此时会在字段开始处附加 W/
:
ETag: W/"0815"
用于将页面重定向至新的地址,一般与 3xx 状态码配合使用,例如:
Location: <url>
Proxy-Authenticate 会把由代理服务器所要求的认证信息发送给客户端,例如:
Proxy-Authenticate: Basic realm="Access to the internal site"
用于告知客户端应该在多久之后再次发送请求。主要配合状态码 503 Service Unavailable,或 3xx Redirect 响应一起使用,例如:
Retry-After: 120(以秒为单位)
表明服务器的软件和版本信息,指令格式:
Server: Apache/2.2.6 (Unix) PHP/5.2.5
决定缓存能否使用,关于缓存,建议用 cache-control 而非 vary。
WWW-Authenticate 定义了应该使用何种验证方式去获取对资源的连接,例如服务器利用该字段规定了 Basic 认证 :
WWW-Authenticate: Basic realm="Access to the staging site"
WWW-Authenticate header 通常会和一个 401 Unauthorized 的响应一同被发送。
用于枚举资源所支持的 HTTP 方法的集合,指令格式:
Allow: GET, HEAD
如果 Allow 字段的值为空,说明资源不接受使用任何 HTTP 方法的请求,这可能是因为服务器需要临时禁止对资源的任何访问。
消息文本的编码类型,指令格式:
Content-Encoding: deflate, gzip
指令参数:
Content-Language 用来说明服务器希望访问者采用的语言或语言组合,例如报文的 Content-Language 字段值为 de
,那么说明这份文件是为说德语的人提供的,但是这并不意味着文本是德文,它也可能是英文等其他语言:
Content-Language: de
用于指明发送给客户端的消息主体
的大小,用十进制数字表示,例如:
Content-Length: 15900
对应资源的 URL。
用来表示一个数据片段在整个文件中的位置,例如:
Content-Range: bytes 200-1000/67589
用于告知客户端响应报文内容的内容类型,例如:
Content-Type: text/html; charset=utf-8
Content-Type: multipart/form-data; boundary=something
指令参数:
资源的 MIME 类型,比如 text/html、multipart/form-data。
字符编码标准。
用于告知客户端缓存的失效日期,指令格式:
Expires: Wed, 21 Oct 2015 07:28:00 GMT
如果在 Cache-Control 响应头设置了 max-age 或者 s-max-age 指令,那么 Expires 头会被忽略。
资源最后一次修改的时间,指令格式:
Last-Modified: Wed, 21 Oct 2015 07:28:00 GMT
服务器利用 Set-Cookie 字段来告知客户端 cookie,例如设置一个永久 cookie:
Set-Cookie: id=a3fWa; Expires=Wed, 21 Oct 2015 07:28:00 GMT; Secure; HttpOnly
会话期 cookie
Set-Cookie: sessionId=38afes7a8
会话期 cookie 将会在客户端关闭时被移除。会话期 cookie 不设置 Expires 或 Max-Age 属性。
持久化 cookie
Set-Cookie: id=a3fWa; Expires=Wed, 21 Oct 2015 07:28:00 GMT
Set-Cookie: id=a3fWa; Max-Age=2592000
持久化 cookie 不会在客户端关闭时失效,而是在特定的日期(Expires)或者经过一段特定的时间之后(Max-Age)才会失效。
客户端在向服务器发 HTTP 请求时附带 Cookie 以获得服务器的认证。(Cookie 值来源于上文的 Set-Cookie 字段值)
一个 HTTP 请求要跨过多个代理服务器,经过多次代理服务器的转发才能到达目标服务器。
代理服务器做转发时,对原 HTTP 报文头部字段有两种处理方式:
哪些字段是逐条策略?哪些又是端到端策略?
除了下面这些字段为逐跳策略外,其他字段都属于端到端策略:
Location 指定的是一个重定向请求的目的地址(或者新创建的文件的 URL)。
Content-Location 指向的是可供访问的资源的直接地址。
Proxy-Authenticate 规定了客户端与代理服务器的认证方式,而 WWW-Authenticate 规定了客户端与服务器的认证方式。
Etag 由服务器端生成,客户端通过 If-Match 或者说 If-None-Match 这个条件判断请求来验证资源是否修改。常见的是使用 If-None-Match,比如请求一个文件的流程可能如下:
第一次请求:
第二次请求:
当首部字段 Cache-Control 有指定 max-age 指令时,会优先处理 max-age 指令,而忽略 Expires 字段。
参考:
简单总结一下 Go 语言中 make 和 new 关键字的实现原理:make
关键字的作用是创建切片、哈希表和 Channel 等内置的数据结构。new
的作用是为类型申请一片内存空间,并返回指向这片内存的指针。
type UserBasic struct {
gorm.Model
Name string `gorm:"size:150;default:'';not null;comment:姓名"`
Password string `gorm:"size:255;default:'';not null;comment:密码"`
Phone string `gorm:"size:11;default:'';not null;comment:手机号码"`
Email string `gorm:"size:150;default:'';not null;comment:邮箱"`
Client string `gorm:"size:100;default:'';not null;comment:客户端"`
Identity string `gorm:"size:255;default:'';not null;comment:暂无"`
ClientIp string `gorm:"size:20;default:'';not null;comment:客户端IP"`
ClientPort string `gorm:"size:10;default:'';not null;comment:客户端端口"`
LoginTime time.Time `gorm:"comment:登陆时间"`
HeartbeatTime time.Time `gorm:"comment:心跳时间"`
LogoutTime time.Time `gorm:"comment:退出时间"`
IsLogout bool `gorm:"default:true;not null;comment:是否登陆"`
DeviceInfo string `gorm:"size:255;default:'';not null;comment:设备信息"`
}
package service
import (
"encoding/json"
"fmt"
"ginStudy/tool"
"github.com/aliyun/alibaba-cloud-sdk-go/sdk"
"github.com/aliyun/alibaba-cloud-sdk-go/sdk/auth/credentials"
"github.com/aliyun/alibaba-cloud-sdk-go/services/dysmsapi"
"math/rand"
"time"
)
type MemberService struct {
}
func (ms *MemberService) SendSms(phone string) bool {
//1.产生一个验证码
code := fmt.Sprintf("%04v", rand.New(rand.NewSource(time.Now().UnixNano())).Int31n(1000))
//2.调用全局配置参数
config := tool.GetConfig().Sms
configSdk := sdk.NewConfig()
credential := credentials.NewAccessKeyCredential(config.AppKey, config.AppSecret)
client, err := dysmsapi.NewClientWithOptions(config.RegionId, configSdk, credential)
if err != nil {
panic(err)
}
//3.构造请求参数
request := dysmsapi.CreateSendSmsRequest()
request.Scheme = "https"
request.SignName = config.SignName
request.TemplateCode = config.TemplateCode
request.PhoneNumbers = phone
par, err := json.Marshal(map[string]interface{}{
"code": code,
})
request.TemplateParam = string(par)
//接收响应
response, err := client.SendSms(request)
if err != nil {
fmt.Print(err.Error())
return false
}
fmt.Printf("response is %#v\n", response)
//5.判断返回
if response.Code == "OK" {
return true
}
return false
}
chan
的值或者状态会有很多种情况,一些操作可能会出现panic
异常场景,如下表:
接收/发送 | nil channel | 有值 channel | 没值 channel | 满 channel |
---|---|---|---|---|
<- ch (发送数据) | 阻塞 | 发送成功 | 发送成功 | 阻塞 |
ch <- (接收数据) | 阻塞 | 接收成功 | 阻塞 | 接收成功 |
close (ch) 关闭 channel | panic | 关闭成功 | 关闭成功 | 关闭成功 |