20210912のGoに関する記事は2件です。

【備忘】Go 言語の標準入力の基本的なこと

Go 言語の標準入力について、自分用の備忘として残しておきます。 どこにでもある一般的な内容です。 <参考サイト> ・【Golang】fmt.Scanとbufio.Scannerの速度比較 ・Go 言語で標準入力から読み込む競技プログラミングのアレ --- 改訂第二版 1. fmt ライブラリを使用した標準入力 まず、fmt.Scan を使用して読み込む方法です。 簡単に使用できますが、大容量の入力を読み場合にはスピードが遅くなるなどの弱点もあります。 1-1. 基本的なサンプル 次のように、fmt.Scan(&格納先の変数) とすれば、コンソールからの入力を取得できます。 & は変数のアドレスを表します。 main.go package main import "fmt" func main() { var str string fmt.Scan(&str) // データを格納する変数のアドレスを指定 fmt.Println(str) } ターミナル(コマンドプロンプト) C:\golang\sample>go build main.go C:\golang\sample>.\main 123 456 789 123 fmt.Scan を使用した場合、「改行」又は「スペース」までを1つの入力として読み込みます。 上記では、123 456 789 と入力していますが、最初のスペース区切りまでの 123 のみが取得されています。 1-2. スペース区切りの入力を読み込む場合 スペース区切りの入力を読み込むには、単純に、必要な回数分だけ読み込みを行います。 次のように、fmt.Scan の引数にカンマ区切りで変数を指定することで、複数の入力を受けることができます。 main.go package main import "fmt" func main() { var n [3]int fmt.Scan(&n[0], &n[1], &n[2]) fmt.Println(n) } ターミナル(コマンドプロンプト) C:\golang\sample>go build main.go C:\golang\sample>.\main 123 456 789 [123 456 789] 上記のサンプルは、スペース区切りで3つの値があることを前提としていますが、値の数が未知の場合の対応はできません(未知の場合は、次項の bufio を使用します)。 なお、ここでは、入力を受け付ける変数に、int 型を使用しています。 fmt.Scan では、入力値を望んだデータ型で受け取ることができますので、後でキャストする必要がなくなります。 <別の書き方> 必要な回数分だけ、入力を受け付ければ良いので、次のような書き方でも同じ結果を取得することができます。 複数行の入力を受け付ける場合も、これで対応できます。 main.go func main() { var n [3]int for i := 0; i < 3; i++ { fmt.Scan(&n[i]) } fmt.Println(n) } 2. bufio ライブラリを使用した標準入力 個人的には、この bufio ライブラリの方が使い勝手が良いです。 ただし、文字列型(string)で入力を取得するため、データ型を変更するなどの手間が生じます。 2-1. 基本的なサンプル 以下のように、まず、bufio.NewScanner で、標準入力(os.Stdin)を受け付けるスキャナを生成します。 あとは、scanner.Scan() で1行分のスキャンを行い、scanner.Text() で入力値を取得します(scanner はただの変数なので、s でも何でも構いません)。 main.go package main import ( "bufio" "fmt" "os" ) func main() { scanner := bufio.NewScanner(os.Stdin) // 標準入力を受け付けるスキャナ scanner.Scan() // 1行分の入力を取得する fmt.Println(scanner.Text()) } ターミナル(コマンドプロンプト) C:\golang\sample>go build main.go C:\golang\sample>.\main abc 123 456 abc 123 456 bufio を使用した場合、標準で「改行」を区切りとして入力を受け付けます。 上記のように、abc 123 456 というようにスペースを含めた入力をしても、そのまま1行分を取得します。 2-2. デフォルトサイズ(65,536文字)以上を読み込む bufio では、デフォルトで読み込む文字の最大サイズは MaxScanTokenSize(65,536文字)に設定されます。 これ以上の文字数をスキャンできるようにするには、次のように、Buffer 関数(メソッド)を使用します。 main.go package main import ( "bufio" "fmt" "os" ) func main() { scanner := bufio.NewScanner(os.Stdin) scanner.Buffer(make([]byte, 64*1024), 100001) // 最大100,001文字 scanner.Scan() fmt.Println(scanner.Text()) } 上の例は、最大の読み込み文字数を、100,001 文字に指定した場合となります。 Buffer 関数は次の構文で表されます。 func (*Scanner) Buffer func (s *Scanner) Buffer(buf []byte, max int) buf にはスキャン時に使用する初期バッファを指定します(コード中の 64*1024 が該当)。 max にはスキャン中に割り当てられる可能性のあるバッファの最大サイズを指定します(コード中の 100001 が該当)。 2-3. スペース区切りの入力を個別に取得する 入力を1行ずつ取得した場合は、次のような形でスペース区切りで分割することで、それぞれの値を取得できます。 分割には、strings.Split(対象文字列, 区切り文字) を使用しています。 main.go package main import ( "bufio" "fmt" "os" "strings" ) func main() { scanner := bufio.NewScanner(os.Stdin) scanner.Scan() inputs := strings.Split(scanner.Text(), " ") for _, input := range inputs { fmt.Println(input) } } ターミナル(コマンドプロンプト) C:\golang\sample>go build main.go C:\golang\sample>.\main aaa bbb ccc ddd eee aaa bbb ccc ddd eee 上の例では、aaa bbb ccc ddd eee という入力を1行分取得した上で、スペース区切りで分解しています。 スペース区切りの数が未知の場合でも対応できます。 2-4. 複数行の入力を取得する 複数の入力を取得するには、その回数分 scanner.Scan() を繰り返します(scanner はただの変数なので、s でも何でも構いません)。 main.go package main import ( "bufio" "fmt" "os" ) func main() { var n int = 3 var str []string scanner := bufio.NewScanner(os.Stdin) for i := 0; i < n; i++ { scanner.Scan() str = append(str, scanner.Text()) } fmt.Println(str) } ターミナル(コマンドプロンプト) C:\golang\sample>go build main.go C:\golang\sample>.\main 123 456 789 [123 456 789] 3. テキストファイルを読み込む 最後に、テキストファイルを読み込む方法も残しておきます。 準備として、main.go と同じフォルダ内に次のテキストファイルを保存しておきます。 C:\golang\sample\test.txt abcdefghijklmnopqrstuvwxyz あいうえおかきくけこさしすせそ 3-1. 基本的なサンプル まず、エラーが起こらない前提として簡単に書きます。 次のように、bufio.NewScanner を使用することで、テキストファイルの内容が取得できます。 main.go package main import ( "bufio" "fmt" "os" ) func main() { file, _ := os.Open("test.txt") // ファイルを開く scanner := bufio.NewScanner(file) // ファイルを読み取るスキャナー for scanner.Scan() { // ファイルを1行ずつ読み込む(読み込む行が無かった場合はFalse) fmt.Println(scanner.Text()) } } ターミナル(コマンドプロンプト) C:\golang\sample>go build main.go C:\golang\sample>.\main abcdefghijklmnopqrstuvwxyz あいうえおかきくけこさしすせそ 3-2. エラー処理も含めたサンプル エラー処理も含めると、次のようになります。 main.go package main import ( "bufio" "fmt" "os" ) func main() { file, err := os.Open(`C:\golang\sample\test.txt`) if err != nil { // ファイルを開く際にエラーが生じた場合 fmt.Println(err) return } defer file.Close() scanner := bufio.NewScanner(file) for scanner.Scan() { fmt.Println(scanner.Text()) } if scanner.Err() != nil { // 読み込み時にエラーがあった場合 fmt.Println(scanner.Err()) } }
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Dockerの最新版(20.10.8)でコンテナ内部からhttps通信ができない問題の対策

エラー内容:何が起きたか コンパイル言語であるGoのコードや機能の改修を行いやすくするために、Dockerを用いてGoの環境構築を行っていたところコンテナ内部で謎のネットワークエラーが出た。 行った手順としてはまず、以下のようなDockerfileとdocker-compose.ymlを作成。 Dockerfile FROM golang:latest WORKDIR /path/to/go/project COPY go.mod ./ COPY go.sum ./ RUN go mod download COPY path/to/main.go ./ EXPOSE 8080 docker-compose.yml version: '3' services: app: container_name: go_container image: golang:latest tty: true ports: - "3000:3000" volumes: - /path/to/$GOPATH:/go (※なお、今回の事象はこれと全く同じ構成のdocker-composeでなくとも発生する。 理由は後述) docker-composeを実行 docker-compose up --build -d すると、go mod downlodのところで永遠に処理が終わらない。 別のアプローチ Dockerfileを以下のように変更 Dockerfile FROM golang:latest WORKDIR /path/to/go/project COPY go.mod ./ COPY go.sum ./ RUN go get -u github.com/go-sql-driver/mysql COPY path/to/main.go ./ EXPOSE 8080 go getで取得するモジュールはなんでもOK これを実行すると以下のようなエラーが返ってくる。 (前略) net/http: TLS handshake timeout つまりネットワークが途絶えてしまっている。 後の調査(内容割愛。curlとか)の結果、奇妙なことにhttps通信の場合にだけこのような事象が発生することがわかった。 原因:答えはスタート地点に 原因がわからず数日にわたって調べまくったところ、公式の最新バージョン(20.10.8)リリースノートに以下のような記述を発見。 IMPORTANT Due to net/http changes in Go 1.16, HTTP proxies configured through the \$HTTP_PROXY environment variable are no longer used for TLS (https://) connections. Make sure you also set an $HTTPS_PROXY environment variable for handling requests to https:// URLs. Refer to the HTTP/HTTPS proxy section to learn how to configure the Docker Daemon to use a proxy server. go側のnet/httpの仕様変更に伴って環境変数 $HTTPS_PROXYも自分で設定しなきゃダメだよということ。(かなり大雑把) 灯台下暗しとはまさにこのこと。 これならどのようなDockerfileを書いたところで同じ事象に遭遇するはず。 とはいえ、設定するようなプロキシサーバがなかったため、squidを使用してホスト側にプロキシサーバを建てることに。 対策:環境変数の設定とプロキシサーバの設置 squidのインストール・設定 (僕の利用していた環境はcentos7なので、yumを利用して)squidをインストール sudo yum install -y squid /etc配下にある設定ファイルを編集 /etc/squid/squid.conf # # Recommended minimum configuration: # # Example rule allowing access from your local networks. # Adapt to list your (internal) IP networks from where browsing # should be allowed acl localnet src 10.0.0.0/8 # RFC1918 possible internal network acl localnet src 172.16.0.0/12 # RFC1918 possible internal network acl localnet src 192.168.0.0/16 # RFC1918 possible internal network acl localnet src fc00::/7 # RFC 4193 local private network range acl localnet src fe80::/10 # RFC 4291 link-local (directly plugged) machines acl SSL_ports port 443 acl Safe_ports port 80 # http acl Safe_ports port 21 # ftp acl Safe_ports port 443 # https acl Safe_ports port 70 # gopher acl Safe_ports port 210 # wais acl Safe_ports port 1025-65535 # unregistered ports acl Safe_ports port 280 # http-mgmt acl Safe_ports port 488 # gss-http acl Safe_ports port 591 # filemaker acl Safe_ports port 777 # multiling http acl CONNECT method CONNECT # 以下2行を追記(多分コンテナの方だけでいい) acl name(任意の名前1) src (DockerホストのIP) acl name2(任意の名前2) src (DockerコンテナのIP) # # Recommended minimum Access Permission configuration: # # Deny requests to certain unsafe ports http_access deny !Safe_ports # Deny CONNECT to other than secure SSL ports http_access deny CONNECT !SSL_ports # Only allow cachemgr access from localhost http_access allow localhost manager http_access deny manager # We strongly recommend the following be uncommented to protect innocent # web applications running on the proxy server who think the only # one who can access services on "localhost" is a local user #http_access deny to_localhost # # INSERT YOUR OWN RULE(S) HERE TO ALLOW ACCESS FROM YOUR CLIENTS # # Example rule allowing access from your local networks. # Adapt localnet in the ACL section to list your (internal) IP networks # from where browsing should be allowed # http_access allow localnet http_access allow localhost # 以下2行を追記(こちらも多分コンテナの方ry) # (※name, name2はさきほど定義したもの) http_access allow name http_access allow name2 # http_access allow all # And finally deny all other access to this proxy http_access deny all # Squid normally listens to port 3128 http_port 3128 # Uncomment and adjust the following to add a disk cache directory. #cache_dir ufs /var/spool/squid 100 16 256 # Leave coredumps in the first cache dir coredump_dir /var/spool/squid # # Add any of your own refresh_pattern entries above these. # refresh_pattern ^ftp: 1440 20% 10080 refresh_pattern ^gopher: 1440 0% 1440 refresh_pattern -i (/cgi-bin/|\?) 0 0% 0 refresh_pattern . 0 20% 4320 (squidのデフォルトのポート番号は3128。今回はとくに変更しない。) コンテナのIDがわからない場合は、コンテナ内部に入り以下のコマンドで確認 hostname -i 参考:コンテナのIPを確認する方法 開始 sudo systemctl start squid 次はdocker側の設定 Docker側、環境変数$HTTP(S)_PROXYの設定 Docker公式の設定ページに従って環境変数を設定。 sudo mkdir -p /etc/systemd/system/docker.service.d /etc/systemd/system/docker.service.d Environment="HTTP_PROXY=(squidを設置したホストのIP):3128" Environment="HTTPS_PROXY=(squidを設置したホストのIP):3128" ついでに、dockerを利用するユーザーのホームディレクトにconfig.jsonを設置。 公式ページの設定ガイドラインはこちら ~/.docker/config.json { "proxies": { "default": { "httpProxy": "(squidホスト):3128", "httpsProxy": "(squidホスト):3128" } } } 書いたらDockerを再起動。 sudo systemctl restart docker 再起動するとコンテナが止まってしまうためコンテナをstartするのを忘れないように。 自分はそれで気づかずにホストでhttpsの検証してぬか喜びしてしまった 確認 コンテナ内部でcurlコマンドを使い検証。 alpine使ってる人とかでcurlできない人はインストールするか、apk updateでもかけてみるのがよし。 crul https://google.com <HTML><HEAD><meta http-equiv="content-type" content="text/html;charset=utf-8"> <TITLE>301 Moved</TITLE></HEAD><BODY> <H1>301 Moved</H1> The document has moved <A HREF="https://www.google.com/">here</A>. </BODY></HTML> HTMLが返ってきたらOK。 この状態になればgo getやgo mod downloadなどのコマンドも通る。 総括 最新版(20.10.8)のDockerはコンテナ内部からhttps通信を行うためにプロキシが必要。 20.10.7などの直前バージョンを使おうとしても、他モジュールが20.10.8でくっついてきてしまったり、そもそものgoの仕様変更だったりという事情で結局この事象から逃れられなかった。 また、最初からhttp通信はできていたが、$HTTP_PROXYのデフォルト値は見つけられなかった。(echoだと空行) もっと簡単にできたなどあれば方法教えてください。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む