更新時にFTP情報の入力を促されるのを改善

WordPress更新時にFTP情報の入力が必要と表示される

WordPressの更新通知が来ていたので更新しようとしたところ、以下のような文言の画面が表示されてしまった。
「要求されたアクションを実行するには、WordPress が Web サーバーにアクセスする必要があります。 次に進むには FTP の接続情報を入力してください。 接続情報が思い出せない場合は、ホスティング担当者に問い合わせてください。」
どうやら、パーミッションや所有者の問題で更新できないようである。

所有者の見直し

サーバはnginxなので、phpの実行時ユーザはnginxになる。そのため、WordPressディレクトリの所有者をnginxに。
パーミッションも問題なし。これで大丈夫のはずだが、なぜか直らず。

問題はSELinuxだった

正直、1時間以上はまった。どうみても所有者もパーミッションも大丈夫なのに更新できない。
ふと、phpでmkdirとかできるのだろうかと思い、実行したところ Permission Denied。なんかおかしい。
そして、やっと原因がわかった。問題はSELinuxだったのだ。以下の記事が答え。
PHPでPermission deniedが出てしまう(CentOS7)

chcon -R -t httpd_sys_content_rw_t wp
WordPressのフォルダに対して、以上のように実行して、解決した。

Node.jsでAES暗号化・復号化

Node.jsでAES暗号化・復号化する話。
今回は、AES256bitで行う。
cryptoモジュール使えば簡単にできるのだが、createCipherメソッドだと鍵と初期化ベクトルを指定できない。
代わりにcreateCipherivを使うことでそれらを指定できる。
以下コード。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var cryp = require("crypto");
var key = new Buffer('bFr3r5iRkiTWf3r-a8GsHVZUgpAtDL7X', 'utf8'); //鍵256bit
var iv = new Buffer('5xWAzpRh6TgybfGd', 'utf8');//初期化ベクトル128bit
var text = "あいうえお";
//暗号化
var cipher = cryp.createCipheriv('aes-256-cbc', key, iv);
var encoded = cipher.update(text, 'utf8', 'binary');
encoded += cipher.final('binary');
//復号化
var decipher = cryp.createDecipheriv('aes256', key, iv);
var decoded = decipher.update(encoded, 'binary', 'utf8');
decoded += decipher.final('utf8');
console.log(decoded); //あいうえお

go言語でリバースプロキシ

httputilのReverseProxyを使う

go言語でリバースプロキシを立てるには、httputilパッケージにあるReverseProxyを使うと簡単。
ReverseProxyはhttp.Handlerインタフェースを実装していて、http.ServerのHandlerに渡せる。
以下はlocalhost:9000のリクエストをlocalhost:9001に移譲する処理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package main
import (
"log"
"net/http"
"net/http/httputil"
)
func main() {
director := func(request *http.Request) {
request.URL.Scheme = "http"
request.URL.Host = ":9001"
}
rp := &httputil.ReverseProxy{Director: director}
server := http.Server{
Addr: ":9000",
Handler: rp,
}
if err := server.ListenAndServe(); err != nil {
log.Fatal(err.Error())
}
}

別ドメインへの移譲

あんまりないケースかもしれないが、localhost:9000のアクセスを別のドメインに移譲する場合は以下。
bodyやヘッダをそのままコピーして新しいhttpのリクエストを生成しなおす必要がある。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
package main
import (
"bytes"
"io/ioutil"
"log"
"net/http"
"net/http/httputil"
)
func main() {
director := func(request *http.Request) {
url := *request.URL
url.Scheme = "https"
url.Host = "kido0617.github.io"
buffer, err := ioutil.ReadAll(request.Body)
if err != nil {
log.Fatal(err.Error())
}
req, err := http.NewRequest(request.Method, url.String(), bytes.NewBuffer(buffer))
if err != nil {
log.Fatal(err.Error())
}
req.Header = request.Header
*request = *req
}
rp := &httputil.ReverseProxy{Director: director}
server := http.Server{
Addr: ":9000",
Handler: rp,
}
if err := server.ListenAndServe(); err != nil {
log.Fatal(err.Error())
}
}

参考

Golang Reverse Proxy
Reverse Proxy in Go
ichigo

go言語のmap、sliceのコピー

mapのコピー

go言語でmapは参照型のため、コピーするとき注意が必要。
以下にコードを例示する。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package main
import "fmt"
func main() {
m1 := make(map[string]int)
m1["a"] = 1
m1["b"] = 2
m2 := m1 //同じものを参照する
m2["c"] = 3
fmt.Println(m1) //map[a:1 b:2 c:3]
fmt.Println(m2) //map[a:1 b:2 c:3]
}

完全に別のmapとしてコピーにするには要素を全てコピーする必要がある。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package main
import "fmt"
func main() {
m1 := make(map[string]int)
m1["a"] = 1
m1["b"] = 2
m2 := make(map[string]int)
for key, value := range m1 {
m2[key] = value
}
m2["c"] = 3
fmt.Println(m1) //map[b:2 a:1]
fmt.Println(m2) //map[c:3 a:1 b:2]
}

sliceのコピー

sliceも同様に参照型であるが、コピーするときはcopyメソッドを使える。

1
2
3
4
5
6
7
8
9
10
11
12
package main
import "fmt"
func main() {
s1 := []int{1, 2, 3}
s2 := make([]int, len(s1))
copy(s2, s1)
s2 = append(s2, 4)
fmt.Println(s1) //[1 2 3]
fmt.Println(s2) //[1 2 3 4]
}

参考

Maps - Go Language

go言語でoauth2パッケージを使う

oauth2パッケージ

go言語でoauth2を扱うのに便利なoauth2パッケージがあります。
今回は、これを使ってみたいと思います。
例として、Googleフォト(Picasa)のAPIを扱います。
ソースは下記を参照。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
import (
"bufio"
"fmt"
"io/ioutil"
"log"
"os"
"golang.org/x/oauth2"
"golang.org/x/oauth2/google"
)
func main() {
//ClientIDやClientSecretはGoogle API Console からコピーしてきます。
//Endpointはgoogle、github、facebookなどがoauth2配下のパッケージに用意されています。
config := oauth2.Config{
ClientID: "xxxxxxxxxxx",
ClientSecret: "yyyyyyyyyyyy",
Endpoint: google.Endpoint,
RedirectURL: "urn:ietf:wg:oauth:2.0:oob", //今回はリダイレクトしないためこれ
Scopes: []string{"https://picasaweb.google.com/data/"}, //必要なスコープを追加
}
//認証のURLを取得。AuthCodeURLには文字列を渡す。CSRF攻撃の回避のため、本当はリダイレクトのコールバックで検証する。
url := config.AuthCodeURL("test")
fmt.Println(url) //認証のURLを表示。ブラウザにコピペする
//リダイレクト先がないため、ブラウザで認証後に表示されるコードを入力
var s string
var sc = bufio.NewScanner(os.Stdin)
if sc.Scan() {
s = sc.Text()
}
//アクセストークンを取得
token, err := config.Exchange(oauth2.NoContext, s)
if err != nil {
log.Fatalf("exchange error")
}
client := config.Client(oauth2.NoContext, token) //httpクライアントを取得
//取得したclientでGetする
resp, err := client.Get("https://picasaweb.google.com/data/feed/api/user/default?start-index=1")
if err != nil {
log.Fatalf("client get error")
}
//レスポンスを表示
defer resp.Body.Close()
byteArray, _ := ioutil.ReadAll(resp.Body)
fmt.Println(string(byteArray))
}

どこでoauth2のアクセストークンを扱っているか

上記のソースではトークンを取得した後、config.Client()でhttp Clientを取得してそれを使うだけで、oauth2の通信が行えています。
どこで、アクセストークンをセットし、トークンの期限が切れていたらリフレッシュトークンを使うなどの処理をしているか気になります。
ソースコードを見ていくと、config.Client()の先、NewClient()でhttp.Clientを作成しているのが見えます。
ここで、http.ClientのTransportに独自のTransportを設定しているのがそれっぽいです。

1
2
3
4
5
6
7
8
9
10
func NewClient(ctx context.Context, src TokenSource) *http.Client {
//省略
return &http.Client{
Transport: &Transport{
Base: internal.ContextTransport(ctx),
Source: ReuseTokenSource(nil, src),
},
}
}

このTransportですが、RoundTripというメソッドを持っています。この辺について詳しくは以下のページを参照。
Go http.RoundTripper 実装ガイド
RoundTrip内を見ると、トークンが切れていたらリフレッシュしたり、Authorizationヘッダにアクセストークンをつけているのが追えます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
func (t *Transport) RoundTrip(req *http.Request) (*http.Response, error) {
if t.Source == nil {
return nil, errors.New("oauth2: Transport's Source is nil")
}
token, err := t.Source.Token() //アクセストークンが切れていたらリフレッシュする
if err != nil {
return nil, err
}
req2 := cloneRequest(req)
token.SetAuthHeader(req2) //アクセストークンをAuthorizationヘッダに設定
t.setModReq(req, req2)
res, err := t.base().RoundTrip(req2)
if err != nil {
t.setModReq(req, nil)
return nil, err
}
res.Body = &onEOFReader{
rc: res.Body,
fn: func() { t.setModReq(req, nil) },
}
return res, nil
}