この記事はGoの
codelabに基づいていますが、それに限定されるものではありません。 この記事を読み進むと、http、テンプレート、regexpライブラリを使用してWebアプリケーション、テンプレート、フィルター入力をそれぞれ作成するGoデータ構造、動的配列について学習できます。

この記事を理解するには、unixやwebという言葉に怖がらないように、少しプログラミングができる必要があります。 言語の基本については、記事で概説します。
インストールに行く
したがって、Goで最初に作業する必要があるのはLinux、FreeBSD(OS X)ですが、
MinGWは Windowsでも動作します。
Goをインストールする必要があります。そのためには、次のようなことを行う必要があります(dpkgを備えたシステムについての指示が与えられます)
$ sudo apt-get install bison ed gawk gcc libc6-dev make#ビルドツール
$ sudo apt-get install python-setuptools python-dev build-essential#ツールのビルドとインストール
$ sudo easy_install mercurial#まだお持ちでない場合は、リポジトリから(apt経由で)インストールしない方が良い
$ hg clone -r release https://go.googlecode.com/hg/ go
$ cd go / src
$ ./all.bash
すべてがうまく
いけば、以下を
〜/ .bashrcまたは
〜/ .profileに追加できます:
エクスポートGOROOT = $ HOME / go
export GOARCH = 386#またはamd64、OSアーキテクチャに応じて
エクスポートGOOS = linux
エクスポートGOBIN = $ GOROOT / bin
PATH = $ PATH:$ GOBIN
シェルを再入力してコンパイラーを呼び出すと(i386の場合は
8g 、amd64の場合は
6g 、
8gがあります)、ヘルプメッセージが表示されます。
gc:使用法8g [フラグ] file.go ...
これは、Goがインストールされて動作していることを意味し、アプリケーションに直接アクセスできます。
スタート。 データ構造
アプリケーションのディレクトリを作成します。
$ mkdir〜/ gowiki
$ cd〜/ gowiki
テキストエディター(
vimおよびemacsのバインダー)を 使用して、次の内容の
wiki.goファイルを作成しましょう。
パッケージメイン
インポート(
「fmt」
「io / ioutil」
「os」
)
名前から、アプリケーションがページの編集と保存を許可することは明らかです。
このコードは、Go標準ライブラリから
fmt、ioutil、および
osライブラリをインポートします。 後で他のライブラリを追加します。
複数のデータ構造を定義します。 Wikiは、本文とタイトルを持つリンクされたページのコレクションです。 対応するデータ構造には2つのフィールドがあります。
type page struct {
タイトル文字列
本体[]バイト
}
[]バイトデータ型は、動的配列の類似体であるバイト型のスライスです(詳細:
Effective Go )。記事の本文は、標準ライブラリでの作業の便宜上、
文字列ではなく
[]バイトに格納されます。
データ構造は、データがメモリに格納される方法を記述します。 しかし、データを長時間保存する必要がある場合はどうでしょうか? ディスクに保存する
saveメソッドを実装し
ます 。
func(p *ページ)save()os.Error {
ファイル名:= p.title + ".txt"
return ioutil.WriteFile(ファイル名、p.body、0600)
}
この関数のシグネチャは次のとおりです。「これは、パラメーターなしでページポインターに適用可能な
saveメソッドで、タイプ
os.Errorの値を返します。」
このメソッドはテキストをファイルに保存します。 簡単にするために、ヘッダーはファイル名であると想定しています。 これが十分に安全でないと思われる場合は、
crypto / md5をインポートし、
md5.New(ファイル名)呼び出しを使用できます。
戻り値は、
WriteFile呼び出し(スライスをファイルに書き込むための標準ライブラリ関数)の戻り値に対応する
os.Error型になります。 これは、将来ファイルに保存するエラーを処理できるようにするために行われます。 問題がない場合、
page.save()は
nil (ポインター、インターフェース、およびその他のタイプの場合はヌル値
)を返します。
WriteFile呼び出しの3番目のパラメーターである8進定数0600は、現在のユーザーに対してのみ読み取りおよび書き込み権限でファイルが保存されることを示します。
ページをロードすることも興味深いでしょう:
func loadPage(タイトル文字列)*ページ{
ファイル名:=タイトル+ ".txt"
body、_:= ioutil.ReadFile(ファイル名)
リターン&ページ{タイトル:タイトル、ボディ:ボディ}
}
この関数は、ヘッダーからファイル名を取得し、内容をページ型の変数に読み取り、そのポインターを返します。
Goの関数は複数の値を返すことができます。
io.ReadFile標準ライブラリ
関数は
[]バイトと
os.Errorを返します。
loadPage関数のエラーはまだ処理されていません。下線は「この値を保存しない」ことを意味します。
ReadFileがエラーを返すとどうなりますか? たとえば、そのタイトルのページはありません。 これは重大な間違いであり、無視することはできません。 関数が
* pageと
os.Errorの 2つの値も返すようにします。
func loadPage(タイトル文字列)(*ページ、os.Error){
ファイル名:=タイトル+ ".txt"
body、err:= ioutil.ReadFile(ファイル名)
if err!= nil {
戻り値nil、err
}
リターン&ページ{タイトル:タイトル、ボディ:ボディ}、nil
}
これで、2番目のパラメーターの値を確認できます
。nilの場合、ページは正常にロードされています。 それ以外の場合は、タイプ
os.Errorの値になります。
したがって、データ構造と、ロードおよびアンロードのメソッドがあります。 これがどのように機能するかを確認する時が来ました:
func main(){
p1:=&page {title: "TestPage"、body:[] byte( "Test page。")}
p1.save()
p2、_:= loadPage( "TestPage")
fmt.Println(文字列(p2.body))
}
このコードをコンパイルして実行すると、
TestPage.txtファイルに値
p1-> bodyが含まれます。 その後、この値は
p2変数にロードされて表示されます。
プログラムをビルドして実行するには、次を実行する必要があります。
$ 8g wiki.go
$ 8l wiki.8
$ ./8.out
これはサンプルページです。
HTTPライブラリ
Goの最も単純なWebサーバーは次のようになります。
パッケージメイン
インポート(
「fmt」
「http」
)
funcハンドラー(w http.ResponseWriter、r * http.Request){
fmt.Fprintf(w、 "Hello%s!"、r.URL.Path [1:])
}
func main(){
http.HandleFunc( "/"、ハンドラー)
http.ListenAndServe( ":8080"、なし)
}
メイン関数は
http.HandleFuncを呼び出し
ます 。これは、すべての種類の要求(
"/" )が
ハンドラー関数によって処理されることを
httpライブラリに伝えます。
次の
http.ListenAndServeの呼び出し
により、ポート8080(
":8080" )上のすべてのインターフェースでリクエストを処理することを決定します。 2番目のパラメーターはまだ必要ありません。 プログラムは、強制終了するまでこのモードで動作します。
ハンドラー関数のタイプは
http.HandlerFuncです。 パラメータとして
http.ResponseWriterと
http.Requestへのポインタを
取ります。
タイプ
http.ResponseWriterの値は、
http応答を生成し
ます 。 データをそこに書き込む(
Fprintfを呼び出す)ことで、ページのコンテンツをユーザーに返します。
http.Requestデータ
構造はユーザーリクエストです。 文字列
r.URL.Pathはパスです。 接尾辞
[1:]は、「最初の文字から最後までの
パススライス(サブストリング)を取得する」、つまり先頭のスラッシュを削除することを意味します。
ブラウザを起動し、
http:// localhost:8080 / habrahabrというURLを開くと、目的のページが表示されます。
こんにちはhabrahabr!
httpを使用してページを返す
httpライブラリをインポートし
ます 。
インポート(
「fmt」
「http」
「io / ioutil」
「os」
)
記事を表示するハンドラーを作成します。
const lenPath = len( "/ view /")
func viewHandler(w http.ResponseWriter、r * http.Request){
title:= r.URL.Path [lenPath:]
p、_:= loadPage(タイトル)
fmt.Fprintf(w、 "<h1>%s </ h1> <div>%s </ div>"、p.title、p.body)
}
まず、この関数は、指定されたURLのパスコンポーネントである
r.URL.Pathからヘッダーを取得します。 グローバル定数
lenPathは、システム内の記事のテキストを表示することを示すパス内のプレフィックス
「/ view /」の長さです。 部分文字列
[lenPath:]が強調表示されます。つまり、記事のタイトルです。プレフィックスは除外されます。
この関数はデータをロードし、単純なhtmlタグで補完して、タイプ
http.ResponseWriterのパラメーター
wに書き込みます。
os.Error型の戻り値を無視するために_を
再利用しました。 これは単純化のために行われ、通常はあまりうまくいきません。 以下に、このようなエラーを正しく処理する方法を示します。
このハンドラーを呼び出すには、適切な
viewHandlerを使用して
httpを初期化し、パス
/ view /に沿って要求を処理する
メイン関数を
作成します。
func main(){
http.HandleFunc( "/ view /"、viewHandler)
http.ListenAndServe( ":8080"、なし)
}
(
test.txtファイルに)テストページを作成し、コードをコンパイルして
、ページを
表示してみます。
$ echo "Hello world"> test.txt
$ 8g wiki.go
$ 8l wiki.8
$ ./8.out
サーバーの実行中、「Hello world」という単語を含む「test」という見出しのあるページは、
http:// localhost:8080 / view / testで利用できます。
ページを変更
ページを編集する機能がないこのウィキはどのようなものですか? 編集フォームを表示する
editHandlerと受信データを保存する
saveHandlerの 2つの新しいハンドラーを作成しましょう。
まず、それらを
main()に追加します。
func main(){
http.HandleFunc( "/ view /"、viewHandler)
http.HandleFunc( "/ edit /"、editHandler)
http.HandleFunc( "/ save /"、saveHandler)
http.ListenAndServe( ":8080"、なし)
}
editHandler関数はページをロードし(そのようなページがない場合は空の構造を作成します)、フォームを表示します:
func editHandler(w http.ResponseWriter、r * http.Request){
title:= r.URL.Path [lenPath:]
p、err:= loadPage(タイトル)
if err!= nil {
p =&ページ{タイトル:タイトル}
}
fmt.Fprintf(w、 "<h1> Editing%s </ h1>" +
"<form action = \" / save /%s \ "method = \" POST \ ">" +
"<テキストエリア名= \" body \ ">%s </テキストエリア>" +
"<input type = \" submit \ "value = \" Save \ ">" +
「</ form>」、
p.title、p.title、p.body)
}
この関数は正常かつ正常に機能しますが、見苦しくなります。 理由はhtmlハードコードですが、修正可能です。
ライブラリテンプレート
テンプレートライブラリは、標準のGoライブラリの一部です。 テンプレートを使用してHTMLマークアップをコードの外部に保存し、再コンパイルせずにマークアップを変更できるようにします。
まず、
テンプレートをインポートします:
インポート(
「http」
「io / ioutil」
「os」
「テンプレート」
)
edit.htmlファイルに次の内容のフォームテンプレートを作成します。
<h1> {title}の編集</ h1>
<form action = "/ save / {title}" method = "POST">
<div> <text area name = "body" rows = "20" cols = "80"> {body | html} </ text area> </ div>
<div> <input type = "submit" value = "Save"> </ div>
</ form>
テンプレートを使用するように
editHandlerを変更します。
func editHandler(w http.ResponseWriter、r * http.Request){
title:= r.URL.Path [lenPath:]
p、err:= loadPage(タイトル)
if err!= nil {
p =&ページ{タイトル:タイトル}
}
t、_:= template.ParseFile( "edit.html"、nil)
t.Execute(p、w)
}
template.ParseFileメソッドは
edit.htmlファイルを読み取り、タイプ
* template.Templateの値を
返します。
t.Executeメソッドは、出現するすべての
{title}および
{body}を
p.titleおよび
p.bodyの値に
置き換え 、結果のhtmlをhttp.ResponseWriter型の変数に出力します。
テンプレートで
{body | html}テンプレートが検出されたことに注意してください。 これは、パラメーターがhtmlでの出力用にフォーマットされることを意味します。 エスケープされ、たとえば、
>は
&gt;に置き換えられ
ます。 。 これにより、フォーム内のデータが正しく表示されます。
プログラムに
fmt.Sprintfの呼び出しが
なくなったため、インポートから
fmtを削除できます。
また、ページを表示するためのテンプレート
view.htmlを作成します。
<h1> {title} </ h1>
<p> [<a href="/edit/{titlele"">編集</a>] </ p>
<div> {body} </ div>
それに応じて
viewHandlerを変更し
ます 。
func viewHandler(w http.ResponseWriter、r * http.Request){
title:= r.URL.Path [lenPath:]
p、_:= loadPage(タイトル)
t、_:= template.ParseFile( "view.html"、nil)
t.Execute(p、w)
}
どちらの場合も、テンプレートを呼び出すためのコードはほぼ同じであることに注意してください。 このコードを別の関数に取り込むことで、重複を取り除きます。
func viewHandler(w http.ResponseWriter、r * http.Request){
title:= r.URL.Path [lenPath:]
p、_:= loadPage(タイトル)
renderTemplate(w、「ビュー」、p)
}
func editHandler(w http.ResponseWriter、r * http.Request){
title:= r.URL.Path [lenPath:]
p、err:= loadPage(タイトル)
if err!= nil {
p =&ページ{タイトル:タイトル}
}
renderTemplate(w、「編集」、p)
}
func renderTemplate(w http.ResponseWriter、tmpl文字列、p *ページ){
t、_:= template.ParseFile(tmpl + "。html"、nil)
t.Execute(p、w)
}
ハンドラーが短くなり、シンプルになりました。
欠落ページの処理
/ view / APageThatDoesntExistに移動するとどうなりますか? プログラムは落ちます。 そして、すべては
loadPageによって返される2番目の値を処理しなかったため
です 。 ページが存在しない場合、新しい記事を作成するためにユーザーをページにリダイレクトします。
func viewHandler(w http.ResponseWriter、r * http.Request、タイトル文字列){
p、err:= loadPage(タイトル)
if err!= nil {
http.Redirect(w、r、 "/ edit /" + title、http.StatusFound)
帰る
}
renderTemplate(w、「ビュー」、p)
}
http.Redirect関数は、HTTPステータス
http.StatusFound(302)と
LocationヘッダーをHTTP応答に追加します。
ページを保存する
saveHandler関数は、フォームからのデータを処理します。
func saveHandler(w http.ResponseWriter、r * http.Request){
title:= r.URL.Path [lenPath:]
body:= r.FormValue( "body")
p:=&ページ{タイトル:タイトル、本文:[]バイト(本文)}
p.save()
http.Redirect(w、r、 "/ view /" + title、http.StatusFound)
}
選択したセキュリティと本文で新しいページが作成されます。
save()メソッドはデータをファイルに保存し、クライアントは
/ view /ページにリダイレクトされます。
FormValueによって返される値は
string型です。 ページ構造に保存するには、
[] byte(body)を書き込むことで
[]バイトに変換します。
エラー処理
いくつかの場所でプログラムのエラーを無視します。 これは、エラーが発生するとプログラムがクラッシュするという事実につながるため、サーバーが動作し続けている間にユーザーにエラーメッセージを返すことをお勧めします。
まず、
renderTemplateにエラー処理を追加します:
func renderTemplate(w http.ResponseWriter、tmpl文字列、p *ページ){
t、err:= template.ParseFile(tmpl + "。html"、nil)
if err!= nil {
http.Error(w、err.String()、http.StatusInternalServerError)
帰る
}
err = t.Execute(p、w)
if err!= nil {
http.Error(w、err.String()、http.StatusInternalServerError)
}
}
http.Error関数は、選択したHTTPステータス(この場合は「Internal Server Error」)を送信し、エラーメッセージを返します。
saveHandlerで同様の編集を行いましょう。
func saveHandler(w http.ResponseWriter、r * http.Request、タイトル文字列){
body:= r.FormValue( "body")
p:=&ページ{タイトル:タイトル、本文:[]バイト(本文)}
err:= p.save()
if err!= nil {
http.Error(w、err.String()、http.StatusInternalServerError)
帰る
}
http.Redirect(w、r、 "/ view /" + title、http.StatusFound)
}
p.save()で発生したエラーはすべてユーザーに渡されます。
テンプレートのキャッシュ
コードは十分に効率的ではありません
。renderTemplateは、ページがレンダリングされるたびに
ParseFileを呼び出します。 プログラムの起動時にテンプレートごとに
ParseFileを 1回呼び出して、
*テンプレートタイプの受け取った値を将来の使用のために構造体に保存することをお
勧めします。
まず、
* Templateの値を保存する
テンプレートマップを作成します。マップ内のキーはテンプレートの名前になります。
var templates = make(map [string] * template.Template)
次に、
main()の前に呼び出す初期化関数を作成します。
template.MustParseFile関数は、エラーコードを返さない
ParseFileのラッパーであり、代わりにパニックします。 実際、不正なテンプレートを処理する方法がわからないため、この動作はプログラムに受け入れられます。
func init(){for _、tmpl:= range [] string {"edit"、 "view"} {templates [tmpl] = template.MustParseFile(tmpl + "。html"、nil)}}
forループは、
範囲構成で使用され、指定されたパターンを処理します。
次に、対応するテンプレートの
Executeメソッドを
呼び出すように
renderTemplate関数を変更します。
func renderTemplate(w http.ResponseWriter、tmpl文字列、p *ページ){
err:= templates [tmpl] .Execute(p、w)
if err!= nil {
http.Error(w、err.String()、http.StatusInternalServerError)
}
}
検証
すでに述べたように、プログラムには重大なセキュリティエラーがあります。 名前の代わりに、任意のパスを渡すことができます。 正規表現チェックを追加します。
正規表現ライブラリをインポートします。 RVを保存するグローバル変数を作成します。
var titleValidator = regexp.MustCompile( "^ [a-zA-Z0-9] + $")
regexp.MustCompile関数
は正規表現を
コンパイルし、 regexp.Regexpを返します。
template.MustParseFileのような
MustCompileは、エラーが発生した場合にパニックを
起こすという点で
Compileと異なり、
Compileはエラーコードを返します。
次に、URLからタイトルを抽出し、PB
titleValidatorをチェックする関数を作成しましょう。
func getTitle(w http.ResponseWriter、r * http.Request)(タイトル文字列、err os.Error){
title = r.URL.Path [lenPath:]
if!titleValidator.MatchString(title){
http.NotFound(w、r)
err = os.NewError( "無効なページタイトル")
}
帰る
}
タイトルが正しい場合、
nilが返されます。 そうでない場合、「404 Not Found」がユーザーに表示され、エラーがハンドラーに返されます。
各ハンドラーに
getTitle呼び出しを追加します。
func viewHandler(w http.ResponseWriter、r * http.Request){
title、err:= getTitle(w、r)
if err!= nil {
帰る
}
p、err:= loadPage(タイトル)
if err!= nil {
http.Redirect(w、r、 "/ edit /" + title、http.StatusFound)
帰る
}
renderTemplate(w、「ビュー」、p)
}
func editHandler(w http.ResponseWriter、r * http.Request){
title、err:= getTitle(w、r)
if err!= nil {
帰る
}
p、err:= loadPage(タイトル)
if err!= nil {
p =&ページ{タイトル:タイトル}
}
renderTemplate(w、「編集」、p)
}
func saveHandler(w http.ResponseWriter、r * http.Request){
title、err:= getTitle(w、r)
if err!= nil {
帰る
}
body:= r.FormValue( "body")
p:=&ページ{タイトル:タイトル、本文:[]バイト(本文)}
err = p.save()
if err!= nil {
http.Error(w、err.String()、http.StatusInternalServerError)
帰る
}
http.Redirect(w、r、 "/ view /" + title、http.StatusFound)
}
機能タイプとクロージャー
エラーと戻り値をチェックすると、かなり均一なコードが生成されます。一度だけ記述するとよいでしょう。 これは、たとえば、対応する呼び出しでエラーを返す関数をラップする場合に可能です。機能タイプはこれに役立ちます。
titleパラメーターを追加して、ハンドラーを書き換えます。
func viewHandler(w http.ResponseWriter、r * http.Request、タイトル文字列)
func editHandler(w http.ResponseWriter、r * http.Request、タイトル文字列)
func saveHandler(w http.ResponseWriter、r * http.Request、タイトル文字列)
ここで、上記で定義した関数のタイプを取り、
http.HandlerFuncを返すラッパー関数を定義します(
http.HandleFuncに渡すため)。
func makeHandler(fn func(http.ResponseWriter、* http.Request、string))http.HandlerFunc {
return func(w http.ResponseWriter、r * http.Request){
//ここでは、リクエストからページタイトルを抽出し、
//そして提供されたハンドラー 'fn'を呼び出します
}
}
返される関数はクロージャーです。なぜなら 外部で定義された値(この場合、変数
fn 、ハンドラー)を使用します。
getTitleのコードを
ここに移動してみましょう 。
func makeHandler(fn func(http.ResponseWriter、* http.Request、string))http.HandlerFunc {
return func(w http.ResponseWriter、r * http.Request){
title:= r.URL.Path [lenPath:]
if!titleValidator.MatchString(title){
http.NotFound(w、r)
帰る
}
fn(w、r、タイトル)
}
}
makeHandlerによって返される
クロージャーは、
http.ResponseWriterや
http.Requestなどのパラメーターを取る関数です(つまり、
http.HandlerFuncなどの関数)。 このクロージャは、URLからタイトルを抽出し、そのPB
titleValidatorをチェックします。 ヘッダーが正しくない場合、エラーが
ResponseWriterに送信されます(
http.NotFoundを呼び出し
ます )。 それ以外の場合、対応する
fnハンドラーが呼び出されます。
main()関数にラッパー呼び出しを追加します。
func main(){
http.HandleFunc( "/ view /"、makeHandler(viewHandler))
http.HandleFunc( "/ edit /"、makeHandler(editHandler))
http.HandleFunc( "/ save /"、makeHandler(saveHandler))
http.ListenAndServe( ":8080"、なし)
}
最後に、ハンドラー関数からgetTitleの呼び出しを削除して、それらをより簡単にします。
func viewHandler(w http.ResponseWriter、r * http.Request、タイトル文字列){
p、err:= loadPage(タイトル)
if err!= nil {
http.Redirect(w、r、 "/ edit /" + title、http.StatusFound)
帰る
}
renderTemplate(w、「ビュー」、p)
}
func editHandler(w http.ResponseWriter、r * http.Request、タイトル文字列){
p、err:= loadPage(タイトル)
if err!= nil {
p =&ページ{タイトル:タイトル}
}
renderTemplate(w、「編集」、p)
}
func saveHandler(w http.ResponseWriter、r * http.Request、タイトル文字列){
body:= r.FormValue( "body")
p:=&ページ{タイトル:タイトル、本文:[]バイト(本文)}
err:= p.save()
if err!= nil {
http.Error(w、err.String()、http.StatusInternalServerError)
帰る
}
http.Redirect(w、r、 "/ view /" + title、http.StatusFound)
}
これが結果になるはずですコードを再構築し、アプリケーションを実行します。
$ 8g wiki.go
$ 8l wiki.8
$ ./8.out
http:// localhost:8080 / view / ANewPageには、フォームのあるページがあります。 ページを保存して、そこに行くことができます。
ご注意 habraparserを怒らせないように、コード内のtextareaを壊す必要がありました。