poniedziałek, października 18, 2010

Wysyłamy pliki do Google Docs przy pomocy Go :-)

W ramach szukania pomysłów na wpis do bloga, a i nauki Go chciałem napisać sobie uploadowarkę plików do Google Docs przy pomocy Go........
I częściowo mi się to udało ;-)

Częściowo, bo działa tylko pod Linuskem, pod portem dla Windows nie działa.

Błąd na Windows pojawia się w trakcie próby zalogowania się do Google Docs, a dokładniej do Writely bo tak nazywa się serwis związany z Google Docs [bez arkuszy] dostaję błąd "could not find root certificate for chain", a przez to reszta zabawy nie ma sensu :-(

Na Linuksie jednak wszystko działa :-) i cały program wygląda tak:

package main

import ("fmt";"http";"os";"strings";"io";"net";"bufio")

type nopCloser struct {
io.Reader
}

func (nopCloser) Close() os.Error { return nil }

type readClose struct {
io.Reader
io.Closer
}


func main() {
var user string
var password string
if len(os.Args)==3 {
user = os.Args[1]
password = os.Args[2]
} else {
fmt.Println("Sorry, you need to provide your Google Account credentials")
return
}
fmt.Println(password)
fmt.Println(user)

// OK, tutaj zaczyna się logowanie do serwisów Google, tym razem do "writely" czyli składowiska dla dokumentów, prezentacji i obrazków Google Docs

bodyType:="application/x-www-form-urlencoded"
appName:="RMKGOTest"
appVersion:="0.01"
service:="writely"
var loginRequestBody = "accountType=HOSTED_OR_GOOGLE&Email="+user+"&Passwd="+password+"&service="+service+"&source="+appName+"-"+appVersion;
r,err:=http.Post("https://www.google.com/accounts/ClientLogin",bodyType,strings.NewReader(loginRequestBody))
fmt.Println(err)
reader := r.Body
var buf []byte = make([]byte,r.ContentLength)
reader.Read(buf)
fmt.Println(string(buf))
elems:=strings.Split(string(buf),"\n",-1)
fmt.Println(len(elems))

// OK, już jesteśmy zalogowani, teraz musimy pobrać nagłówki autoryzacji

var authHeader string
for i:=range elems {
if strings.Index(elems[i],"Auth=")==0 {
authHeader=strings.Replace(elems[i],"Auth=","GoogleLogin auth=",1)
}
}

// Teraz przygotowujemy się do wysłania naszego "pliku" do Google Docs :-)

// treść pliku
documentBody:="This is only a test\nSend from Go to Google Docs :-)"
// MIME type
bodyType="text/plain"

// Tworzymy requesta

req:=new(http.Request)
req.Method="POST"
req.RawURL="https://docs.google.com/feeds/default/private/full"
req.ProtoMajor = 1
req.ProtoMinor = 1
req.Host="docs.google.com"
req.Close = true
req.Body=nopCloser{strings.NewReader(documentBody)}
req.Header = make(map[string]string)
req.Header["Content-Type"]=bodyType
req.Header["Authorization"]=authHeader
req.Header["Slug"]="toster.txt"
req.Header["GData-Version"]="3.0"
req.ContentLength=int64(len(documentBody))
c,errNet:=net.Dial("tcp","","docs.google.com:80")

fmt.Println(errNet)
fmt.Println(c)

err2:=req.Write(c)
fmt.Println(err2)
reader2 := bufio.NewReader(c)
resp, err3 := http.ReadResponse(reader2, req.Method)
if err3 != nil {
c.Close()
fmt.Println(err3)
return
}

resp.Body = readClose{resp.Body, c}

fmt.Println(resp.Body)
fmt.Println(resp.ContentLength)
cLen:=resp.ContentLength
if cLen==-1 {
cLen = 8192
}
var buf2 []byte = make([]byte,cLen)
reader2.Read(buf2)
fmt.Println(string(buf2))

c.Close()

}


W celu jego uruchomienia kompilujemy go standardem:
8g httpTest2.go
8l httpTest2.8

i uruchamiamy:
./8.out eMail HasłoDoKontaGoogle

Gdzie eMail to adres którego używamy w Google Docs, a HasłoDoKontaGoogle to hasło do konta związanego z tym mailem.
Ta wersja programu wysyła plik do Google Docs przy pomocy HTTP, jeśli chcemy użyć HTTPS, to trzeba w sekcji import zmienić "net" w "crypto/tls", a w linii z net.Dial trzeba zmienić to net.Dial na tls.Dial, do tego trzeba zmienić docs.google.code:80 na docs.google.code:443.
Po skompilowaniu działa :-)

Efektem działania programu jest pojawienie się nowego pliku toster.txt w Google Docs :-)

Wszystko działa także z kontami w Google Apps.

Teraz parę słów krytyki ;-)
Po pierwsze zwracanie wielu wartości z funkcji jest super, ale tylko do czasu. Bo zawsze trzeba pobrać wszystkie wartości, a że Go chce być pomocne i uznaje za błąd istnienie niewykorzystanej zmiennej to trzeba też używać tych zwracanych wartości. Ja oszukałem bo błędy w większości przypadków tylko wypisuję, w rzeczywistości za każdym razem powinienem stosować taki "idiom":
coś, err = cośIo
if err!=nil {
// obsłuż błąd
return
}

Który jest jak najbardziej OK, ale to, że język do tego zmusza to przeszkadzajka. W ogólnym rozrachunku nie jest to może takie złe, bo w kodzie produkcyjnym trzeba się upewniać czy wszystko poszło OK, ale to męczy. Zresztą często w takich sytuacjach stosuje się sterowanie przepływem przy pomocy wyjątków, co nie jest ładne, ale jest szybsze w pisaniu, czyli robi się serie rzeczy, które mogą spowodować wyjątek, i jeśli wyjątek nie poleci to sterowanie przechodzi do następnej linii, aż wystąpi wyjątek, albo wszystko się uda zrobić.

Po drugie, pomieszanie światów. Raz używając http można stworzyć cały request POST i go wysłać, drugim razem trzeba zniżyć się do net albo crypto/tls czyli do poziomu socketów bo zechcieliśmy wykonać jedną nietypową rzecz, czyli ustawić nagłówki requesta.
W tym miejscu może nawet więcej sensu miałoby użycie tylko net albo crytpo/tls bez bawienia się w requesty.

Po trzecie, to wkurzające wymaganie jawnej konwersji typów. Java robi to milej. Przez tą konwersję trzeba ciągle kombinować. Przez co mam taką linię:
req.ContentLength=int64(len(documentBody))
Bo oczywiście do ContentLength trzeba włożyć int64, a len zwraca "tylko" int.
Strasznie to upierdliwe.

Po czwarte, nadużywanie struktur. Jak widać w źródle, musiałem ukraść ze źródeł Go 2 struktury, nopCloser i readClose, bo komuś się ubzdurało, żeby strumień i jego zamykanie były oddzielnymi bytami, ale akurat do wielu bibliotek trzeba dostarczać strumienie, które są jednocześnie czytalne czy zapisywalne i zamykalne......

Po piątek, zmiany w kompilatorze. To jest na razie język eksperymentalny, choć Google podobno pisze w nim niektóre swoje wewnętrzne narzędzia.
Np. to, że teraz nie używa się średnika bo kompilator sam sobie go potrafi włożyć, nie działa z kompilatorem sprzed pół roku czy jakoś tak. Biblioteki też się zmieniają.

Po szóste, port dla Windows. Nie ma oficjalnego, jest taki mniej oficjalny, który ma np. problemy z HTTPSem.

Dla ciekawskich, ten program wyżej to dziecko jakichś 4 godzin, czyli dość długo powstawał.


Podobne postybeta
Zmienne Go ;-)
Chrome OS, Chrome2Chrome i w ogóle Chrome ;-)
Toperz ;-) czyli OCR + Android odsłona 2 albo któraś tam
Go dla Java'owca ;-) odcinek 1 "klasy"
Wredne Google Docs