Golang : Resumable upload to Google Drive(RESTful) example
This tutorial is a supplement to previous tutorial on how to upload file to Google Drive via REST API with MULTIPART upload method . This time, we will use the resumable upload method.
Before you start, please turn on the Google Drive API if you haven't do so and download the credential file - client_secret.json
by following the Step 1: Turn on the Drive API
section found in https://developers.google.com/drive/v3/web/quickstart/go#prerequisites
Once you've downloaded the credential file, move it to the same directory as the source code below before running the program.
NOTE:
You will need to change the file to be uploaded - installgoogledrive.dmg
to something else that you have.
When prompted for the authorization code for the first time, cut-n-paste the URL from your terminal to your browser, then you will get a string(token), cut-n-paste that string into your terminal where you execute the program.
In case you get Authorization error in the API JSON reply, you will need to authorize the Drive API v3 scope at https://developers.google.com/oauthplayground/
Next, do a go get command
go get google.golang.org/api/drive/v3
Finally, enable the Drive API by going to https://developers.google.com/drive/v3/web/enable-sdk and follow the instruction listed below To enable the Drive API, complete these steps:
Run this code example below and observe the changes to your own Google Drive content at https://drive.google.com/drive/my-drive
Here comes the RESUMABLE upload method!
package main
import (
"encoding/json"
"fmt"
"golang.org/x/net/context"
"golang.org/x/oauth2"
"golang.org/x/oauth2/google"
"google.golang.org/api/drive/v3"
"io/ioutil"
"log"
"net/http"
"net/url"
"os"
"os/user"
"path/filepath"
"strconv"
"strings"
)
// NOTE : we don't want to visit CSRF URL to get the authorization code
// and paste into the terminal each time we want to send an email
// therefore we will retrieve a token for our client, save the token into a file
// you will be prompted to visit a link in your browser for authorization code only ONCE
// and subsequent execution of the program will not prompt you for authorization code again
// until the token expires.
// getClient uses a Context and Config to retrieve a Token
// then generate a Client. It returns the generated Client.
func getClient(ctx context.Context, config *oauth2.Config) *http.Client {
cacheFile, err := tokenCacheFile()
if err != nil {
log.Fatalf("Unable to get path to cached credential file. %v", err)
}
tok, err := tokenFromFile(cacheFile)
if err != nil {
tok = getTokenFromWeb(config)
saveToken(cacheFile, tok)
}
return config.Client(ctx, tok)
}
// getTokenFromWeb uses Config to request a Token.
// It returns the retrieved Token.
func getTokenFromWeb(config *oauth2.Config) *oauth2.Token {
authURL := config.AuthCodeURL("state-token", oauth2.AccessTypeOffline)
fmt.Printf("Go to the following link in your browser then type the "+
"authorization code: \n%v\n", authURL)
var code string
if _, err := fmt.Scan(&code); err != nil {
log.Fatalf("Unable to read authorization code %v", err)
}
tok, err := config.Exchange(oauth2.NoContext, code)
if err != nil {
log.Fatalf("Unable to retrieve token from web %v", err)
}
return tok
}
// tokenCacheFile generates credential file path/filename.
// It returns the generated credential path/filename.
func tokenCacheFile() (string, error) {
usr, err := user.Current()
if err != nil {
return "", err
}
tokenCacheDir := filepath.Join(usr.HomeDir, ".credentials")
os.MkdirAll(tokenCacheDir, 0700)
return filepath.Join(tokenCacheDir,
url.QueryEscape("google-drive-golang.json")), err
}
// tokenFromFile retrieves a Token from a given file path.
// It returns the retrieved Token and any read error encountered.
func tokenFromFile(file string) (*oauth2.Token, error) {
f, err := os.Open(file)
if err != nil {
return nil, err
}
t := &oauth2.Token{}
err = json.NewDecoder(f).Decode(t)
defer f.Close()
return t, err
}
// saveToken uses a file path to create a file and store the
// token in it.
func saveToken(file string, token *oauth2.Token) {
fmt.Printf("Saving credential file to: %s\n", file)
f, err := os.Create(file)
if err != nil {
log.Fatalf("Unable to cache oauth token: %v", err)
}
defer f.Close()
json.NewEncoder(f).Encode(token)
}
func main() {
ctx := context.Background()
// process the credential file
credential, err := ioutil.ReadFile("client_secret.json")
if err != nil {
log.Fatalf("Unable to read client secret file: %v", err)
}
// In order for POST upload attachment to work
// You need to authorize the Gmail API v1 scope
// at https://developers.google.com/oauthplayground/
// otherwise you will get Authorization error in the API JSON reply
// Use DriveScope for this example. Because of we want to Manage the files in
// Google Drive.
// See the rest at https://godoc.org/google.golang.org/api/drive/v3#pkg-constants
config, err := google.ConfigFromJSON(credential, drive.DriveScope)
if err != nil {
log.Fatalf("Unable to parse client secret file to config: %v", err)
}
client := getClient(ctx, config)
// initiate a new Google Drive service
//driveClientService, err := drive.New(client)
//if err != nil {
// log.Fatalf("Unable to initiate new Drive client: %v", err)
// }
// get our token
cacheFile, err := tokenCacheFile()
if err != nil {
log.Fatalf("Unable to get path to cached credential file. %v", err)
}
token, err := tokenFromFile(cacheFile)
if err != nil {
log.Fatalf("Unable to get token from file. %v", err)
}
// here comes the Resumable Upload REST method
// https://developers.google.com/drive/v3/web/manage-uploads#resumable
postURL := "https://www.googleapis.com/upload/drive/v3/files?uploadType=resumable"
// read file for upload purpose
fileName := "installgoogledrive.dmg" // <------------ CHANGE HERE!
fileBytes, err := ioutil.ReadFile(fileName)
if err != nil {
log.Fatalf("Unable to read file for upload: %v", err)
}
fileMIMEType := http.DetectContentType(fileBytes)
fileSize := len(string(fileBytes))
// extract auth or access token from Token file
// see https://godoc.org/golang.org/x/oauth2#Token
authToken := token.AccessToken
uploadData := []byte("\n" +
"{ \n" +
string('"') + "name" + string('"') + ":" + string('"') + fileName + string('"') + "\n" +
"} \n\n")
// STEP 1: initiate a resumable session for the Drive API.
request, _ := http.NewRequest("POST", postURL, strings.NewReader(string(uploadData)))
request.Header.Add("Host", "www.googleapis.com")
request.Header.Add("Authorization", "Bearer "+authToken)
request.Header.Add("Content-Length", "38")
request.Header.Add("Content-Type", "application/json; charset="+string('"')+"UTF-8"+string('"'))
request.Header.Add("X-Upload-Content-Type", fileMIMEType)
// optional - if we use chunked transfer encoding
request.Header.Add("X-Upload-Content-Length", strconv.Itoa(fileSize))
// debug
//fmt.Println(request)
response, err := client.Do(request)
if err != nil {
log.Fatalf("Unable to be post to Google API: %v", err)
}
defer response.Body.Close()
_, err = ioutil.ReadAll(response.Body)
if err != nil {
log.Fatalf("Unable to read Google API response: %v", err)
}
// STEP 2: Save the resumable session URI
// see https://golang.org/pkg/net/http/#Header
//fmt.Println("Status : ", response.Status)
//fmt.Println("Header : ", response.Header)
//fmt.Println("Location : ", response.Header["Location"])
session_uri := ""
if response.Status == "200 OK" {
session_uri = response.Header["Location"][0]
} else {
log.Fatalf("Unable to get session URI and response status is not 200")
}
// STEP 3: Upload the file
putRequest, _ := http.NewRequest("PUT", session_uri, strings.NewReader(string(fileBytes)))
putRequest.Header.Add("Content-Length", strconv.Itoa(fileSize))
putRequest.Header.Add("Content-Type", fileMIMEType)
putResponse, err := client.Do(putRequest)
if err != nil {
log.Fatalf("Unable to be post to Google API: %v", err)
}
defer response.Body.Close()
_, err = ioutil.ReadAll(response.Body)
if err != nil {
log.Fatalf("Unable to read Google API response: %v", err)
}
// STEP 4 -
// if put response returns 201 Created or 200 OK - file uploaded successfully and everything is ok
// if put response returns 5xx - follow the procedure outlined in
// resume an interrupted upload.
// see https://developers.google.com/drive/v3/web/manage-uploads#resume-upload
// debug
//fmt.Println("-----------------------------------------")
//fmt.Println("put status : ", putResponse.Status) // returns 200 OK instead of 201 Created
if putResponse.Status == "200 OK" || putResponse.Status == "200 Created" {
fmt.Println(fileName + " uploaded to Drive via REST API")
} else {
fmt.Println("Server responded : ", putResponse.Status)
log.Fatalf("Try uploading again or implement the exponential backoff strategy -- some complicated stuff that sane people should avoid ")
}
// NOTE : While it is NOT recommended to upload file in small chunks
// you can see
// https://www.socketloop.com/tutorials/golang-how-to-split-or-chunking-a-file-to-smaller-pieces
// on how chunk file into smaller pieces to use resumable file upload request
}
References:
https://developers.google.com/drive/v3/web/manage-uploads#resumable
https://www.socketloop.com/tutorials/golang-how-to-split-or-chunking-a-file-to-smaller-pieces
See also : Golang : Google Drive API upload and rename example
By Adam Ng
IF you gain some knowledge or the information here solved your programming problem. Please consider donating to the less fortunate or some charities that you like. Apart from donation, planting trees, volunteering or reducing your carbon footprint will be great too.
Advertisement
Tutorials
+12.1k Golang : Search and extract certain XML data example
+22.4k Golang : untar or extract tar ball archive example
+8k Golang : Auto-generate reply email with text/template package
+5.6k Unix/Linux : How to open tar.gz file ?
+6.5k Golang : Reverse by word
+37.4k Golang : Comparing date or timestamp
+7.6k Setting $GOPATH environment variable for Unix/Linux and Windows
+6.9k Golang : Get Alexa ranking data example
+46k Golang : Marshal and unmarshal json.RawMessage struct example
+15.1k Golang : invalid character ',' looking for beginning of value
+17.3k Golang : Defer function inside init()
+12.4k Golang : Listen and Serve on sub domain example