【Golang】リダイレクトされた最終的な URL を取得する【Go 1.20】

> Go 言語（以下 golang）で URL をリクエストした際の<font color=darkorange>**最終的にリダイレクトされる先の URL** が知りたい</font>。

例えば、GitHub の Gist や Qiita では、ID だけを指定してリクエストするとコンテンツ・オーナーのユーザー名を付けた URL にリダイレクトされます。

```diff:転送前と転送後の URL
- https://gist.github.com/42f5f23053ab59ca480f480b8d01e1fd
+ https://gist.github.com/KEINOS/42f5f23053ab59ca480f480b8d01e1fd

- https://qiita.com/items/6f3bbc7f4682887c0b89
+ https://qiita.com/KEINOS/items/6f3bbc7f4682887c0b89
```
```shell-session:cURL によるリクエスト例
$ # リダイレクト先に /KEINOS が付加されている

$ # Gist@GitHub
$ curl -Ls -o /dev/null -w %{url_effective} https://gist.github.com/42f5f23053ab59ca480f480b8d01e1fd
https://gist.github.com/KEINOS/42f5f23053ab59ca480f480b8d01e1fd

$ # Items@Qiita
$ curl -Ls -o /dev/null -w %{url_effective} https://qiita.com/items/6f3bbc7f4682887c0b89
https://qiita.com/KEINOS/items/6f3bbc7f4682887c0b89
```

コンテンツもサルことながら、単純に golang で、この URL を取得したいのです。

[「"golang" リダイレクト 最終 URL 取得」でググって](https://www.google.com/search?q=%22golang%22+%E3%83%AA%E3%83%80%E3%82%A4%E3%83%AC%E3%82%AF%E3%83%88+%E6%9C%80%E7%B5%82+URL+%E5%8F%96%E5%BE%97)も、ソースが動かなかったり、`HEAD` メソッドを受け付けてないエラーが出たりしたので、自分のググラビリティとして。

## TL; DR <sub><sup>（今北産業）</sup></sub>

```go
import (
	"net/http"
	"time"
)

// FindRedirectURL はリダイレクトされた最終的な URL を返します。15 秒経過してもレスポンスが
// ない場合はタイムアウトでエラーを返します。
func FindRedirectURL(url string) (string, error) {
	client := &http.Client{
		Timeout: 15 * time.Second,
	}

	resp, err := client.Get(url)
	if err != nil {
		return "", err
	}

	finalURL := resp.Request.URL.String()

	return finalURL, nil
}
```
```go:ヘッダー情報だけで判断したい場合（軽量版）
import (
	"net/http"
	"time"
)

// FindRedirectURL はリダイレクトされた最終的な URL を返します。15 秒経過してもレスポンスが
// ない場合はタイムアウトでエラーを返します。
func FindRedirectURL(url string) (string, error) {
	client := &http.Client{
		Timeout: 15 * time.Second,
	}

	//resp, err := client.Get(url)
	resp, err := client.Head(url)
	if err != nil {
		return "", err
	}

	finalURL := resp.Request.URL.String()

	return finalURL, nil
}
```
```go:example.go
package main

import (
	"fmt"
	"log"
	"net/http"
	"time"
)

func FindRedirectURL(url string) (string, error) {
	client := &http.Client{
		Timeout: 15 * time.Second,
	}

	resp, err := client.Head(url)
	if err != nil {
		return "", err
	}

	finalURL := resp.Request.URL.String()

	return finalURL, nil
}

func main() {
    urlOriginal := "https://gist.github.com/42f5f23053ab59ca480f480b8d01e1fd"
    
    fmt.Println("Original:", urlOriginal)
    
	urlRedirect, err := FindRedirectURL(urlOriginal)
	if err != nil {
		log.Fatal(err)
	}

	fmt.Println("Redirect:", urlRedirect)
    // Output:
    // Original: https://gist.github.com/42f5f23053ab59ca480f480b8d01e1fd
    // Redirect: https://gist.github.com/KEINOS/42f5f23053ab59ca480f480b8d01e1fd
}
```

- [オンラインで動作を見る](https://paiza.io/projects/gNEtTbX7Oz-qLBcTQTsryg) @ paiza.IO

```shell-session:検証環境
$ date
2023年 5月15日 月曜日 12時26分29秒 JST

$ go version
go version go1.20.4 darwin/amd64

$ sw_vers
ProductName:	macOS
ProductVersion:	12.6.5
BuildVersion:	21G531
```

- 参考文献:
    - [Overview](https://pkg.go.dev/net/http#pkg-overview) | http | net @ pkg.go.dev
    - [「Go net/http タイムアウト」の完全ガイド](https://blog.cloudflare.com/ja-jp/the-complete-guide-to-golang-net-http-timeouts-ja-jp/) @ blog.cloudflare.com
    - [In golang, how to determine the final URL after a series of redirects?](https://stackoverflow.com/questions/16784419/in-golang-how-to-determine-the-final-url-after-a-series-of-redirects) @ StackOverflow
    - [Get final URL after curl is redirected](https://stackoverflow.com/a/3077316/18152508) @ StackOverflow