url-retrieve-synchronicallyのデバッグについてでurl-retrieve-synchronouslyのデバッグについて書きました。url-debugという変数にtを入れることで *URL-DEBUG* というバッファにログが出力されるようになりました。url-retrieve-synchronouslyは内部でurl-retrieveを使っていて、これを使っているものは同様にデバッグできます。

Emacsのパッケージ管理のライブラリであるpackage.elもurl-retrieveを内部で使ってindex情報の取得やパッケージのダウンロードを行っているので同様にログから挙動の確認をできます。

ということで今回はpackage.elでどんなリクエストが送信されているのかを確認します。

準備

まず必要なライブラリ(package.elとurl.el)をrequireで読み込んでおきます。そしてurl-debugにはtを設定します。

(require 'package)
(require 'url)

(setq url-debug t)

インデックス情報の取得

(package-refresh-contents)を評価するとpackage-archivesに登録されたリポジトリからパッケージ情報を取得します。まずはその挙動を確認します。

M-x package-refresh-contents とやればいいのですが、それだとpackage-archivesに登録されたリポジトリ全てからパッケージ情報を取ってきてしまいます。そうするとログがたくさん出て見にくいので今回は一つのリポジトリだけを対象にします。

package–download-and-read-archivesはpackage-refresh-contentsの内部で使っている関数ですがここでdolistを使って一つずつリポジトリからインデックス情報を取得しているようです。

(defun package--download-and-read-archives (&optional async)
  "Download descriptions of all `package-archives' and read them.
This populates `package-archive-contents'.  If ASYNC is non-nil,
perform the downloads asynchronously."
  ;; The downloaded archive contents will be read as part of
  ;; `package--update-downloads-in-progress'.
  (dolist (archive package-archives)
    (cl-pushnew archive package--downloads-in-progress
                :test #'equal))
  (dolist (archive package-archives)
    (condition-case-unless-debug nil
        (package--download-one-archive archive "archive-contents" async)
      (error (message "Failed to download `%s' archive."
               (car archive))))))

一つのリポジトリだけを対象にするならpackage–download-one-archiveをやれば良いことがわかります。これを使ってelpa.gnu.orgからインデックス情報を取得してみます。

(package--download-one-archive '("gnu" . "https://elpa.gnu.org/packages/")
                               "archive-contents" t)

出力は次のようになります。長いので結果は省略/加工してあります。

cdhttp -> Contacting host: elpa.gnu.org:443
http -> Marking connection as busy: elpa.gnu.org:443 #<process elpa.gnu.org>
http -> getting referer from buffer: buffer:#<buffer  *temp*-882153> target-url:#s(url "https" nil nil "elpa.gnu.org" nil "/packages/archive-contents" nil nil t silent t t) lastloc:nil
http -> Request is:
GET /packages/archive-contents HTTP/1.1
MIME-Version: 1.0
Connection: keep-alive
Extension: Security/Digest Security/SSL
Host: elpa.gnu.org
Accept-encoding: gzip
Accept: */*
User-Agent: URL/Emacs Emacs/27.0.50 (OpenStep; x86_64-apple-darwin18.2.0)


http -> Calling after change function `url-http-wait-for-headers-change-function' for `#<process elpa.gnu.org>'
http -> url-http-wait-for-headers-change-function ( *http elpa.gnu.org:443*)

〜省略〜

http -> Found existing connection: elpa.gnu.org:443 #<process elpa.gnu.org>
http -> Reusing existing connection: elpa.gnu.org:443
http -> Marking connection as busy: elpa.gnu.org:443 #<process elpa.gnu.org>
http -> getting referer from buffer: buffer:#<buffer  *temp*-440903> target-url:#s(url "https" nil nil "elpa.gnu.org" nil "/packages/archive-contents.sig" nil nil t silent t t) lastloc:nil
http -> Request is:
GET /packages/archive-contents.sig HTTP/1.1
MIME-Version: 1.0
Connection: keep-alive
Extension: Security/Digest Security/SSL
Host: elpa.gnu.org
Accept-encoding: gzip
Accept: */*
User-Agent: URL/Emacs Emacs/27.0.50 (OpenStep; x86_64-apple-darwin18.2.0)


http -> Calling after change function `url-http-wait-for-headers-change-function' for `#<process elpa.gnu.org>'
http -> Marking connection as free: elpa.gnu.org:443 #<process elpa.gnu.org>

〜省略〜

 (ace-window .
	     [(0 9 0)
	      ((avy
		(0 2 0)))
	      \"Quickly switch windows.\" single
	      ((:url . \"https://github.com/abo-abo/ace-window\")
	       (:keywords \"window\" \"location\"))])
 (ack .
      [(1 8)
       nil \"interface to ack-like tools\" tar
       ((:keywords \"tools\" \"processes\" \"convenience\")
	(:url . \"https://github.com/leoliu/ack-el\"))])
 (ada-mode .
	   [(6 1 0)
	    ((wisi
	      (2 1 0))
	     (cl-lib
	      (1 0))
	     (emacs
	      (25 0)))
	    \"major-mode for editing Ada sources\" tar
	    ((:keywords \"languages\" \"ada\")
	     (:url . \"http://www.nongnu.org/ada-mode/\"))])
 (ada-ref-man .
	      [(2012 5)
	       nil \"Ada Reference Manual 2012\" tar
	       ((:keywords \"languages\" \"ada\")
		(:url . \"http://stephe-leake.org/ada/arm.html\"))])
 (adaptive-wrap .
		[(0 7)
		 nil \"Smart line-wrapping with wrap-prefix\" single
		 ((:url . \"http://elpa.gnu.org/packages/adaptive-wrap.html\")
		  (:keywords))])
 (adjust-parens .
		[(3 0)
		 nil \"Indent and dedent Lisp code, automatically adjust close parens\" tar
		 ((:url . \"http://elpa.gnu.org/packages/adjust-parens.html\"))])

〜省略〜

			     [(1 0 2)
 (yasnippet-classic-snippets .
			      ((yasnippet
				(0 9 1)))
			      \"\\\"Classic\\\" yasnippet snippets\" tar
			      ((:keywords \"snippets\")
			       (:url . \"http://elpa.gnu.org/packages/yasnippet-classic-snippets.html\"))])
 (zones .
	[(2019 4 30)
	 nil \"Zones of text - like multiple regions\" single
	 ((:url . \"https://elpa.gnu.org/packages/zones.html\")
	  (:keywords \"narrow\" \"restriction\" \"widen\" \"region\" \"zone\"))])
 (ztree .
	[(1 0 5)
	 ((cl-lib
	   (0)))
	 \"Text mode directory tree\" tar
	 ((:keywords \"files\" \"tools\")
	  (:url . \"https://github.com/fourier/ztree\"))]))
〜省略〜

(fn STATUS)"]  〜省略〜
http -> Spinning waiting for headers...

リクエストを2回送っています。1度目はhttps://elpa.gnu.org//packages/archive-contentsに対してGETでリクエストを送っ ています。これはインデックス情報そのもので次のような形式をしたデータです。

(パッケージ名 .
	[(バージョン番号)
	 nil \"パッケージ情報\" パッケージ形式
	 ((:url . \"パッケージのURL\")
	  (:keywords \"パッケージの\" \"キーワード\"))])

 ↑この形式が登録されたパッケージの個数分続く。

2度目はhttps://elpa.gnu.org/packages/archive-contents.sigにGETでリクエストを送っています。これは先ほどダウンロードしたファイルのシグネチャです。この値はpackage–check-sgnatureを使って検証しています。

パッケージのインストール

今回はcsv-modeをインストールします。先ほどの中でcsv-modeの情報はこのようになっています。

 (csv-mode .
	   [(1 7)
	    nil \"Major mode for editing comma/char separated values\" single
	    ((:url . \"http://elpa.gnu.org/packages/csv-mode.html\")
	     (:keywords \"convenience\"))])

それではpackage-installを使ってパッケージをインストールしてみます。

(package-install 'csv-mode)

*URL-DEBUG* の出力はこのようになっています。

http -> Contacting host: elpa.gnu.org:443
http -> Marking connection as busy: elpa.gnu.org:443 #<process elpa.gnu.org>
http -> getting referer from buffer: buffer:#<buffer  *temp*-83450> target-url:#s(url "https" nil nil "elpa.gnu.org" nil "/packages/csv-mode-1.7.el" nil nil t nil t nil) lastloc:nil
http -> Request is:
GET /packages/csv-mode-1.7.el HTTP/1.1
MIME-Version: 1.0
Connection: keep-alive
Extension: Security/Digest Security/SSL
Host: elpa.gnu.org
Accept-encoding: gzip
Accept: */*
User-Agent: URL/Emacs Emacs/27.0.50 (OpenStep; x86_64-apple-darwin18.2.0)


retrieval -> Spinning in url-retrieve-synchronously: nil (#<buffer  *http elpa.gnu.org:443*-877685>)
〜省略〜
retrieval -> Synchronous fetching done (#<buffer  *http elpa.gnu.org:443*-877685>)
http -> Found existing connection: elpa.gnu.org:443 #<process elpa.gnu.org>
http -> Reusing existing connection: elpa.gnu.org:443
http -> Marking connection as busy: elpa.gnu.org:443 #<process elpa.gnu.org>
http -> getting referer from buffer: buffer:#<buffer  *temp*-740429> target-url:#s(url "https" nil nil "elpa.gnu.org" nil "/packages/csv-mode-1.7.el.sig" nil nil t nil t nil) lastloc:nil
http -> Request is:
GET /packages/csv-mode-1.7.el.sig HTTP/1.1
MIME-Version: 1.0
Connection: keep-alive
Extension: Security/Digest Security/SSL
Host: elpa.gnu.org
Accept-encoding: gzip
Accept: */*
User-Agent: URL/Emacs Emacs/27.0.50 (OpenStep; x86_64-apple-darwin18.2.0)


retrieval -> Spinning in url-retrieve-synchronously: nil (#<buffer  *http elpa.gnu.org:443*-955551>)
http -> Calling after change function `url-http-wait-for-headers-change-function' for `#<process elpa.gnu.org>'
http -> url-http-wait-for-headers-change-function ( *http elpa.gnu.org:443*-955551)
〜省略〜
http -> Spinning waiting for headers...

今回もリクエストを2回送っています。1度目はhttps://elpa.gnu.org/packages/csv-mode-1.7.elにGETでリクエストを送っています。これはcsv-modeのemacs-lispのコードです。2度目はhttps://elpa.gnu.org//packages/csv-mode-1.7.el.sigにGETでリクエストを送っ ています。こちらは1回目で取得したデータのシグネチャです。

まとめ

Emacsのパッケージのダウンロードの挙動を確認しました。パッケージリポジトリがインデックス情報を更新する際にはarchive-contentsという名称でインデックス情報とシグネチャを取得してました。またインデックス情報に基づいてパッケージの実体とそのシグネチャを取得していることを確認しました。