PythonからElasticsearchを扱うelasticsearch-dsl-pyがなかなか良かった
elastic/elasticsearch-dsl-py · GitHub
いつの間に公式からHigh levelなPythonクライアントが出ていたので使用してみました。
PythonのElasticsearch関連のライブラリは色々とHaystack(つらい)とかelasticutils(いつの間にかDeprecatedになっていた)とかがあって 不遇だったのでコレで解消されればよいのかな。
機能面
elasticsearch-dsl-pyはElasticsearchの薄いラッパーと言うことらしいのですが、ひと通りの機能は揃っているみたいです。
あと特にDjangoやらSQLAlchemyやらを併用して使用することは今のところ想定されていないみたい。
Analysis
analyzerは kuromoji_analyzer
と ngram_analyzer
がすでに定義されているものとします
Mapping
Mappingはdynamicに定義するのとstaticに定義する方法がある
dynamic:
from elasticsearch_dsl import Mapping m = Mapping('mix') m.field('title', 'string') m.field('kuromoji', 'string', analyzer="kuromoji_analyzer") m.field('ngram', 'string', analyzer="ngram_analyzer") m.field('published', 'boolean') m.field('ctime', 'date') m.meta('_all', enabled=False) m.save('mix') # send mapping
static: model-likeとのこと。なんか既に誰かがDjangoとかSQLAlchemy用に拡張してそうな形だ
from datetime import datetime from elasticsearch_dsl import ( DocType, String, Date, Boolean ) class Mix(DocType): title = String() kuromoji = String(analyzer="kuromoji_analyzer") ngram = String(analyzer="ngram_analyzer") published = Boolean() ctime = Date() class Meta: index = 'mix' Mix.init() # send mapping
saveまたはinitすると以下のような物が送られます
[in env/lib/python3.4/site-packages/elasticsearch/connection/base.py:72] 2015-02-04 16:30:33 91613 140735325455120 INFO base: \ curl -XPUT 'http://localhost:9200/mix/_mapping/mix?pretty' -d '{ "mix": { "properties": { "ctime": { "type": "date" }, "kuromoji": { "analyzer": "kuromoji_analyzer", "type": "string" }, "ngram": { "analyzer": "ngram_analyzer", "type": "string" }, "published": { "type": "boolean" }, "title": { "type": "string" } } } }'
save document
model-likeなMixオブジェクト使用してdocumentの送信
sentence = "アナライザの実装には細かいプロパティがいくつかあり" mix = Mix(id=1, kuromoji=sentence, ngram=sentence, title=sentence, published=True) mix.save() # curl -XPUT 'http://localhost:9200/mix/mix/1' -d '{"ctime": "2015.....}' mix = Mix(id=2, kuromoji=sentence, ngram=sentence, title=sentence, published=True) mix.save() # curl -XPUT 'http://localhost:9200/mix/mix/2' -d '{"ctime": "2015.....}'
In [1]: for mix in Mix.search().query("match", ngram="アナ").execute(): ...: print(mix.published) # Resultオブジェクトとしても使われる ...: True True
他には filters, aggregations, sort, pagination, additional parameters, suggest, highlight などがあるようです
無い機能
上記でも書いているが、ORMのモデルとの関連付けは自分で書かないといけないですね
まぁそこはチョロっとsignralあたりを使って書いてください
こちらからは以上です
おまけ
pip登録されてないけどこれも気になってる
liberation/django-elasticsearch · GitHub
2015年 4月 2日 追記
elasticsearch-dsl-pyを使ってZero Downtime Reindexingを書いてみました
Elasticsearch Zero Downtime Reindexing using elasticsearch-dsl-py ref: https://www.elastic.co/blog/changing-mapping-with-zero-downtime gist.github.com
beego migration
なんかbee new
で新しくprojectを作るとmigrationフォルダができるけどなんか使いづらかったので
gooseを替わりに使用した
Download
$ go get bitbucket.org/liamstask/goose/cmd/goose
db/dbconf.ymlを作成&編集
$ cat db/dbconf.yml development: driver: postgres open: postgres://user:password@develop.local:5432/tablename production: driver: postgres open: postgres://user:password@127.0.0.1:5432/tablename environment_variable_config: driver: $DB_DRIVER open: $DATABASE_URL
'entry'.q カラムにginインデックスをはる
$ goose create InstallPgExtension sql goose: created /path/to/gopath/src/example.com/ikeikeikeike/example/db/migrations/20150130234510_InstallPgExtension.sql $ goose create AddGinIndexToQOnEntry sql goose: created /path/to/gopath/src/example.com/ikeikeikeike/example/db/migrations/20150130234522_AddGinIndexToQOnEntry.sql
20150130234510_InstallPgExtension.sql の中身
-- +goose Up -- SQL in section 'Up' is executed when this migration is applied CREATE EXTENSION IF NOT EXISTS pg_trgm; -- +goose Down -- SQL section 'Down' is executed when this migration is rolled back DROP EXTENSION IF EXISTS pg_trgm;
20150130234522_AddGinIndexToQOnEntry.sql の中身
-- +goose Up -- SQL in section 'Up' is executed when this migration is applied CREATE INDEX index_entry_on_q ON entry USING gin (q gin_trgm_ops); -- +goose Down -- SQL section 'Down' is executed when this migration is rolled back DROP INDEX index_entry_on_q;
migration実行 -env オプションでenvironmentの変更ができる
up
$ goose up goose: migrating db environment 'development', current version: 0, target: 20150130174127 OK 20150130234510_InstallPgExtension.sql OK 20150130234522_AddGinIndexToQOnEntry.sql
down
$ goose down goose: migrating db environment 'development', current version: 20150130174127, target: 20150130174043 OK 20150130174127_AddGinIndexToQOnEntry.sql $ goose down goose: migrating db environment 'development', current version: 20150130174043, target: 0 OK 20150130174043_InstallPgExtension.sql
よさげ
べーごー signup,login,logout
Beegoで認証あたりを処理を書いた(メール飛ばしたりはしてない)。以下やっていること
- プロジェクト生成
- Model定義
- Form処理
- ユーザー, 認証処理
- Flash messages, Session処理
- login filter(require)なるもの
もろもろインストール
$ go get -u github.com/beego/bee $ go get -u github.com/astaxie/beego
プロジェクト作成コマンド
$ bee new auth
こんなツリーが出来る
$ tree . ├── conf │ └── app.conf ├── controllers │ └── default.go ├── main.go ├── models ├── routers │ └── router.go ├── static │ ├── css │ ├── img │ └── js ├── tests │ └── default_test.go └── views └── index.tpl
ユーザーを定義する
Email,Password,ログイン時間,作成時間,更新時間のよくある形
package models import ( "time" "github.com/astaxie/beego" "github.com/astaxie/beego/orm" ) type User struct { Id int64 Email string `orm:"size(64);unique"` Password string `orm:"size(32)"` Lastlogintime time.Time `orm:"type(datetime);null"` Created time.Time `orm:"auto_now_add;type(datetime)"` Updated time.Time `orm:"auto_now;type(datetime)"` } func init() { orm.RegisterModelWithPrefix( beego.AppConfig.String("dbprefix"), new(User)) }
.. doc::
Fieldに複数の属性をつける場合ははセミコロン(;)でつなげる どんな属性があるかはドキュメントを: Model Definition - beego: simple & powerful Go app framework
次
Modelを定義したらDDLを走らせる。 これは以下のようにorm.RunSyncdbをinitあたりに定義しておけばBeegoがやってくれる (ノリ的にDjangoのsyncdbと同じもの)
func init() { dbname, force, verbose := "default", false, true err := orm.RunSyncdb(dbname, force, verbose) if err != nil { panic(err) } }
私はこんなふうに書いてます: beego-samples/db.go at master · ikeikeikeike/beego-samples · GitHub
次
Formの定義
先ほど定義したuserテーブルを編集, パスワード確認のRepasswordと「form:」「valud:」を追加
type User struct { Id int64 Email string `orm:"size(64);unique" form:"Email" valid:"Required;Email"` Password string `orm:"size(32)" form:"Password" valid:"Required;MinSize(6)"` Repassword string `orm:"-" form:"Repassword" valid:"Required"` Lastlogintime time.Time `orm:"type(datetime);null" form:"-"` Created time.Time `orm:"auto_now_add;type(datetime)"` Updated time.Time `orm:"auto_now;type(datetime)"` }
「form:」はhtml上のkinput[name="Email"]のところ。「valud:」はValidation定義 ドキュメントはここ: Form validation - beego: simple & powerful Go app framework
上述に対応するHTMLが下記のような形になる。
<form method="POST" action='{{urlfor "LoginController.Login"}}'> {{ .xsrfdata }} <input name="Email" type="email" value="{{index .Params "Email"}}" required /> <input name="Password" type="password" value="" required pattern=".{6,}" title="パスワードは6文字以上を入力してください" /> <input name="Repassword" type="password" required pattern=".{6,}" title="パスワードは6文字以上を入力してください" /> <input type="submit" value="ログイン"> </form>
次は認証処理
- Routerの追加
Routerは以下の物があり,Automatching, Annotations, Namespaceがあったり色々できる
以下の例ははSinatra風に記述ができるRegex routerを使用している
package routers import ( ctl "bitbucket.org/ikeikeikeike/auth/controllers" "github.com/astaxie/beego" ) func init() { beego.Router("/login", &ctl.LoginController{}, "get,post:Login") beego.Router("/logout", &ctl.LoginController{}, "get:Logout") beego.Router("/signup", &ctl.LoginController{}, "get,post:Signup") }
- Controller Signup部分
xsrf, flash message, formのパース, Sessionとか色々やってる
func (c *LoginController) Signup() { c.TplNames = "login/signup.tpl" c.Data["xsrfdata"] = template.HTML(c.XsrfFormHtml()) if !c.Ctx.Input.IsPost() { return } var err error flash := beego.NewFlash() u := &models.User{} if err = c.ParseForm(u); err != nil { flash.Error("Signup invalid!") flash.Store(&c.Controller) return } if err = models.IsValid(u); err != nil { flash.Error(err.Error()) flash.Store(&c.Controller) return } id, err := lib.SignupUser(u) if err != nil || id < 1 { flash.Warning(err.Error()) flash.Store(&c.Controller) return } flash.Success("Register user: %s", u.Email) flash.Store(&c.Controller) c.SetLogin(u) c.Redirect(c.UrlFor("UsersController.Index"), 303) }
- Ctrl Login,Logout部分
とくに変わったことなし, URL参照: beego-samples/login.go at master · ikeikeikeike/beego-samples · GitHub
次,リクエスト時にログインしているか事前に確認するようにする
Filtersを使用: Filters - beego: simple & powerful Go app framework
beego.InsertFilter("/", beego.BeforeRouter, func(ctx *context.Context) { _, ok := ctx.Input.Session("userinfo").(int64) if !ok { ctx.Redirect(302, "/login") } }) beego.Router("/", &ctl.UsersController{}, "get:Index") beego.Router("/login", &ctl.LoginController{}, "get,post:Login") beego.Router("/logout", &ctl.LoginController{}, "get:Logout") beego.Router("/signup", &ctl.LoginController{}, "get,post:Signup")
Prepareをoverwriteすることでも実現できる: Controller funcs - beego: simple & powerful Go app framework
func (c *UsersController) Prepare() { if !c.IsLogin { c.Ctx.Redirect(302, c.LoginPath()) return } }
以上どす
上述の例を少しだけ拡張したサンプルソースコード
beego ormとdjango orm
Beego ORMはDjango ORMは影響されているということらしいので
http://beego.me/docs/mvc/model/query.md#advanced-queries の内容をDjangoORMに置き換えようと思ってたけど途中で疲れて終わらしてます (またリンクのみの箇所は置き換える必要がないほど似ているため面倒で書いていません)
Basic Usage: Advanced Queries
Go
o := orm.NewOrm() // Get a QuerySeter object. User is table name qs := o.QueryTable("user") // Can also use object as table name user := new(User) qs = o.QueryTable(user) // return a QuerySeter
Python
from django.contrib.auth import models qs = models.User.objects.get_queryset()
expr
Go
qs.Filter("id", 1) // WHERE id = 1 qs.Filter("profile__age", 18) // WHERE profile.age = 18 qs.Filter("Profile__Age", 18) // key name and field name are both valid qs.Filter("profile__age", 18) // WHERE profile.age = 18 qs.Filter("profile__age__gt", 18) // WHERE profile.age > 18 qs.Filter("profile__age__gte", 18) // WHERE profile.age >= 18 qs.Filter("profile__age__in", 18, 20) // WHERE profile.age IN (18, 20) qs.Filter("profile__age__in", 18, 20).Exclude("profile__lt", 1000) // WHERE profile.age IN (18, 20) AND NOT profile_id < 1000
Python
qs.filter(id=1) # WHERE id = 1 qs.filter(profile__age=18) # WHERE profile.age = 18 qs.filter(Profile__Age=18) # key name and field name are both valid qs.filter(profile__age=18) # WHERE profile.age = 18 qs.filter(profile__age__gt=18) # WHERE profile.age > 18 qs.filter(profile__age__gte=18) # WHERE profile.age >= 18 qs.filter(profile__age__in=[18, 20]) # WHERE profile.age IN (18, 20) qs.filter(profile__age__in=[18, 20]).exclude(profile__lt=1000) # WHERE profile.age IN (18, 20) AND NOT profile_id < 1000
Operators
exact
Go
qs.Filter("name", "slene") // WHERE name = 'slene' qs.Filter("name__exact", "slene") // WHERE name = 'slene' // using = , case sensitive or not is depending on which collation database table is used qs.Filter("profile", nil) // WHERE profile_id IS NULL
Python
qs.filter(name="slene") # WHERE name = 'slene' qs.filter(name__exact="slene") # WHERE name = 'slene' # using = , case sensitive or not is depending on which collation database table is used qs.filter(profile=None) # WHERE profile_id IS NULL
Advanced Query API
SetCond
Go
cond := NewCondition() cond1 := cond.And("profile__isnull", false).AndNot("status__in", 1).Or("profile__age__gt", 2000) qs := orm.QueryTable("user") qs = qs.SetCond(cond1) // WHERE ... AND ... AND NOT ... OR ... cond2 := cond.AndCond(cond1).OrCond(cond.And("name", "slene")) qs = qs.SetCond(cond2).Count() // WHERE (... AND ... AND NOT ... OR ...) OR ( ... )
Python
from django.db.models import Q cond1 = Q(profile__isnull=False) & ~Q(status__in=[1]) | Q(profile__age__gt=2000) from django.contrib.auth import models qs = models.User.objects.filter(cond1) cond2 = Q(cond1) | Q(name="slene") qs.filter(cond2).count() # Output number
All
Go
var users []*User num, err := o.QueryTable("user").Filter("name", "slene").All(&users) fmt.Printf("Returned Rows Num: %s, %s", num, err)
Python
users = models.User.objects.filter(name="slene") print("Returned Rows Num: %s, %s" % (len(users), None))
Go
type Post struct { Id int Title string Content string Status int } // Only return Id and Title var posts []Post o.QueryTable("post").Filter("Status", 1).All(&posts, "Id", "Title")
Python
class Post(models.Model): title = models.CharField(null=False, max_length=255) content = models.CharField(null=False, max_length=255) status = models.IntegerField(null=False) class Meta: db_table = 'post' # Only return Id and Title Post.objects.filter(status=1).values("id", "title")
One
Go
var user User err := o.QueryTable("user").Filter("name", "slene").One(&user) if err == orm.ErrMultiRows { // Have multiple records fmt.Printf("Returned Multi Rows Not One") } if err == orm.ErrNoRows { // No result fmt.Printf("Not row found") }
Python
try: user = User.objects.get(name="slene") except User.MultipleObjectsReturned as err: # Have multiple records print("Returned Multi Rows Not One") except User.DoesNotExist as err: # No result print("Not row found")
Relational Query
User and Profile is OnToOne relation
Go
user := &User{Id: 1} o.Read(user) if user.Profile != nil { o.Read(user.Profile) }
Python
user = User.objects.get(id=1) if user.profile: pass
Go
user := &User{} o.QueryTable("user").Filter("Id", 1).RelatedSel().One(user) // Get Profile automatically fmt.Println(user.Profile) // Because In Profile we defined reverse relation User, Profile's User is also auto assigned. Can directly use: fmt.Println(user.Profile.User)
Python
user = User.objects.get(id=1) println(user.profile.user)
Post and User are ManyToOne relation. i.e.: ForeignKey is User
Go
type Post struct { Id int Title string User *User `orm:"rel(fk)"` Tags []*Tag `orm:"rel(m2m)"` }
Python
class Post(models.Model): title = models.CharField(null=False, max_length=255) user = models.ForeignKey("User", related_name='posts', null=False) tags = models.ManyToManyField("Tag", related_name='posts', null=False) class Meta: db_table = 'post'
Go
var posts []*Post num, err := o.QueryTable("post").Filter("User", 1).RelatedSel().All(&posts) if err == nil { fmt.Printf("%d posts read\n", num) for _, post := range posts { fmt.Printf("Id: %d, UserName: %d, Title: %s\n", post.Id, post.User.UserName, post.Title) } }
Python
posts = Post.objects.filter(user=1) if posts: print("%d posts read\n" % len(posts)) for post in posts: print("Id: %d, UserName: %d, Title: %s\n" % (post.id, post.user.username, post.title))
Go
var user User err := o.QueryTable("user").Filter("Post__Title", "The Title").Limit(1).One(&user) if err == nil { fmt.Printf(user) }
Python
try: user = User.objects.get(posts__title="The Title") print(user) except User.DoesNotExist: pass
Post and Tag are ManyToMany relation
Djangoぽく書いた場合
Go
type Post struct { Id int Title string User *User `orm:"rel(fk)"` Tags []*Tag `orm:"rel(m2m)"` }
type Tag struct { Id int Name string Posts []*Post `orm:"reverse(many)"` }
Python
class Post(models.Model): title = models.CharField(null=False, max_length=255) user = models.ForeignKey("User", related_name='posts', null=False) tags = models.ManyToManyField("Tag", related_name='posts', null=False) class Meta: db_table = 'post'
class Tag(models.Model): name = models.CharField(null=False, max_length=255) class Meta: db_table = 'tag'
Railsぽく書いた場合
中間テーブルでIndexとか自由に付けられる
Go
type Post struct { Id int64 `orm:"auto"` Title string `orm:"size(255)"` User *User `orm:"rel(fk);index"` Tags []*Tag `orm:"rel(m2m);index;rel_through(example.com/ikeikeikeike/unk/models.PostTag)"` }
type PostTag struct { Id int64 `orm:"auto"` Tag *Tag `orm:"rel(fk);index"` Post *Post `orm:"rel(fk);index"` }
type Tag struct { Id int64 Name string Posts []*Post `orm:"reverse(many)"` }
Ruby(On Rails)
class Post < ActiveRecord::Base belongs_to :user, touch: true has_many :post_tags has_many :tags, through: :post_tags end
class PostTag < ActiveRecord::Base belongs_to :post belongs_to :tag end
class Tag < ActiveRecord::Base has_many :post_tags has_many :posts, through: :post_tags end
Go
var posts []*Post num, err := dORM.QueryTable("post").Filter("Tags__Tag__Name", "golang").All(&posts)
var tags []*Tag num, err := dORM.QueryTable("tag").Filter("Posts__Post__Title", "Introduce Beego ORM").All(&tags)
Python
Post.objects.filter(tags__tag__name="golang")
Tag.objects.filter(posts__post__title="Introduce Beego ORM")
Ruby(On Rails)
Post.joins(:tags).where("tags.name" => "golang")
Tag.joins(:posts).where("posts.title" => "Introduce Beego ORM")
Load Related Field
Handling ManyToMany relation
Go
o := orm.NewOrm() post := Post{Id: 1} m2m := o.QueryM2M(&post, "Tags") // In the first param object must have primary key // The second param is the M2M field will work with // API of QueryM2Mer will used to Post with id equals 1
Python
post = Post.objects.get(id=1) m2m = post.tags
QueryM2Mer Add
Go
tag := &Tag{Name: "golang"} o.Insert(tag) num, err := m2m.Add(tag) if err == nil { fmt.Println("Added nums: ", num) }
Python
tag = Tag(name="golang") tag.save() m2m.add(tag) # return None
TODO: 各例に一言説明文を書くように努力する
続きはまた明日
Ubuntu 14.04 LTS Trusty Tahrにおける各種ulimit設定
この間プライベートのサーバーが too many open files になっていたので設定した備忘録的なもの
Nginx
/etc/default/nginx
ULIMIT="-n 65536"
Redis
/etc/default/redis-server
ULIMIT=65536
Memcached
/etc/memcached.conf
-c 65536
PAM認証系
/etc/security/limits.conf
アスタリスクorユーザー名 soft nofile 65536 アスタリスクorユーザー名 hard nofile 65536
Sysvinit系
/etc/initscript
ulimit -n 65536 eval exec "$4"
Upstart系
/etc/init/任意の名称.conf
limit nofile 65536 65536 #limit memlock 82000 82000
systemdは使ってないので無し
それとupstartで共通の書き方はないものか
追記
Ansibleだとこう書いた added ulimit role · a972eb1 · ikeikeikeike/ansible-playbooks · GitHub
どーしてもPEP8を守れない人用のDjango1.7の設定
以下ネタです。基本的にPEP8は準拠しませう また使用しているDjango1.7の新機能Applications, System check frameworkの使用方法は全くもって正しくないので真似しないで下さい
Django1.7おめでとうございます!!!
Django1.7がリリースしてめでたかったので新しく1.7から追加された機能ApplicationsとSystem check frameworkの仕組みを利用して強制的にPEP8を守らしてみました。
まずDjangoのappをつくりましょう。checkpep8でよいですかね?
$ python manage.py startapp checkpep8
出来上がったcheckpep8アプリの__init__.pyにdefault_app_configを追加
checkpep8/__init__.py
default_app_config = 'checkpep8.apps.Checkpep8Config'
apps.pyを作成、下記の内容を書いておく(予めsettingsにはcheckpep8を登録しておきましょう)
checkpep8/apps.py
import sys import pep8 from cStringIO import StringIO from django import apps from django.core import checks from django.conf import settings from django.utils.translation import ugettext_lazy as _ def capture(func, *args, **kwargs): out, sys.stdout = sys.stdout, StringIO() func(*args, **kwargs) sys.stdout.seek(0) res = sys.stdout.read() sys.stdout = out return res def checker(**kwargs): app_names = ( app_name for app_name in settings.INSTALLED_APPS if not app_name.startswith('django') ) app_paths = [ apps.AppConfig.create(app_name).path for app_name in app_names ] errors = [] outs = capture(pep8.StyleGuide().check_files, app_paths) for out in outs.splitlines(): errors.append( checks.Error( out, hint=None, obj=None, id='pep8.%s' % out.split(' ')[1], ) ) return errors class Checkpep8Config(apps.AppConfig): name = 'checkpep8' verbose_name = _("Checkpep8") def ready(self): checks.register('pep8')(checker)
pep8に引っかかるようにcheckpep8/models.pyを編集
from django.db import models class First(models.Model): def pep(self): return def too_many_blank_linespep(self): return
現在のProjectの状態
$ tree
.
├── checkpep8
│ ├── __init__.py
│ ├── admin.py
│ ├── apps.py
│ ├── models.py
│ ├── tests.py
│ ├── urls.py
│ └── views.py
├── manage.py
└── runserver_pep8
├── __init__.py
├── settings.py
├── urls.py
└── wsgi.py
いざmakemigrations
$ python manage.py makemigrations checkpep8 CommandError: System check identified some issues: ERRORS: ?: (pep8.E303) /Users/ikeda/.virtualenvs/django17/blog/runserver_pep8/checkpep8/models.py:13:5: E303 too many blank lines (3)
やりました!! System check framework が効いている。。。 pep8チェックで makemigrations 動かない!!
models.pyを直してみる
from django.db import models class First(models.Model): def pep(self): return def too_many_blank_linespep(self): return
再度 makemigration
$ python manage.py makemigrations checkpep8 Migrations for 'checkpep8': 0001_initial.py: - Create model First
おおお、makemigrationsがうごいたー
これでpep8準拠てか、強制の方向へ。。だれかautopep8でも試してみてください〜。
管理画面を提供しているWebFWに対応するVarnish vclの書き方
sub vcl_hash { # Cookie毎にキャッシュ hash_data(req.http.cookie); # Device毎にキャッシュ hash_data(req.http.X-Device); # HTML, JS, CSS毎にキャッシュ hash_data(req.http.Accept); # URL毎にキャッシュ hash_data(req.url); if (req.http.host) { hash_data(req.http.host); } else { hash_data(server.ip); } return (hash); }
Varnishはvcl_hashで生成されたhash毎にキャッシュ生成するようなのでキャッシュしたい区別したい項目を列挙
管理画面なので今回はreq.http.cookieをhash_dataに入れておく。以上で私の場合はOKだった。
もちろん vcl_recv
内では req.request POST、req.http.Authorizationなどは return (pass);
しています。
ansible-playbooks/default.vcl.j2 at master · ikeikeikeike/ansible-playbooks · GitHub