Web Audio API系列之一Web Audio初探

本文介绍Web Audio API的简介以及使用范围等基础内容。

一、特性
Audio API主要有以下特性:

  • 为简单或复杂的混音/特效提供模块化路由架构,包括多路传送以及子混合(submixes)
  • 高动态范围,内部处理使用32位单精度浮点数
  • 对于需要极高节奏精度的音乐应用,例如鼓机和音序器,精确取样低延迟播放。同时也为动态创建音效提供了可能性
  • 为envelopes、淡入/淡出、粒状效应、滤波器扫描、LFOs等提供自动化的音频参数
  • 灵活的处理音频流中的声道,允许对其进行分离与合并
  • 可以从audio或video媒体元素中处理音频源
  • 可以处理从getUserMedia函数获取的媒体流
  • 与WebRTC集成:使用MediaStreamAudioSourceNode和webrtc处理从远端接收的音频;通过MediaStreamAudioDestinationNode和webrtc发送生成或处理过的音频流到远端
  • 音频流的合成与处理可以直接使用JavaScript
  • 支持各种3D游戏和身临其境的空间化音频:平移模型(等功率,HRTF,直通);距离衰减;声锥;阻塞/闭塞;多普勒频移;源/监听器
  • 卷积引擎适用于各种线性效果,尤其是非常高品质的室内效果,例如:小/大房间、大教堂、音乐厅、洞穴、隧道、门厅、圆形剧场、一个遥远的声音通过门口、极端过滤器、倒退效果、极端梳状滤波效果(Extreme comb filter effects)
  • 动态压缩整体控制以及甜味的混合(sweetening of the mix)
  • 高效的实时时域和频率分析/音乐可视化支持
  • 用于低通,高通和其他常见滤波器的高效双二阶滤波器。
  • 波形整形效果,用于失真和其他非线性效果
  • 振荡器

二、模块化路由
模块化路由允许不同AudioNode对象之间的任意连接。每个节点可以具有输入和(或)输出。源节点没有输入有一个输出。目的节点有一个输入,没有输出,最常见的例子是音频硬件的最终目的地AudioDestinationNode。可以在源节点和目的地节点之间放置其他节点,例如过滤器。当两个对象连接在一起时,开发人员不必担心低级流格式细节。例如,如果单声道音频流连接到立体声输入,它应该恰当地混合到左和右声道。
在最简单的情况下,单个源可以直接路由到输出。所有路由在单个AudioDestinationNode的AudioContext中:
Source→Destination
下面是一个简单的例子:

var context = new AudioContext();

function playSound() {
    var source = context.createBufferSource();
    source.buffer = dogBarkingBuffer;
    source.connect(context.destination);
    source.start(0);
}

模块化路由还允许将AudioNode的输出路由到控制不同AudioNode行为的AudioParam参数。在这种情况下,节点的输出可以用作调制信号而不是输入信号。

JavaScript通过execCommand向剪贴板写入数据

本文介绍JavaScript使用document.execCommand等函数向系统剪贴板中写入数据的方法。

一、document.execCommand
document.execCommand(‘copy’)可以复制当前页面上选中的内容到剪贴板中
兼容性:
document.execCommand本身的兼容性非常好,主流的浏览器(包括IE6等低版本浏览器)都支持。
document.execCommand(‘copy’)并不是所有浏览器都支持,可以根据document.queryCommandSupported(‘copy’)来查询是否支持copy命令。
根据MDN官网上说明,copy命令的兼容性为:
Chrome:43
Firefox:41
IE:9
Opera:29
Safari:10

二、input的select方法
input的select方法可以选中input的全部内容
注意,input的type不能是hidden,并且input不能display:none或visibility:hidden,否则无法选中

三、示例代码
document.execCommand(‘copy’)会复制当前页面被选中的文本,如果想要将页面被选中的文本复制到剪贴板中,直接调用document.execCommand(‘copy’)即可

js:
const inputHidden = document.querySelector('#inputHidden');
inputHidden.value = '复制到剪贴板的内容';
inputHidden.select();
document.execCommand('copy');
html:
<input type="text" id="inputHidden" />
css:
#inputHidden{ left: -9999px; top: -9999px; position: fixed;}

四、在线Demo
https://demo.lyz810.com/clipBoard/

nginx代理入门

本文通过几个的实例,介绍nginx配置代理的几种方式,以及全局代理的配置。

注:本文中所有实例均可以直接使用,不需要改变任何配置(包括域名,域名解析到127.0.0.1,注意:由于https证书自动续期的需要,仅国内线路的*.local.lyz810.com指向127.0.0.1,国外线路已变更为服务器的IP地址
原始站点(被代理站点)
测试后面所有实例时,请确保该源站配置存在

server {
    listen 80;
    server_name origin.local.lyz810.com;
    default_type text/plain;
    location / {
        return 200 "This is origin.local.lyz810.com$request_uri";
    }

    location =/setcookie/ {
        add_header Set-Cookie "testcookie=123; domain=origin.local.lyz810.com; path=/";
        return 200 "Please find result in response header";
    }

    location =/showcookie/ {
        return 200 $http_cookie;
    }
}

当访问origin.local.lyz810.com的任意页面,都可以看到This is origin.local.lyz810.com后面跟着url

实例一:
通过其他域名进行代理(如目前线上访问google.lyz810.com即可实现代理google.com.hk)
代理配置:

server {
    listen 80;
    server_name proxy.local.lyz810.com;

    location / {
        proxy_pass http://origin.local.lyz810.com/;
    }
}

访问proxy.local.lyz810.com的任意页面,会对应访问origin.local.lyz810.com的页面
如访问proxy.local.lyz810.com/test,返回This is origin.local.lyz810.com/test

实例二:
代理服务器上的url与源站url存在差异,访问proxy.local.lyz810/test/下的所有页面,要求返回origin.local.lyz810.com/hello/对应页面(test改为源站的hello)
代理配置:

server {
    listen 80;
    server_name proxy.local.lyz810.com;

    location /test/ {
        proxy_pass http://origin.local.lyz810.com/hello/;
    }
}

请注意hello后面的/,这个是必须有的,nginx在处理url时,会将location匹配的路径从访问url中去掉后拼接到proxy_pass指令后面
例如访问proxy.local.lyz810.com/test/abc.html,匹配location为/test/,把匹配的部分从url中去掉,还剩abc.html,直接拼接到proxy_pass后面,即http://origin.local.lyz810.com/hello/abc.html
一种典型的错误写法:

    ...
    location /test {
        proxy_pass http://origin.local.lyz810.com/hello/;
    }
    ...

上面的写法语法上没有问题,但是跟需求不匹配。也许你会发现,这个错误的写法在访问proxy.local.lyz810.com/test/abc.html时仍然返回This is origin.local.lyz810.com/hello//abc.html(nginx会对url进行标准化处理,两个/跟一个/访问的是同一个资源)
这是由于hello以/结尾,nginx会将/test后面的内容直接接到proxy_pass最后,造成2个/
但如果访问proxy.local.lyz810.com/test123/abc.html则会返回http://origin.local.lyz810.com/hello/123/abc.html,这个与预期不符。

实例三:
源站要设cookie,由于域名不同,跨域无法设置cookie,通过nginx可以解决这个问题。如访问origin.local.lyz810.com/setcookie/后会在origin.local.lyz810.com域下根目录种一个cookie,现在希望通过代理,访问源站,并在proxy.local.lyz810.com的/test上种上相同的cookie,配置如下:

server {
    listen 80;
    server_name proxy.local.lyz810.com;

    location / {
        proxy_pass http://origin.local.lyz810.com/;
        proxy_cookie_domain origin.local.lyz810.com $host;
        proxy_cookie_path / /test;
    }
}

访问proxy.local.lyz810.com/setcookie/,查看响应头(Set-Cookie:testcookie=123; domain=proxy.local.lyz810.com; path=/test)。
这种使用场景是使用一个域名代理另一个域名的页面,并可以代理认证信息。
我们知道,浏览器是按照域名携带cookie的,所以访问proxy.local.lyz810.com的时候只能带着proxy.local.lyz810.com的cookie访问源站,显然用户不可能自己在proxy.local.lyz810.com上添加cookie,所以我们需要把访问源站登录时,设置的cookie转换为代理域下的cookie,这样访问代理域就和访问源站域完全一样了,相当于镜像站。
通常做完整镜像站时,只需要将cookie的domain修改为代理域即可,path保持一致不需要配置。

实例四:
有时候,页面上的js会对域名做判断,上面所有的方法只能骗过源站的服务器,而不能骗过浏览器的location.hostname,这种情况下,nginx是无法完美解决,如果是开发调试,可以通过配置host来实现。
例如,我的项目地址是demo.lyz810.com,我负责前端开发,需要调用后端接口(后端接口都在/fetch/下)demo.lyz810.com/fetch/api.php?action=getJson(完全可以通过实例三的方法设置另一个域,然后做cookie共享,但我不喜欢开发和线上访问的不一样),那么可以通过下面的方式来实现:
1.设置host 127.0.0.1 demo.lyz810.com
2.nginx配置如下:

server {
    listen 80;
    server_name demo.lyz810.com;
    default_type text/html;
    location /fetch/ {
        proxy_pass http://133.130.97.238/fetch/;
        proxy_set_header Host $host;
    }

    location / {
        return 200 "Please open console to see result<script src='/fetch/api.php?action=getJson'></script>";
    }
}

访问demo.lyz810.com,打开浏览器的控制台,看到发送请求/fetch/api.php返回了信息(这个是线上真实的接口数据),本例中使用了return指令直接返回了一段html代码,正常开发时,此处一般是由root指令指定的静态文件目录,也就是我们的工程目录。
几点说明:
1.proxy_pass需要填写服务器的IP地址,因为本机设置了host,并且nginx在本机部署,所以会受hosts的影响
2.proxy_set_header是nginx访问线上服务器时携带的请求头,因为上面写的是IP,而服务器不能直接通过IP访问(服务器上挂了那么多站点,你用IP访问,它也不知道返回哪个站点),加上Host这个请求头,服务器就知道返回哪个站点下的资源。

实例五:
终极代理大法一,访问本机8888端口,代理任何网站(需要配hosts文件)
代理配置:

server {
    listen 8888;
    server_name _ default;
    resolver 119.29.29.29;
    location / {
        proxy_pass https://$host$request_uri$is_args$args;
    }
}

绑定hosts:127.0.0.1 www.sogou.com,访问http://www.sogou.com:8888/web?query=lyz810&_asf=www.sogou.com可以看到结果,这里用80端口也是完全可以的,hosts文件只要指向本机就可以,如果是局域网内其他机器访问,可以直接配置hosts文件指向nginx服务器的IP,可以通过nginx代理任意站点。
注意:server_name 后面的default表示如果没有匹配到其他的域名,就用当前server,nginx配置文件中同一个端口最多有1个default的server,否则后面设置的default会无效。proxy_pass中域名使用了变量,所以需要配置resolver,即dns服务器地址,这里用的是腾讯云提供的公共dns。此例中代理走https协议,因为目前大部分网站支持https,这里暂且用https,后面有更灵活配置方式。
这种配置的一种应用是共享账号,可以登录某个网站后,将cookie记下来,配置在服务端的proxy_set_header Cookie …中,其他人访问该代理服务器,不需要登录就可以访问公共账号登录的内容。

实例六:
全局代理,无需配置hosts

server {
    listen 80;
    server_name proxy.local.lyz810.com;
    resolver 119.29.29.29;

    location ~ ^/([^/]*)(.*)$ {
        proxy_pass $scheme://$1$2$is_args$args;
    }
}

注意:这种方法只适合于代理任意已知地址,由于网页中很少使用相对地址,所以一般不能直接通过这种代理访问网站,仅限开发调试使用。
这里给出的例子并不完美,转发使用了$scheme根据访问的协议进行同协议转发,但只监听了80端口,所以此处一定为http,可以同时监听443并开启ssl,同时设置证书,已超出本文的内容,不再赘述。
这种配置也有应用场景,目前用于统一代理某些已知接口,不需要每次修改配置文件,只需要写好正确的地址,即可拿到数据。
线上应用在proxy.lyz810.com上,通过此服务器进行跨域管理,所有需要跨域访问的内容交给服务器去访问,返回的结果通过add_header增加跨域头,这样就可以让应用轻松的跨域,具体即实现方法以后介绍。

Nginx配置WordPress与HSTS

本文介绍HSTS基本概念,以及在nginx上配置HSTS的方法,配置站点基于WordPress。

本博客(https://blog.lyz810.com)已开启HSTS,读者可以尝试访问http协议,并观察浏览器的行为。
一、背景介绍
HSTS(HTTP Strict Transport Security)是HTTP严格传输安全,它是全站HTTPS时的一个更安全的策略,并且对网站性能有一定的优化。
全站HTTPS是一个必然的趋势,目前全站HTTPS通常的做法是同时开启80和443端口,当请求访问的是http协议时,通过rewrite将请求301重定向到https协议的对应URI。这么做的缺点是当用户访问http协议的页面时,总会有一次301重定向,增加网络请求以及服务器负担。
使用HSTS后,浏览器在第一次访问http协议时,会通过rewrite重定向到https协议,并根据https协议头中设定的相关响应头缓存下HSTS配置,下次用户再访问该站点下的任意http页面,浏览器会自动通过307跳转到https对应的URI上,不需要向服务器多发送一次请求。
HSTS配置方法如下:
1.当客户端通过HTTPS发出请求时,在服务器返回的超文本传输协议响应头中包含Strict-Transport-Security字段。非加密传输时设置的HSTS字段无效。
2.Strict-Transport-Security有3个参数,max-age表示有效期(如max-age=31536000表示未来的一年内,访问该域名会强制使用https,其中数字表示一年的秒数);includeSubDomains表示子域名也强制使用https;preload主要用于加入preload列表使用。
preload列表是一个站点的列表,他将会被通过硬编码写入 Chrome 浏览器中,列表中的站点将会默认使用 HTTPS 进行访问,此外,Firefox 、Safari 、IE 11 和 Edge 也同样一份 HSTS 站点列表,其中包括了 Chrome 的列表
加入preload列表的条件:

  • 有一张有效的证书
  • 重定向所有的 HTTP 流量到 HTTPS ( HTTPS ONLY )
  • 全部子域名的流量均通过 HTTPS ,如果子域名的 www 存在的话也同样需要通过 HTTPS 传输。
  • 为https协议添加的响应头Strict-Transport-Securit内容必须满足:max-age必须大于18周,必须指定includeSubDomains和preload,如果从当前https站点重定向到其他的站点,那个站点也必须启用HSTS

加入HSTS preload list:https://hstspreload.appspot.com/
二、Nginx配置

server {
  ......
  if ( $https != "on" ) {#https变量的值在https协议下为on,否则为空字符串
     rewrite ^(.*) https://$host$1 permanent;#301定向到https协议的相同URI,主要是首次访问本站的请求使用
     break;
  }

  location / {
     try_files $uri $uri/ /index.php$args;#此行是WordPress使用的,它会将请求都交给/index.php处理
     add_header Strict-Transport-Security max-age=86400000;#添加响应头,过期时间1000天,即1000天之内,浏览器会自动将本站http协议转为https协议
  }

  location ~ \.php$ {
     fastcgi_pass localhost:9000;#fastcgi监听9000端口,其他fastcgi配置写在了公用配置文件中
     add_header Strict-Transport-Security max-age=86400000;#由于正则的优先级更高,所以所有动态的页面均会走这里,需要添加响应头
   }
}

三、使用HSTS的弊端
用户只要访问一次站点,就会在max-age指定的时间内,强制访问https协议,在此期间,如果网站https出现了问题(如证书配置错误),用户就无法进行访问,没有配置HSTS时,证书错误用户可以选择忽略错误继续访问。
如果想要在过期时间内再次开启http访问,是无法做到的(对没有访问过站点的用户可行,只要访问过,过期时间前用户不能访问http协议),如果加入到HSTS Preload List中,就更没有办法启用http访问。
四、浏览器支持度
Chromium和Google Chrome从4.0.211.0版本开始支持HSTS
Firefox 4及以上版本
Opera 12及以上版本
Safari从OS X Mavericks起
Internet Explorer和Microsoft Edge从Windows 10开始支持

Nginx loaction优先级探究

本文通过配置实例来探究Nginx中location的优先级,在Nginx官方文档中,有优先级的说明,我们通过实例对此进行验证,加深理解。

实验声明:
return指令
该指令可以直接返回一个状态码及文本信息,由于是实验环境,没有必要建立真实的目录来对比location的命中,也免去了查看日志等繁琐的步骤。
因此,此实验均使用return指令来返回结果,用到的指令格式为return code text

实验环境
本文中使用的server_name采用的域名test.local.lyz810.com为真实域名,绑定IP为127.0.0.1,所以读者可以完全复制到本机实验。另外,*.local.lyz810.com域名做了泛解析,全部解析到127.0.0.1,读者可以任意选用该域名的子域名进行各种实验。

location类型

  1. 精确匹配:=
  2. 正则大小写敏感:~
  3. 正则大小写不敏感:~*
  4. 前缀匹配(高优先级):^~
  5. 前缀匹配:/

location优先级
以下是官网中给出的优先级:

To find location matching a given request, nginx first checks locations defined using the prefix strings (prefix locations). Among them, the location with the longest matching prefix is selected and remembered. Then regular expressions are checked, in the order of their appearance in the configuration file. The search of regular expressions terminates on the first match, and the corresponding configuration is used. If no match with a regular expression is found then the configuration of the prefix location remembered earlier is used.

翻译:
Nginx首先检查使用前缀定义的location,会选择并记住一个最长匹配的location作为候选。
检查正则表达式,根据出现在配置文件中的顺序。
遇到第一个匹配的正则表达式就停止搜索。
如果没有匹配的正则表达式,就使用最开始候选的location。

由此可以看出,正则表达式优先级最高,多个正则表达式按从上到下就先匹配到的为准,其次是前缀location。
注意,上面是普通前缀与正则之间的比较,所有location中精确匹配优先级最高。

实验一
目的:验证精确匹配优先级最高
原理:让所有类型的location出现在一个server中,并将精确匹配放到最后,消除顺序可能带来的影响,观察是否命中精确匹配location
配置:

server {
  server_name test.local.lyz810.com;
  default_type text/plain;

  location /test123 {
    return 200 prefix;
  }
  
  location ~ "/test\d{3}$" {
    return 200 regular;
  }

  location =/test123 {
    return 200 exact;
  }
}

结果:访问/test123,返回exact.说明精确匹配优先级最高

实验二
目的:验证正则的优先级高于前缀
原理:访问一个同时满足前缀和正则的页面,观察返回结果。为了排除顺序的影响,提供两个不同顺序。
配置:

server {
  server_name test.local.lyz810.com;
  default_type text/plain;

  location /test123 {
    return 200 prefix;
  }
  
  location ~ "/test\d{3}" {
    return 200 regular;
  }

  location ~ "/welcome\d{3}$" {
    return 200 regular;
  }

  location /welcome123 {
    return 200 prefix;
  }
}

结果:访问/test123和/welcome123都返回regular,说明正则优先级高于前缀,并且与正则和前缀出现的顺序无关。

实验三
目的:验证前缀匹配只与匹配长度有关,与顺序无关。
原理:通过不同顺序的前缀匹配,观察输出结果。
配置:

server {
  server_name test.local.lyz810.com;
  default_type text/plain;

  location /test123 {
    return 200 test123;
  }
  
  location /test1234 {
    return 200 test1234;
  }

  location /welcome1234 {
    return 200 welcome1234;
  }

  location /welcome123 {
    return 200 welcome123;
  }
}

结果:访问/welcome1234和/test1234分别返回welcome1234和test1234,与顺序无关,只与匹配长度有关。

实验四
目的:比较高优先级前缀(^~)与正则的优先级
原理:将正则location置于最前,高优先级前缀location置于后面,通过观察输出结果,比较优先级。
配置:

server {
  server_name test.local.lyz810.com;
  default_type text/plain;

  location ~ "/test\d{3}" {
    return 200 regular;
  }

  location ^~ /test123 {
    return 200 prefix;
  }
}

结果:访问/test123返回prefix,证明^~优先级高于正则

实验五
目的:一个配置文件了解location优先级
配置:

server {
  server_name test.local.lyz810.com;
  default_type text/plain;

  location =/test12345 {
    return 200 =/test12345;
  }

  location /test12 {
    return 200 /test12;
  }

  location /test1234 {
    return 200 /test1234;
  }

  location ^~ /test123 {
    return 200 "^~/test123";
  }

  location ~ "/test\d{3}$" {
    return 200 "~/test\d{3}";
  }

  location ~ "/test\d{4}$" {
    return 200 "~/test\d{4}";
  }

  location ~ "/test\d*" {
    return 200 "~/test\d*";
  }
}

结果:
1.访问/test12返回~/test\d*,匹配项/test12、/test\d*,正则优先级高,故命中正则。
2.访问/test123返回^~/test123,匹配项/test12、^~/test123、~/test\d{3}、~/test\d*,先对前缀进行比较,发现有2个前缀/test12和^~/test123,后者长,用后者,然后发现前缀匹配带^~,不再查找正则,故命中^~/test123。
3.访问/test1234返回~/test\d{4},匹配项/test12、/test1234、^~/test123、~/test\d{4}、~/test\d*,先对前缀进行比较,发现有3个前缀/test12、/test1234、^~/test123,根据匹配长度,选中/test1234,再对正则进行匹配,~/test\d{4}是第一个匹配的正则,而之前选出的前缀中不带^~,故命中第一个匹配的正则。
4.访问/test12345返回=/test12345,匹配项=/test12345、/test12、/test1234、^~/test123、~/test\d*,有精确匹配存在,其他的都靠边站。
5.访问/test123456返回~/test\d*,匹配项/test12、/test1234、^~/test123、~/test\d*,前缀选择/test1234,无^~,命中第一个(唯一一个)匹配的正则表达式。
6.访问/welcome/test1234返回~/test\d{4},匹配项~/test\d{4}、~/test\d*,正则可以匹配path的一部分,而前缀只能从头开始匹配。
7.访问/test12345?params=1返回=/test12345,location匹配的是path部分,不包括后面的queryString。

总结
location匹配的算法描述如下(实验的出的结论,非源码中的算法):
1.令pathname=url路径部分,findPrefix=””
2.令prefixList=所有前缀location,regularList=所有正则location,exactList=所有精确匹配location
3.遍历exactList,查找location =pathname,如果找到,则命中,算法结束
4.遍历prefixList,如果匹配pathname则与findPrefix的长度进行比较
4.1如果长度大于findPrefix,令findPrefix=当前location
4.2如果长度小于findPrefix,不做任何操作,继续遍历
4.3如果长度等于findPrefix,在配置载入的时候,会报location重复的错误(如同时存在/123和^~/123),没有该分支
5.判断findPrefix是否带^~前缀,如果带,命中findPrefix,算法结束
6.遍历regularList,如果匹配pathname,命中当前location,算法结束
7.如果findPrefix不为空,命中findPrefix,算法结束
8.输出404