kivagant / staticus
基于HTTP的文件代理和文件管理器,带有REST API
Requires
- php: ^5.6 || ^7.0
- aura/di: ^3.1
- kivagant/staticus-core: ^1.1
- kivagant/staticus-fractal-manager: ^1.0
- kivagant/staticus-search-manager: ^1.0
- league/flysystem: ^1.0
- mtymek/expressive-config-manager: ^0.4.0
- newage/audio-manager: ^1.1
- roave/security-advisories: dev-master
- vlucas/phpdotenv: 2.2
- zendframework/zend-expressive: ^1.0
- zendframework/zend-expressive-fastroute: ^1.0
- zendframework/zend-expressive-helpers: ^2.0
- zendframework/zend-permissions-acl: ^2.6
- zendframework/zend-session: ^2.7
- zendframework/zend-stdlib: ~2.7
Requires (Dev)
- filp/whoops: ^1.1 || ^2.0
- league/flysystem-memory: ^1.0
- phpunit/phpunit: ^4.8
- squizlabs/php_codesniffer: ^2.3
README
简而言之,这是一个基于PSR-7的服务,它是一个“隐形”层,会动态查找请求的静态文件,并告诉Nginx它们的位置。 “管道后处理”,内容生成器和ACL支持为您提供了在您的Web服务上管理文件的有力工具。
快速示例
- POST https://www.your.project.dev/staticus/waxwing.mp3
> File will be generated (if you have access) and placed to path like ~/mp3/def/0/22af64.mp3
- GET https://www.your.project.dev/staticus/waxwing.mp3
> The php-backend layer will be called once for the file path search, then file will be sended throught Nginx
- GET https://www.your.project.dev/staticus/waxwing.mp3
> File will be returned from Nginx cache
该服务处理HTTP请求,并给Nginx返回X-Accel-Redirect,这将强制下载文件。
根据请求的路径,加载相应的代理层,该层引用提供者生成内容,然后缓存结果,下次从缓存中提供文件。
应配置Nginx内部缓存以提供重复请求的最大速度。 以下是示例。
使用Staticus,您将可以
- 调用HTTP CRUD操作来处理项目中不同的静态文件,而无需进行硬集成工作;
- 根据请求生成任何文件资源:图像、声音、文档等;
- 当从前端请求时,'即时'调整和裁剪图像;
- 在Google中搜索新图像;
- 编写自己的操作层(中间件)并使此工具更加强大!
依赖项
阅读项目使用的依赖项信息。
内容
免责声明
- 本说明书的某些部分尚未翻译。 如果您能帮助翻译,欢迎提交 PR。
- 示例由工具 jkbrzt/httpie 展示。提示:如果您在控制台看到一些意外的 HTML,请使用 your-command | html2text
- 所有数据操作(如读取、生成或删除)都由 ACL 配置控制。
安装和测试
注意:Composer 将运行 post-create-project-cmd
。所有分发配置文件将从模板中复制而不替换。如果您不信任 Composer 脚本,只需添加 --no-scripts
参数。
- 此项目类似于即用型应用程序。因此,您不需要 require 它。相反,运行
$ composer create-project "kivagant/staticus"
$ cd staticus
-
打开 .env 文件进行编辑并设置其中的变量。首先,不要忘记设置 DATA_DIR!
-
重要提示:下一步将尝试创建和删除测试文件(在 AcceptanceTest 中)。因此,请阅读 许可协议,运行并祈祷 :)
$ composer run-script test
> phpunit
PHPUnit 4.8.24 by Sebastian Bergmann and contributors.
...
OK (85 tests, 377 assertions)
然后,您可以在没有 Nginx 的情况下运行项目,并且几乎像下面的示例那样与之一起工作。唯一的区别是,由于只发送 X-Accel-Redirect 标头,您在 GET 请求中看不到任何文件。
$ composer run-script serve
但如果您想看到真正的黑暗魔法,请阅读本文档的下一部分。
Nginx配置
寻找简单的 Nginx 配置示例: staticus.conf
Nginx 的主要主机代理请求到“辅助主机”(实际上是它自己)并缓存成功的响应
proxy_pass http://127.0.0.1:8081;
辅助 Nginx 主机代理请求到后端(Staticus php-project)。
location / {
...
include fastcgi_fpm.conf;
}
后端处理请求,查找文件或生成新内容,并发送 X-Accel-Redirect,该响应告诉 Nginx 从哪里获取下载的最终文件。
Nginx 根据内部位置配置处理路由并将结果发送给客户端。
location ~* ^/data/(img|voice)/(.+)\.(jpg|jpeg|gif|png|mp3)$ {
internal;
...
}
对于客户端来说,一切都像他刚刚收到了一个静态文件。
查询结构
方案:[//[用户:密码@]主机[:端口]][/路径到主页][/命名空间/子命名空间]/资源类型[?参数]
- 用户:密码:管理员角色的基于 HTTP 的身份验证。
- 路径到主页:项目可以位于子路由中,在创建客户端视图中的正确 URL 时应考虑这一点。
- 命名空间:具有单独 ACL 规则的逻辑分组资源。每个会话授权用户都有自己的命名空间
/user/{id}
。 - 资源:资源的基本简短名称。使用相同的地址,相同的资源始终会返回。
- 类型:资源类型,它保证了返回文件的扩展名和 MIME 类型。
- 参数:默认支持一些参数,但不同类型的资源可以有它们自己的参数。参数影响返回的数据。可以发送在查询或 POST 正文中。
支持的HTTP方法
PUT 不受支持。
参数
var: 字符串,资源变体名称
默认使用 'def' 变体。
对于某些资源可能需要存储或生成不同的唯一变体。例如,用户可以上传他的资源版本,或者资源可以以几种变体生成。
alt: 字符串,替代资源名称
此参数可以针对不同资源类型进行不同处理。
当您创建一个资源时,您可以传递一个替代名称。
有时主资源名称不足以生成正确数据,或确保唯一性。例如,如果常见的短名称不能充分描述资源,或缺少GET请求的长度,或全名包含Unicode字符等。
根据资源类型,替代名称可以被进一步处理或忽略。例如,替代名称将用于语音而不是通用名称。对于图像搜索——与主名称一起。
资源car.jpg
、car.jpg?alt=вагон
和car.jpg?alt=машина
被视为不同的资源。替代名称将与基本名称一起用于生成资源的uuid。
body: 字符串,用于资源创建的附加信息
有时仅使用URI变量来创建资源是不够的。例如,当您需要创建一个带有uri: /my-audio.mp3
的音频文件,其中包含大量文本(用于语音)时,您可以通过HTTP POST正文发送body参数。资源将具有简短的URL(没有alt=...),同时,在内部包含正确的大量数据。
v: 整数,版本ID
每个资源版本都包含自己的版本。默认情况下使用0版本(并且v=0是不必要的),它反映了当前资源的最新状态。
如果更改了资源的标准版本或特殊版本(重新创建或删除),当前的零版本将自动保存为新的自动递增版本,并创建新的零版本。
当删除零版本时,它只是移动到新的自动递增版本,其他版本将不会被删除。
在更改资源时,您可以发送对特定版本的指针,它可以被完全删除或替换。如果中间列表中的版本将被删除(例如v=2),将出现“空隙”:v=1: 200,v=2: 404 Not found,v=3: 200。
如果您删除列表末尾的版本,其他版本将在下一次更改时可用相同的编号。
- v=1: 200,v=2: 404 < 已删除
- v=1: 200,v=2: 200 < 在更改零版本后再次添加
对于完全删除资源,您需要发送一个destroy参数。
DELETE destroy: 布尔值,删除不备份
- 如果在删除资源时,在默认版本的默认版本中传递参数destroy,则资源将在所有版本和版本中删除。
- 如果在删除默认版本时,为特定版本传递参数destroy,则将删除该版本的 所有版本。
- 如果在删除资源时指定了特定的版本(对于任何版本)并传递了参数destroy,则将只删除该版本的指定版本,即参数destroy不会对行为产生影响。
POST author: 字符串,作者
待办事项:尚未实现。关于更改作者的任意字符串的信息的行。仅用于日志记录。
POST uri=http 通过远程URI上传图像
在url参数中指定的图像将被上传到服务器。
路径结构
不同的资源类型可以有不同的路径结构。资源对象内部有路径映射规范。您可以通过方法ResourceDOInterface::getDirectoryTokens()查看规范。
- [/namespace]/type/shard_variant/variant/version/[size/][other-type-specified/]shard_uuid/uuid.type
- /jpg/def/def/0/0/22a/22af64.jpg
- /jpg/use/user/3/0/22a/22af64.jpg
- /jpg/fra/fractal/0/22a/30x40/22af64.jpg
- /jpg/som/some_module/0/22a/100x110/22af64.jpg
- /mp3/def/def/0/22a/22af64.mp3
- /mp3/def/def/1/22a/22af64.mp3
注意:`shard_variant`和`shard_uuid`应有助于避免文件系统崩溃或关键响应时间。在下面的示例中,它们可以省略。
JPG类型
特殊参数
size=WxH, 字符串,图像尺寸
对于jpg图片,在输出时会自动进行图像裁剪。为了使图像能够裁剪,在配置文件中必须注册所有允许的尺寸。拥有未注册尺寸的图像将被裁剪到最近找到的已注册的较大尺寸。
如果向图片发送没有上传文件的POST请求,将生成一个分形图片。这非常适合用作默认的占位符。
filters[]=filtername, 字符串,后处理过滤器
待办事项:支持过滤器
MP3类型
GET /*.mp3
- 后端检查文件是否存在
- 如果找到文件,则向Nginx报告最终的URL,该URL将被缓存
- 如果没有找到,则返回404 Not found
第一次请求(无缓存)
$ http --verify no -h GET https://www.your.project.dev/staticus/waxwing.mp3
HTTP/1.1 200 OK
Accept-Ranges: bytes
Cache-Control: public
Connection: keep-alive
Content-Length: 4904
Content-Type: audio/mpeg
Date: Mon, 04 Apr 2016 07:13:12 GMT
ETag: "5701963e-1328"
Last-Modified: Sun, 03 Apr 2016 22:16:30 GMT
Server: nginx/1.9.7
X-Proxy-Cache: MISS
第二次请求(Nginx缓存)
Nginx将从其自己的缓存中提供文件,不再调用proxy_pass。
$ http --verify no -h GET https://www.your.project.dev/staticus/waxwing.mp3
HTTP/1.1 200 OK
Accept-Ranges: bytes
Cache-Control: public
Connection: keep-alive
Content-Length: 4904
Content-Type: audio/mpeg
Date: Mon, 04 Apr 2016 07:13:21 GMT
ETag: "5701963e-1328"
Last-Modified: Sun, 03 Apr 2016 22:16:30 GMT
Server: nginx/1.9.7
X-Proxy-Cache: HIT
POST /*.mp3
注意:recreate参数始终会触发重新生成
- 后端检查文件是否存在。
- 如果找到(且没有recreate标志),则返回HTTP 304 Not Modified。
- 如果未找到,则调用已注册的语音提供商(支持一个提供商)。
- 后端将获取的结果缓存在其自己的文件夹中。
- 待办事项:记录请求与文件的关联,以便允许搜索和过滤文件。
- 返回HTTP 201 Created
创建
$ find /var/www/cache/mp3 -type f -name *.mp3
(nothing here)
$ http --verify no --auth Developer:12345 -f POST https://www.your.project.dev/staticus/waxwing.mp3
HTTP/1.1 201 Created
Cache-Control: public
Cache-Control: public
Connection: keep-alive
Content-Length: 0
Content-Type: application/json
Date: Mon, 04 Apr 2016 20:30:37 GMT
Server: nginx/1.9.7
X-Powered-By: PHP/5.6.15
{
"resource": {
"name": "waxwing",
"nameAlternative": "",
"recreate": false,
"type": "mp3",
"uuid": "2d5080a8ea20ec175c318d65d1429e94",
"variant": "def",
"version": 0
},
"uri": "waxwing.mp3"
}
$ find /var/www/cache/mp3 -type f -name *.mp3
/var/www/cache/mp3/def/0/2d5080a8ea20ec175c318d65d1429e94.mp3
次生成
$ http --verify no --auth Developer:12345 -f POST https://www.your.project.dev/staticus/WaxWing.mp3
HTTP/1.1 304 Not Modified
Cache-Control: public
Cache-Control: public
Connection: keep-alive
Content-Length: 0
Date: Mon, 04 Apr 2016 20:36:16 GMT
Server: nginx/1.9.7
X-Powered-By: PHP/5.6.15
find /var/www/cache/mp3 -type f -name *.mp3
/var/www/cache/mp3/def/0/2d5080a8ea20ec175c318d65d1429e94.mp3
再生1:重新创建的文件与现有文件相同
$ http --verify no --auth Developer:12345 -f POST https://www.your.project.dev/staticus/waxwing.mp3 recreate=1
HTTP/1.1 304 Not Modified
Cache-Control: public
Cache-Control: public
Connection: keep-alive
Content-Length: 0
Date: Sat, 09 Apr 2016 10:08:50 GMT
Server: nginx/1.9.7
X-Powered-By: PHP/5.6.15
$ find /var/www/cache/mp3 -type f -name *.mp3
/var/www/cache/mp3/def/0/2d5080a8ea20ec175c318d65d1429e94.mp3
再生2:创建的文件是不同的
$ http --verify no --auth Developer:12345 -f POST https://www.your.project.dev/staticus/waxwing.mp3 recreate=1
HTTP/1.1 201 Created
Cache-Control: public
Cache-Control: public
Connection: keep-alive
Content-Length: 0
Content-Type: application/json
Date: Sat, 09 Apr 2016 10:41:39 GMT
Server: nginx/1.9.7
X-Powered-By: PHP/5.6.15
{
"resource": {
"name": "waxwing",
"nameAlternative": "",
"recreate": true,
"type": "mp3",
"uuid": "2d5080a8ea20ec175c318d65d1429e94",
"variant": "def",
"version": 0
},
"uri": "waxwing.mp3"
}
$ find /var/www/cache/mp3 -type f -name *.mp3
/var/www/cache/mp3/def/0/2d5080a8ea20ec175c318d65d1429e94.mp3
/var/www/cache/mp3/def/1/2d5080a8ea20ec175c318d65d1429e94.mp3 # automatically backuped version
文件上传
- 您可以为上传的文件使用任何参数名称,但只有文件列表中的第一个文件将被上传。
- 如果版本已存在,则忽略上传。因此,使用'recreate'参数强制上传。
$ http --verify no --auth Developer:12345 -f POST https://www.your.project.dev/staticus/waxwing.mp3 \
recreate=true var=uploaded file@/Users/kivagant/vagrant/staticus/test.mp3
HTTP/1.1 201 Created
Cache-Control: public
Cache-Control: public
Connection: keep-alive
Content-Length: 0
Content-Type: application/json
Date: Sun, 10 Apr 2016 14:40:17 GMT
Server: nginx/1.9.7
X-Powered-By: PHP/5.6.15
{
"resource": {
"name": "waxwing",
"nameAlternative": "",
"recreate": true,
"type": "mp3",
"uuid": "2d5080a8ea20ec175c318d65d1429e94",
"variant": "test",
"version": 0
},
"uri": "waxwing.mp3?var=test"
}
$ find /var/www/cache/mp3 -type f -name *.mp3
/var/www/cache/mp3/def/0/2d5080a8ea20ec175c318d65d1429e94.mp3
/var/www/cache/mp3/def/1/2d5080a8ea20ec175c318d65d1429e94.mp3
/var/www/cache/mp3/test/0/2d5080a8ea20ec175c318d65d1429e94.mp3
远程下载文件
$ http --verify no --auth Developer:12345 -f POST https://www.your.project.dev/staticus/waxwing.mp3 var=remote uri='http://some.domain/new.mp3'
HTTP/1.1 201 Created
Cache-Control: public
Cache-Control: public
Connection: keep-alive
Content-Length: 186
Content-Type: application/json
Date: Mon, 11 Apr 2016 01:22:01 GMT
Server: nginx/1.9.7
X-Powered-By: PHP/5.6.15
{
"resource": {
"name": "waxwing",
"nameAlternative": "",
"recreate": false,
"type": "mp3",
"uuid": "2d5080a8ea20ec175c318d65d1429e94",
"variant": "remote",
"version": 0
},
"uri": "waxwing.mp3?var=remote"
}
删除 /*.mp3
- 后端检查文件是否存在。
- 如果找到,将在条件满足的情况下创建备份,即上一个非零版本与删除的版本不匹配。
- 删除当前原始文件。
- 返回204 No content。
安全删除
如果版本1不等于0,则版本0将备份到新版本2。
$ find /var/www/cache/mp3 -type f -name *.mp3
/var/www/cache/mp3/def/0/2d5080a8ea20ec175c318d65d1429e94.mp3
/var/www/cache/mp3/def/1/2d5080a8ea20ec175c318d65d1429e94.mp3
$ http --verify no --auth Developer:12345 DELETE https://www.your.project.dev/staticus/waxwing.mp3
HTTP/1.1 204 No Content
Cache-Control: public
Cache-Control: public
Connection: keep-alive
Content-Length: 0
Content-Type: audio/mpeg
Date: Mon, 04 Apr 2016 20:40:05 GMT
Server: nginx/1.9.7
X-Powered-By: PHP/5.6.15
$ find /var/www/cache/mp3 -type f -name *.mp3
/var/www/cache/mp3/def/2/2d5080a8ea20ec175c318d65d1429e94.mp3 # automatically backuped version
/var/www/cache/mp3/def/1/2d5080a8ea20ec175c318d65d1429e94.mp3
$ http --verify no GET https://www.your.project.dev/staticus/waxwing.mp3\?nocache\=bzbzbz # skip nginx cache
HTTP/1.1 404 Not Found
Connection: keep-alive
Content-Length: 0
Content-Type: audio/mpeg
Date: Sat, 09 Apr 2016 10:48:19 GMT
Server: nginx/1.9.7
X-Powered-By: PHP/5.6.15
销毁
$ http --verify no --auth Developer:12345 DELETE https://www.your.project.dev/staticus/waxwing.mp3\?destroy\=1
HTTP/1.1 204 No Content
Cache-Control: public
Cache-Control: public
Connection: keep-alive
Content-Length: 0
Content-Type: audio/mpeg
Date: Sat, 09 Apr 2016 11:38:30 GMT
Server: nginx/1.9.7
X-Powered-By: PHP/5.6.15
$ find /var/www/cache/mp3 -type f -name *.mp3
(nothing here)
高级使用
列出所有资源文件
- GET|POST /list/{resource_route}
- ACL Action: list
$ http --body --verify no --auth Developer:12345 GET https://www.your.project.dev/staticus/list/welcome.jpg
{
"options": [
{
"dimension": "0",
"size": 5322,
"timestamp": 1464692308,
"variant": "def",
"version": "0"
},
{
"dimension": "100x100",
"size": 2165,
"timestamp": 1464692314,
"variant": "def",
"version": "0"
},
{
"dimension": "0",
"size": 17055,
"timestamp": 1464692306,
"variant": "def",
"version": "1"
}
],
"resource": {
"dimension": "0",
"height": 0,
"name": "welcome",
"nameAlternative": "",
"namespace": "",
"new": false,
"recreate": false,
"type": "jpg",
"uuid": "40be4e59b9a2a2b5dffb918c0e86b3d7",
"variant": "def",
"version": 0,
"width": 0
}
}
使用特殊路由 /search/ 进行 JPG 搜索
在.env
配置中设置GOOGLE_SEARCH_API_KEY和GOOGLE_SEARCH_API_CX。
- GET|POST /search/{resource_route}?cursor=integer
- ACL Action: search
由搜索适配器找到的文件列表将被返回。
- 从列表中选择一个URL。
- 向任何具有相同类型和添加参数uri=chosen-uri的资源路由发送POST请求。
- 您可以为不同类型的资源附加其他搜索适配器和动作(并更改搜索行为)。
- 您可以使用Actions::ACTION_SEARCH命令配置ACL配置以进行搜索。
- 只有具有'adimn'角色的用户才能使用cursor属性。
- 默认情况下,'name'和'alt'将一起使用以进行更准确的搜索。
- 默认情况下,如果传递了(可以用作搜索字符串),则使用POST 'body'参数而不是'name'和'alt'。
搜索示例
$ http --body --verify no --auth Developer:12345 -f GET https://www.your.project.dev/staticus/search/welcome.jpg\?alt\='school'\&cursor\=11
{
"found": {
"count": 10,
"items": [
{
"height": 675,
"size": 453573,
"thumbnailheight": 112,
"thumbnailurl": "https://somehots.somedomain/someurl",
"thumbnailwidth": 146,
"title": "FREE Back to School Party",
"url": "http://somehots.somedomain/wp-content/uploads/2013/02/welcome-back-to-school.jpg",
"width": 880
},
{...},
],
"start": 10,
"total": "449000000"
}
}
基于 HTTP 的身份验证
这是一个主要认证方式。仅用于管理员角色。请查阅在routes.global
配置中激活的AuthBasicMiddleware
。此中间件将为当前User对象设置ADMIN角色,而不管基于会话的登录状态如何。请查阅acl.global
配置以了解ADMIN角色。
基于会话的身份验证
AuthSessionMiddleware
允许您使用项目中的会话,该项目包含'Staticus'。您只需使用Nginx规则即可透明地嵌入此项目。
例如,如果您的基项目域名为https://my.domain.dev
,则可以将'Staticus'放置在子路径中:https://my.domain.dev/static/
,然后所有文件都可以通过此路由访问。此子路径称为文档中查询结构的path-to-home
。请参阅etc/nginx/conf.d/staticus.conf
中的Nginx规则模板以了解此情况。
在这种情况下,'Staticus'将有权访问基本域的cookie,以及用户的会话。
因此,如果您的项目使用了Zend_Auth存储,则AuthSessionMiddleware
将从中加载Redis
会话,并查找以下路径:$_SESSION['Zend_Auth']->storage->user_id
。
如果您想将会话处理器从Redis更改为其他选项,只需在本配置文件的依赖部分将SessionManagerFactory
替换为另一个即可:auth.global.php
。
AuthSessionMiddleware
所需的所有ACL规则和默认用户命名空间支持 - 它是user_id。因此,您可以将其中间件替换为您自己的,并实现此逻辑
$this->user->login($storage->user_id, [Roles::USER]);
$this->user->setNamespace(UserInterface::NAMESPACES . DIRECTORY_SEPARATOR . $storage->user_id);
命名空间
您可以在命名空间中分组您的资源,并为它们设置不同的访问控制列表规则。在staticus.global
配置中设置允许的命名空间列表。您在这里可以使用通配符语法。
AclMiddleware
将帮助实现acl.global
配置中的规则。
因此,您可以为全局命名空间和特殊命名空间中的任何资源类型设置不同角色的规则。
默认情况下
- 任何访客都可以在任何命名空间中读取任何资源。
- 任何授权用户都有自己的命名空间(从
/user/{id}
开始)并在其中具有任何对JPG资源的访问权限。 - 管理员可以对所有资源具有任何访问权限。
您可以通过ACL配置或添加另一个中间件来添加或更改此行为。
贡献者
- Andrew Yanakov
- Eugene Glotov
许可协议
制作于EnglishDom在线学校。
版权所有2016 Eugene Glotov
根据Apache License,版本2.0(“许可”)许可;除非根据适用法律或书面同意,否则不得使用此文件,除非遵守许可。您可以在以下位置获得许可证副本:
https://apache.ac.cn/licenses/LICENSE-2.0
除非适用法律要求或书面同意,否则在本许可下分发的软件按“原样”基础分发,不提供任何明示或暗示的保证或条件。有关许可中规定的权限和限制的具体语言,请参阅许可。