@ka2n

Technology and beer

ブログサービスからPingを受け取る

2018年になってもブログサービスからPingで更新通知を受け取っています。。 XML-RPCWordPress等のブログから更新通知を受け取れるのですがまた探すかもしれないのでメモ。

weblogUpdates.ping

詳細は weblogUpdates.extendedPing を参照

<?xml version="1.0" encoding="UTF-8"?>
<methodCall>
   <methodName>weblogUpdates.ping</methodName>
   <params>
      <param>
         <value>Someblog</value>
      </param>
      <param>
         <value>https://example.com/someblog</value>
      </param>
   </params>
</methodCall>

weblogUpdates.extendedPing

パラメーター

  1. サイト名
  2. ウェブサイトのURLもしくはRSSフィードのURL
  3. (weblogUpdates.pingではOptional) ページのURL
  4. (weblogUpdates.pingではOptional) RSS/RDF/AtomフィードのURL
  5. (Optional) カテゴリーまたはタグ名, |区切り
<?xml version="1.0" encoding="UTF-8"?>
<methodCall>
   <methodName>weblogUpdates.extendedPing</methodName>
   <params>
      <param>
         <value>Someblog</value>
      </param>
      <param>
         <value>https://example.com/someblog</value>
      </param>
      <param>
         <value>https://example.com/someblog/entries/1111</value>
      </param>
      <param>
         <value>https://example.com/someblog/feed.rss</value>
      </param>
      <param>
         <value>personal|friends</value>
      </param>
   </params>
</methodCall>

WordPressからはweblogUpdates.pingweblogUpdates.extendedPingが同時に送られてくるのを見たことがある。

出典:

広告を非表示にする

LinuxからSoftEther VPNサーバ(SSL VPN)へ接続したあとの使い方

接続した後にどうするのかわからなくて苦戦したのでメモ。

環境は

  • クライアント側: SoftEther VPN Version 4.25 Build 9656, Linux healthy 4.15.18-1-MANJARO #1 SMP PREEMPT Thu Apr 19 09:05:40 UTC 2018 x86_64 GNU/Linux
  • サーバ側: GithubのSoftEtherVPN_Stableのmaster

目的: 設定済みのSoftEther VPNサーバへvpnazure.net経由でNATを超えて接続する。ゲートウェイにはせずにVPN内のホストにIPアドレス直打ちでアクセスしたい。

SSL VPNで、サーバ側はNATの中にある状態。なんと、vpnazure.netが使える。(SSTP専用かと思いきや) vpncmdでアカウントを設定しておきます。ホスト名にvpnazure.netのホスト名を使用する。(素晴らしい) 仮想NIC名はVPNで作成。

VPNへ接続

SoftEther VPN Clientで接続

$ vpncmd /client localhost /CMD AccountConnect <アカウント名>

IPアドレスを割り当てる。デフォルトゲートウェイには追加せず、/etc/resolv.confも書き換えないようにする。

# ip link set vpn_vpn down
# ip link set vpn_vpn up
# dhcpcd -G -C resolv.conf vpn_vpn
# ip route add 192.168.100.0/24 via 192.168.30.1 dev vpn_vpn # VPN経由でアクセスしたいネットワークを指定

以上

VPNから切断

SoftEther VPN Clientで切断

$ vpncmd /client localhost /CMD AccountDisconnect <アカウント名>

IPアドレスの割り当てを削除

# ip link set vpn_vpn down
# ip link set vpn_vpn up
# dhcpcd -k vpn_vpn
広告を非表示にする

Go言語で埋め込みをしたstructでのjson.UnmarshalJSON

goを書いていて、部分実装やゆるふわなJSONを扱うためにstructの埋め込みを使うことがある。 そのstructに対してJSONを読み込む時に少しハマった事があるのでメモ。

まずはじめに、こんな具合でJSNONをOuterマッピングしていた。

https://play.golang.org/p/zGo779zhxI

package main

import (
    "encoding/json"
    "fmt"
)

type Outer struct {
    InnerA
    InnerB
}

type InnerA struct {
    FieldA string `json:"a_field"`
}

type InnerB struct {
    FieldB string `json:"b_field"`
}

func main() {
    var v Outer
    data := []byte(`{"a_field": "A", "b_field": "B"}`)
    if err := json.Unmarshal(data, &v); err != nil {
        fmt.Println("[Error] " + err.Error())
        return
    }
    fmt.Printf("%+v", v)
        // {InnerA:{FieldA:A} InnerB:{FieldB:B}}
}

開発中にJSONb_fieldが文字でないfalseを返す場合があることが分かってしまったので以下の様に対応した。

https://play.golang.org/p/0WszSKVN0m

package main

import (
    "encoding/json"
    "fmt"
)

type Outer struct {
    InnerA
    InnerB
}

type InnerA struct {
    FieldA string `json:"a_field"`
}

type InnerB struct {
    FieldB string `json:"b_field"`
}

func (e *InnerB) UnmarshalJSON(data []byte) error {
    var v struct {
        FieldB interface{} `json:"b_field"`
    }
    if err := json.Unmarshal(data, &v); err != nil {
        return nil
    }

    switch v.FieldB.(type) {
    case string:
        break
    default:
        return nil
    }

    e.FieldB = v.FieldB.(string)
    return nil
}

func main() {
    var v Outer
    data := []byte(`{"a_field": "A", "b_field": "B"}`)
    if err := json.Unmarshal(data, &v); err != nil {
        fmt.Println("[Error] " + err.Error())
        return
    }
    fmt.Printf("%+v", v)
        // => {InnerA:{FieldA:} InnerB:{FieldB:B}}
}

これで上手くいくと思いきや、InnerAのフィールドがゼロ値になってしまっている。 そこで、面倒だけどOuterでそれぞれUnmarshalすることでなんとかなった。

https://play.golang.org/p/kii9ZmoX1O

package main

import (
    "encoding/json"
    "fmt"
)

type Outer struct {
    InnerA
    InnerB
}

type InnerA struct {
    FieldA string `json:"a_field"`
}

type InnerB struct {
    FieldB string `json:"b_field"`
}

func (e *Outer) UnmarshalJSON(data []byte) error {
    var err error
    err = json.Unmarshal(data, &e.InnerA)
    if err != nil {
        return err
    }
    err = json.Unmarshal(data, &e.InnerB)
    if err != nil {
        return err
    }
    return nil

}

func (e *InnerB) UnmarshalJSON(data []byte) error {
    var v struct {
        FieldB interface{} `json:"b_field"`
    }
    if err := json.Unmarshal(data, &v); err != nil {
        return nil
    }

    switch v.FieldB.(type) {
    case string:
        break
    default:
        return nil
    }

    e.FieldB = v.FieldB.(string)
    return nil
}

func main() {
    var v Outer
    data := []byte(`{"a_field": "A", "b_field": "B"}`)
    if err := json.Unmarshal(data, &v); err != nil {
        fmt.Println("[Error] " + err.Error())
        return
    }
    fmt.Printf("%+v", v)
}

理由は後で調べる。

広告を非表示にする

WordPress ACF to REST APIで関連記事のアイキャッチ画像を含める

あるサービスの一部がライターさんに直接使ってもらいたいという理由でバックエンドにWordPressを使っている。 最近のWordPressは良くできていて標準でREST APIが叩けるので別のシステムから扱うのが随分楽になった。

別システムから使うため、いろいろな設定項目をWordPress側から変更できるようにしていて、それを実現するためにAdvanced Custom Fields(以下ACF)というプラグインを導入してカスタムフィールドとして本文やタイトル以外の設定ができるようにしている。界隈では結構有名だったと思う。 ただ、ACFを入れるだけではREST APIにカスタムフィールドが出てこないので、さらにACF to REST APIというプラグインも導入している。

ACFではある投稿に対して、別の投稿を紐付けるカスタムフィールドを定義することができ、relationsと呼ばれる。 REST APIである投稿を取得すると、acfというキーで各種カスタムフィールドがまとめられて返ってくるのだが、そこにはアイキャッチ画像が無い。 本体の記事はfeatured_mediaというキーでIDが取得でき、GETパラメータに_embedを含めるとレスポンスの_embedded.wp:featuredmediaに画像の詳細が含まれてレスポンスされ、その中に各サイズごとにサイズ, URLなどが含まれる。 そこで、ACF to REST APIのフックを利用して、関連記事に対しても_embedで画像の情報を含める事ができるようにした。

add_filter( 'acf/rest_api/post/get_fields', 'includeACFFieldsInRESTRelation', 10, 3);

function includeACFFieldsInRESTRelation( $data, $request, $response ) {
    if ( $response instanceof WP_REST_Response ) {
        $data = $response->get_data();
    }
  
    remove_filter('acf/rest_api/post/get_fields', 'includeACFFieldsInRESTRelation', 10);
    if (!empty($data)) {
        array_walk_recursive($data, 'shallowIncludeACFFields', array('post'));
    }
    add_filter( 'acf/rest_api/post/get_fields', 'includeACFFieldsInRESTRelation', 10, 3);

    return $data;
}

function shallowIncludeACFFields( &$item, $key, $postTypes, $level=0, $post ) {
    if (isset( $item->post_type ) && in_array( $item->post_type, $postTypes )) {

        // アイキャッチ画像が設定されていればIDを`featured_media`に入れる
        if($media_id = get_post_thumbnail_id($item->ID)) {
            $item->featured_media = intval($media_id, 10);        
        }

        // `_embed`の場合は`_embedded`内に画像のメタデータを入れる
        if(isset($_GET['_embed']) && isset($item->featured_media)) {
            $media = get_post($media_id);
            $server = rest_get_server();
            $controller = new WP_REST_Posts_Controller($media->post_type);
            $post_type = get_post_type_object($media->post_type);
            $request = WP_REST_Request::from_url(rest_url(sprintf('wp/v2/%s/%d', $post_type->rest_base, $media->ID)));
            $request['context'] = 'embed';
            $response = $server->dispatch($request);
            $response = apply_filters('rest_post_dispatch', rest_ensure_response($response), $server, $request);
            $item->_embedded = array();
            $item->_embedded['wp:featuredmedia'] = array();
                $item->_embedded['wp:featuredmedia'][] = $server->response_to_data($response, isset($_GET['_embed']));  
        }


        // ACFのフィールドも入れる
        if($fields = get_fields($item->ID)) {
            foreach($fields as $key => $value) {
                $item->acf = $fields;
            }
        }
    }
}

広告を非表示にする