Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 12389] ファイルの概要

このコミットは、Go言語のドキュメンテーションツールであるgodocの表示に関するバグ修正です。具体的には、godocが生成するHTMLページ内で、ソースコードへのリンクに余分なスラッシュ(/)が含まれてしまい、ブラウザでのDNS解決エラーを引き起こす問題を解決しています。

コミット

commit 0eb4df0bc8e32fe68d7954055ee36e24e33dc15e
Author: Ugorji Nwoke <ugorji@gmail.com>
Date:   Mon Mar 5 15:36:33 2012 -0500

    cmd/godoc: remove extra / in paths
    
    If I click on links which should send you to source code (e.g. type, function, etc),
    the link is to //src/... (instead of /src/...).
    This causes a DNS resolution failure on the browser.
    
    Quick fix is to remove the leading / from package.html
    (since godoc.go src links automatically add a leading / as necessary).
    
    Fixes #3193.
    
    R=golang-dev, rsc
    CC=golang-dev
    https://golang.org/cl/5730059

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/0eb4df0bc8e32fe68d7954055ee36e24e33dc15e

元コミット内容

このコミットの目的は、godocが生成するHTMLページ内のソースコードへのリンクが誤って二重スラッシュ(//)で始まることによって発生する、ブラウザでのDNS解決エラーを修正することです。具体的には、型や関数などのソースコードへのリンクが//src/...の形式になってしまい、これがブラウザで正しく解釈されず、リンク切れやエラーの原因となっていました。

修正は、lib/godoc/package.htmlテンプレートファイルから、posLink_url関数によって生成されるパスの先頭にある余分なスラッシュを削除するというものです。これは、godoc.goがソースリンクを生成する際に、必要に応じて自動的に先頭のスラッシュを追加するため、package.html側で明示的に追加する必要がないという前提に基づいています。

この変更は、Issue #3193を修正するものであり、Gerritの変更リストhttps://golang.org/cl/5730059に関連しています。

変更の背景

Go言語の公式ドキュメンテーションツールであるgodocは、Goのソースコードから自動的にドキュメントを生成し、Webブラウザで閲覧可能な形式で提供します。このツールは、Goエコシステムにおいて非常に重要な役割を担っており、開発者がライブラリやパッケージのAPIを理解し、その実装を探索する上で不可欠です。

このコミットが行われた2012年当時、godocはまだ活発に開発されており、ユーザーからのフィードバックに基づいて機能改善やバグ修正が頻繁に行われていました。このバグは、ユーザーがgodocで表示されるドキュメントからソースコードへのリンクをクリックした際に、予期せぬエラーが発生するというものでした。具体的には、リンクのURLが//src/...のように二重スラッシュで始まってしまうため、多くのブラウザがこれを不正なURLとして解釈し、DNS解決に失敗したり、ページが見つからないというエラーを表示したりしていました。これはユーザーエクスペリエンスを著しく損なう問題であり、早急な修正が求められていました。

この問題の根本原因は、package.htmlテンプレート内でリンクを生成する際に、すでにgodoc.go側でパスの先頭にスラッシュが追加されることを考慮せずに、テンプレート側でも明示的にスラッシュを追加していたことにありました。結果として、パスが二重にスラッシュで始まってしまうという冗長な状態になっていたのです。

前提知識の解説

Go言語のgodocツール

godocは、Go言語のソースコードからドキュメントを生成し、Webブラウザで閲覧可能にするためのツールです。Goのパッケージ、関数、型、変数などの定義に付随するコメント(GoDocコメント)を解析し、整形されたHTMLドキュメントとして表示します。これにより、開発者はコードとドキュメントを密接に連携させ、常に最新のドキュメントを維持することができます。godocは、ローカルで実行して自身のプロジェクトのドキュメントを閲覧したり、pkg.go.devのような公式のGoパッケージサイトで公開されているドキュメントを生成したりするために利用されます。

HTMLテンプレートとパスの生成

Webアプリケーションでは、動的なコンテンツを生成するためにHTMLテンプレートがよく使用されます。Go言語では、html/templateパッケージなどがこれに該当します。テンプレートは、プレースホルダーや制御構造(条件分岐、ループなど)を含んだHTMLの骨格であり、実行時にデータが埋め込まれて最終的なHTMLが出力されます。

このコミットで関連するのは、package.htmlというテンプレートファイルです。このファイルは、Goのパッケージのドキュメントページを生成するためのテンプレートとして機能します。テンプレート内では、{{posLink_url .Decl $.FSet}}のような構文が使われており、これはposLink_urlという関数を呼び出して、ソースコードへのリンクURLを生成していることを示しています。

Webにおけるパス(URL)の指定方法には、絶対パスと相対パスがあります。

  • 絶対パス: ドメインのルートからの完全なパスを指定します。例えば、/src/pkg/foo/bar.goのように、常にスラッシュで始まります。
  • 相対パス: 現在のページのURLを基準としたパスを指定します。例えば、../bar.goのように、現在のディレクトリからの相対的な位置を示します。

この問題では、絶対パスの指定において、意図せず二重スラッシュが発生していました。

DNS解決

DNS(Domain Name System)は、インターネット上のドメイン名(例: google.com)をIPアドレス(例: 172.217.160.142)に変換するシステムです。WebブラウザがURLにアクセスする際、まずDNSを使ってドメイン名をIPアドレスに解決し、そのIPアドレスを持つサーバーに接続します。

URLのパス部分に//のような不正な形式が含まれている場合、ブラウザはそれをプロトコル相対URL(例: //example.com/path)として解釈しようとすることがあります。しかし、このバグのケースでは、//src/...のようにドメイン名がない状態で二重スラッシュが続くため、ブラウザはこれを有効なURLとして認識できず、DNS解決エラーや「ページが見つかりません」といったエラーを引き起こしていました。

Gerrit

Gerritは、Gitリポジトリに対するコードレビューと変更管理のためのWebベースのツールです。Googleによって開発され、Goプロジェクトのような大規模なオープンソースプロジェクトで広く利用されています。開発者は変更をGerritにアップロードし、他の開発者からのレビューを受けてから、最終的にリポジトリにマージされます。コミットメッセージに含まれるhttps://golang.org/cl/5730059は、この変更がGerrit上でレビューされた際の変更リスト(Change-List)のURLを示しています。

技術的詳細

このバグは、godocのWebサーバーがHTMLページを生成する際のパス構築ロジックの不整合に起因していました。

godocは、Goのソースコードを解析し、その構造(パッケージ、型、関数など)を表現するデータモデルを構築します。そして、このデータモデルをHTMLテンプレートに渡し、最終的なHTMLドキュメントを生成します。ソースコードへのリンクは、通常、godocが提供する内部的なパス生成ロジック(この場合はposLink_url関数)によって生成されます。

問題の箇所は、lib/godoc/package.htmlテンプレートファイル内にありました。このテンプレートは、関数や型の定義など、特定の要素のソースコードへのリンクを生成する際に、以下のようなHTML構造を使用していました。

<a href="/{{posLink_url .Decl $.FSet}}">{{$name_html}}</a>

ここで、posLink_url .Decl $.FSetは、ソースコード内の特定の宣言(Decl)の位置に基づいて、そのソースファイルへのパスを生成する関数呼び出しです。例えば、この関数が/src/pkg/foo/bar.go#L10のようなパスを返すとします。

しかし、godoc.gogodocの主要なロジックを実装しているファイル)の内部では、既に生成されるURLの先頭にスラッシュを追加する処理が行われていました。つまり、posLink_urlが返すパスは、既にルートからの絶対パスとして完全な形式(例: src/pkg/foo/bar.go#L10)であるか、あるいはgodoc.go側で先頭にスラッシュが追加されることを前提とした相対パス(例: src/pkg/foo/bar.go#L10)であるべきでした。

このコミットの作者のコメントによると、「godoc.goのソースリンクは必要に応じて自動的に先頭のスラッシュを追加する」とあります。これは、posLink_urlが返す値は先頭にスラッシュを含まない形式(例: src/pkg/foo/bar.go#L10)であり、godoc.go側でこれにスラッシュを追加して/src/pkg/foo/bar.go#L10という完全なURLを生成する、という設計意図があったことを示唆しています。

しかし、package.htmlテンプレートでは、posLink_urlの出力の前に明示的に/を追加していました。

<a href="/{{posLink_url .Decl $.FSet}}">

この結果、posLink_urlsrc/pkg/foo/bar.go#L10を返した場合、最終的なhref属性の値は//src/pkg/foo/bar.go#L10となってしまい、二重スラッシュが発生していました。

ブラウザは、//で始まるURLを「プロトコル相対URL」として解釈しようとします。これは、現在のページのプロトコル(http:またはhttps:)を使用して、そのプロトコルと二重スラッシュの後に続くドメイン名にアクセスしようとするものです。しかし、このケースでは//src/...のようにドメイン名が続くわけではないため、ブラウザはこれを不正なURLとして扱い、DNS解決エラーやファイルが見つからないというエラーを報告していました。

修正は単純で、package.htmlテンプレートから余分な先頭のスラッシュを削除することでした。

<a href="{{posLink_url .Decl $.FSet}}">

これにより、posLink_urlが返すパス(例: src/pkg/foo/bar.go#L10)に、godoc.go側で自動的に追加されるスラッシュが結合され、最終的に正しい形式のURL(例: /src/pkg/foo/bar.go#L10)が生成されるようになりました。

コアとなるコードの変更箇所

変更はlib/godoc/package.htmlファイルのみです。

--- a/lib/godoc/package.html
+++ b/lib/godoc/package.html
@@ -93,7 +93,7 @@
  		{{range .Funcs}}
  			{{/* Name is a string - no need for FSet */}}
  			{{$name_html := html .Name}}
-			<h2 id="{{$name_html}}">func <a href="/{{posLink_url .Decl $.FSet}}">{{$name_html}}</a></h2>
+			<h2 id="{{$name_html}}">func <a href="{{posLink_url .Decl $.FSet}}">{{$name_html}}</a></h2>
  			<pre>{{node_html .Decl $.FSet}}</pre>
  			{{comment_html .Doc}}
  			{{example_html .Name $.Examples $.FSet}}
@@ -101,7 +101,7 @@
  		{{range .Types}}
  			{{$tname := .Name}}
  			{{$tname_html := html .Name}}
-			<h2 id="{{$tname_html}}">type <a href="/{{posLink_url .Decl $.FSet}}">{{$tname_html}}</a></h2>
+			<h2 id="{{$tname_html}}">type <a href="{{posLink_url .Decl $.FSet}}">{{$tname_html}}</a></h2>
  			<pre>{{node_html .Decl $.FSet}}</pre>
  			{{comment_html .Doc}}
  
@@ -119,7 +119,7 @@
  
  			{{range .Funcs}}
  				{{$name_html := html .Name}}
-				<h3 id="{{$name_html}}">func <a href="/{{posLink_url .Decl $.FSet}}">{{$name_html}}</a></h3>
+				<h3 id="{{$name_html}}">func <a href="{{posLink_url .Decl $.FSet}}">{{$name_html}}</a></h3>
  				<pre>{{node_html .Decl $.FSet}}</pre>
  				{{comment_html .Doc}}
  				{{example_html .Name $.Examples $.FSet}}
@@ -127,7 +127,7 @@
  
  			{{range .Methods}}
  				{{$name_html := html .Name}}
-				<h3 id="{{$tname_html}}.{{$name_html}}">func ({{html .Recv}}) <a href="/{{posLink_url .Decl $.FSet}}">{{$name_html}}</a></h3>
+				<h3 id="{{$tname_html}}.{{$name_html}}">func ({{html .Recv}}) <a href="{{posLink_url .Decl $.FSet}}">{{$name_html}}</a></h3>
  				<pre>{{node_html .Decl $.FSet}}</pre>
  				{{comment_html .Doc}}
  				{{$name := printf "%s_%s" $tname .Name}}

この差分は、package.html内の4つの異なる箇所で、href属性の値の先頭から余分なスラッシュ(/)が削除されたことを示しています。

コアとなるコードの解説

変更された行はすべて、<a>タグのhref属性内でposLink_url関数が呼び出されている部分です。

元のコード:

<a href="/{{posLink_url .Decl $.FSet}}">{{$name_html}}</a>

修正後のコード:

<a href="{{posLink_url .Decl $.FSet}}">{{$name_html}}</a>

この変更のポイントは、posLink_url関数が返す値の前にあった/が削除されたことです。

  • .Decl: これは、GoのAST(Abstract Syntax Tree)における宣言(Declaration)を表すオブジェクトです。関数、型、変数などの定義が含まれます。
  • $.FSet: これは、ファイルセット(FileSet)を表すオブジェクトです。Goのソースコードのファイルと行番号のマッピング情報を含んでいます。

posLink_url関数は、これらの情報(どの宣言の、どのファイルセット内の、どの位置にあるか)を基に、そのソースコードへのパスを生成します。コミットメッセージにあるように、godoc.goのロジックが、このposLink_urlが返すパスの先頭に自動的にスラッシュを追加するようになっています。

したがって、package.htmlテンプレート側でさらにスラッシュを追加してしまうと、結果的に//という二重スラッシュが生成され、これがブラウザで不正なURLとして扱われる原因となっていました。

この修正により、package.htmlposLink_urlが返す「スラッシュなしのパス」(例: src/pkg/foo/bar.go#L10)をそのままhref属性に渡し、godoc.goが最終的に正しい「スラッシュありの絶対パス」(例: /src/pkg/foo/bar.go#L10)を生成するようになります。これにより、ブラウザはリンクを正しく解釈し、ソースコードへのナビゲーションが正常に行われるようになりました。

関連リンク

参考にした情報源リンク