I'm using Indy with Lazarus
Here is my code:
IdHTTP1.Request.ContentType := 'text/plain' ;
IdHTTP1.Response.ContentType := 'text/plain' ;
IdHTTP1.Response.Charset := 'ISO-8859-1,utf-8;q=0.7,*;q=0.3' ;
IdHTTP1.Request.CharSet:= 'ISO-8859-1,utf-8;q=0.7,*;q=0.3 ' ;
IdHTTP1.HTTPOptions := IdHTTP1.HTTPOptions + [hoNoProtocolErrorException];
IdHTTP1.Get('http://192.168.25.965:8541/rest/SearchCard('+MYCARD+')',Stream) ;
If I start MYCARD with a letter, the server is picking up the full string. However, if I start with a number, it stops at the first letter.
MYCARD:= '12366854'; //works
MYCARD:= 'A125ASD555'; //Works
MYCARD:= '123YH963'; // The server only sees 123
What am I doing wrong?
First off, the two Request properties you are setting are meaningless in a GET request, and you should not be setting any Response properties at all.
// get rid of these assignments
//IdHTTP1.Request.ContentType := 'text/plain' ;
//IdHTTP1.Response.ContentType := 'text/plain' ;
//IdHTTP1.Response.Charset := 'ISO-8859-1,utf-8;q=0.7,*;q=0.3' ;
//IdHTTP1.Request.CharSet:= 'ISO-8859-1,utf-8;q=0.7,*;q=0.3 ' ;
IdHTTP1.HTTPOptions := IdHTTP1.HTTPOptions + [hoNoProtocolErrorException];
IdHTTP1.Get('http://192.168.25.965:8541/rest/SearchCard('+MYCARD+')', Stream);
Second, using the current version of Indy, I cannot reproduce your issue. TIdHTTP.Get() sends the specified URL as-is, it makes no assumptions about the characters in it (you are responsible for URL encoding). In my testing, 123YH963 works just fine. Here is the actual HTP request being sent:
GET /rest/SearchCard(123YH963) HTTP/1.1
Host: 192.168.25.965:8541
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,/;q=0.8
User-Agent: Mozilla/3.0 (compatible; Indy Library)
As you can see, the full MYCARD text is in the requested resource, as expected. So any truncation must be happening on the server side, not in TIdHTTP itself.
Are you sure you are formatting the URL correctly to begin with? Are you sure it should actually be sent like this:
/rest/SearchCard(123YH963)
And not something more like these instead?
/rest/SearchCard%28123YH963%29
/rest/SearchCard/123YH963
/rest/SearchCard?param=123YH963
Related
I created a simple http2 server,
If I send a request to it with curl, it responds with some headers, although I did not set them explicity. How can I acces them inside the requesthandling function ( sayhello )? My code ( I've never used golang before)
server.go
package main
import (
"net/http"
"strings"
"fmt"
"github.com/gorilla/mux"
"golang.org/x/net/http2"
)
func sayHello(w http.ResponseWriter, r *http.Request) {
message := r.URL.Path
message = strings.TrimPrefix(message, "/")
message = "Hello " + message
w.Header().Set("myFirst", "golangQuestion")
w.Write([]byte(message))
for k, v := range w.Header() {
fmt.Println("[RESPONSE][Header]", k,":", v)
}
}
func main() {
router := mux.NewRouter()
router.PathPrefix("/").HandlerFunc(sayHello) // catch everything else rule
var srv = &http.Server{
Addr: "127.0.0.1:8081",
}
http2.ConfigureServer(srv, nil)
srv.Handler = router
sslCert := "./ssl.cert"
sslKey := "./ssl.key"
if err := srv.ListenAndServeTLS(sslCert, sslKey); err != nil {
panic(err)
}
}
Sending request:
curl --head --insecure https://127.0.0.1:8081
HTTP/1.1 200 OK
Myfirst: golangQuestion
Date: Tue, 18 Jun 2019 09:18:29 GMT
Content-Length: 6
Content-Type: text/plain; charset=utf-8
I can see that some headers are sent back, the one which I set explicitly is also recieved, but the output of
go run server.go
[RESPONSE][Header] Myfirst : [golangQuestion]
How can I acces the other headers, which were not explicitly set, but recieved by curl as well? I loopd through w.Headers, but it did not contain the implicitly set headers
for k, v := range w.Header() {
fmt.Println("[RESPONSE][Header]", k,":", v)
}
My expectation that the output of go run server.go shall be something like this:
[RESPONSE][Header] Myfirst : [golangQuestion]
[RESPONSE][Header] Date: [2019.02.12 ]
[RESPONSE][Header] Content-Length: [6]
Those headers are sent automatically when you call ResponseWriter.Write(). Quoting from its doc:
// Write writes the data to the connection as part of an HTTP reply.
//
// If WriteHeader has not yet been called, Write calls
// WriteHeader(http.StatusOK) before writing the data. If the Header
// does not contain a Content-Type line, Write adds a Content-Type set
// to the result of passing the initial 512 bytes of written data to
// DetectContentType. Additionally, if the total size of all written
// data is under a few KB and there are no Flush calls, the
// Content-Length header is added automatically.
//
// Depending on the HTTP protocol version and the client, calling
// Write or WriteHeader may prevent future reads on the
// Request.Body. For HTTP/1.x requests, handlers should read any
// needed request body data before writing the response. Once the
// headers have been flushed (due to either an explicit Flusher.Flush
// call or writing enough data to trigger a flush), the request body
// may be unavailable. For HTTP/2 requests, the Go HTTP server permits
// handlers to continue to read the request body while concurrently
// writing the response. However, such behavior may not be supported
// by all HTTP/2 clients. Handlers should read before writing if
// possible to maximize compatibility.
Write([]byte) (int, error)
ResponseWriter.Header() contains only the headers set explicitly. The Content-Type and Content-Length were sent by w.Write().
Note: if you want to suppress such automatic headers, you have to set their values to nil, e.g.:
w.Header()["Date"] = nil
Also note that if you set the values of such headers manually, those values will be sent without being changed.
I try to post a form to server and here is the code:
ar request = new http.MultipartRequest("POST", _uri);
request.fields['user_acc'] = _userAcc;
// this issue should be solve
request.fields['user_nick_name'] = '中文名字';
request.fields['user_password'] = _password;
But the server side in the user_nick_name field always got null, note that is always, but I change it into English the server can receive that. I test on postman, the server can got Chinese correctly, so it's MultipartRequest issue on this problem.
My question is: Why the Dart or Flutter team so careless on this so important basic library? They even not consider about this simply issue. I opened a issue on github but no-one response, I think the team is done. So I ask the develop communit here, how to solve this problem anyway?
[UPDATE]
As kindly people suggested, I update my golang server now, if anyone else got this problem, you may wonna answer and suggestions too.
func HandleUserRegister(context *gin.Context) {
userAcc := context.PostForm("user_acc")
userAvatar := context.PostForm("user_avatar")
userNickName := context.PostForm("user_nick_name")
userPassword := context.PostForm("user_password")
userPhone := context.PostForm("user_phone")
userEmail := context.PostForm("user_email")
userGender := context.PostForm("user_gender")
userSign := context.PostForm("user_sign")
userType := context.PostForm("user_type")
userTypeInt, _ := strconv.Atoi(userType)
log.Infof("userAcc: %s, userNickName: %s, userPassword: %s", userAcc, userNickName, userPassword)}
This is based on gin, and this function is the api solver. If anyone wanna help, please help me figure it out.
OK! I update the question now, because it's really weird!. I did those test:
Post multiform via Flutter to Django server, it receives Chinese filed correctly;
Post multiform data via Postman, the golang(gin) server gots Chinese correctly;
Post multiform data via Flutter to golang(gin) server gots Chinese field null;
For more detail, I log the headers from my server for both postman(normal) and flutter (abnormal):
Postman:
request header: map[Content-Type:[multipart/form-data; boundary=--------------------------022341683711652813100488] Postman-Token:[855646d7-5bea-4b8f-b8df-81366226cd49] User-Agent:[PostmanRuntime/7.1.1] Content-Length:[422] Connection:[keep-alive] Cache-Control:[no-cache] Accept:[*/*] Accept-Encoding:[gzip, deflate]]
Flutter:
request header: map[User-Agent:[Dart/2.0 (dart:io)] Content-Type:[multipart/form-data; boundary=dart-http-boundary-.XUeYeqXpg4Yfyh8QhH1T5JB4zi_f3WxX9t7Taxhw91EFqhyki4] Accept-Encoding:[gzip] Content-Length:[574]]
Does anyone can notice the difference and let me know how to change the it make server can receive the Chinese Characters?
#DannyTuppeny is correct. This is a server problem.
When asked to include a non-ASCII field into a multi-part request, the Dart library correctly wraps this with a binary content-transfer-encoding.
String _headerForField(String name, String value) {
var header =
'content-disposition: form-data; name="${_browserEncode(name)}"';
if (!isPlainAscii(value)) {
header = '$header\r\n'
'content-type: text/plain; charset=utf-8\r\n'
'content-transfer-encoding: binary';
}
return '$header\r\n\r\n';
}
(Postman does not and simply sends the utf8 encoded string without any headers.)
Dart/ASCII looks like this:
--dart-http-boundary-HjDS88CmQicdgd8VaHSwPqJK8iR4H6rTG3LovSZy-QXGpU7pAB0
content-disposition: form-data; name="test"
stackover
--dart-http-boundary-HjDS88CmQicdgd8VaHSwPqJK8iR4H6rTG3LovSZy-QXGpU7pAB0
Dart/non-ASCII looks like this:
First boundary: --dart-http-boundary-58NU6u6_Fo22xjH8H7yPCtKuoKgB+A8+RTJ82iIK1gs3nnGMLlp\r\n
Encapsulated multipart part: (text/plain)
content-disposition: form-data; name="test"\r\n
content-type: text/plain; charset=utf-8\r\n
content-transfer-encoding: binary\r\n\r\n
Line-based text data: text/plain
\344\270\255\346\226\207\345\220\215\345\255\227
Boundary: \r\n--dart-http-boundary-58NU6u6_Fo22xjH8H7yPCtKuoKgB+A8+RTJ82iIK1gs3nnGMLlp\r\n
So the problem is that the server is unable to unwrap the value from the encapsulation.
EDIT
Here's the Postman trace I captured yesterday. It's multi-form, but fails to add the content-type-encoding header despite the field being non-ASCII.
MIME Multipart Media Encapsulation, Type: multipart/form-data, Boundary: "--------------------------595246000077585285134204"
[Type: multipart/form-data]
First boundary: ----------------------------595246000077585285134204\r\n
Encapsulated multipart part:
Content-Disposition: form-data; name="name"\r\n\r\n
Data (12 bytes)
0000 e4 b8 ad e6 96 87 e5 90 8d e5 ad 97 ............
Data: e4b8ade69687e5908de5ad97
[Length: 12]
Last boundary: \r\n----------------------------595246000077585285134204--\r\n
I tested by posting to httpbin and the response suggests that the characters were posted correctly:
"user_nick_name":"\u4e2d\u6587\u540d\u5b57"
I tried with both the Stable v1 SDK and a v2 SDK from Flutter. Is it possible the issue is on the server? Have you tried using something like Fiddler to capture what's actually being sent?
Edit: My guess is that your server side code is not correctly reading the data as MultipartForm data (eg. you should be using ParseMultipartForm and reading from MultipartForm).
The problem, it appears, is in formdata.go part of multipart. Go assumes that any multipart part with an Content-Type header is a file (not a field). However, knowing this you can change your server code as follows:
func main() {
r := gin.Default()
r.POST("/sotest", func(c *gin.Context) {
formValue := c.PostForm("form_value")
if formValue == "" {
formFile, _ := c.FormFile("form_value")
file, _ := formFile.Open()
b1 := make([]byte, formFile.Size)
file.Read(b1)
formValue = string(b1)
}
c.JSON(200, gin.H{
"status": "posted",
"formValue": formValue,
})
})
r.Run() // listen and serve on 0.0.0.0:8080
}
When you detect that PostForm returns the empty string, you know that Go has treated the field as a file, in which case you can Open and Read the 'file' and decode it as the utf-8 string that we know it is. Obviously, you could encapsulate the "try as PostForm and if that's empty, try as FormFile" test into a function.
If you don't want to have to test for empty string at the server, you could change your Dart end code to always utf-8 encode even non-ascii strings with
request.files.add(
new http.MultipartFile.fromBytes(
'some_form_value_name',
utf8.encode('the string value'),
contentType: new MediaType('text', 'plain', {'charset': 'utf-8'}),
),
);
and read them at the server with the Open/Read/string method.
I have now solved this. Thanks to Richard and Danny for their help.
1. Reason for this
No matter what happens but this really not only one-side problem, we can not say it's Flutter or Go wrong. But the combination, Flutter + Go server just may be got this issue. The behind reason I still not quit sure, but it must some head not right set (postman can do it right);
2. Solution
We don't only need know why but also how to solve it. Here is what I do:
Do not use the official http package. Using dio, which is a extension Dart package. link: https://pub.dartlang.org/packages/dio
It's more clean and easy to use, so my code becomes to:
FormData _formData = new FormData.from({
"user_acc": _userAcc,
"user_nick_name": _userNickName,
'user_password': _password,
});
Dio dio = new Dio();
Response response = await dio.post(usersUrl, data: _formData);
print(response.data);
I can not post the none-English words now:
INFO[0668] userAcc: ww, userNickName: 小鹿叮叮婴儿湿巾手口专用80抽湿纸巾婴儿湿巾婴儿100抽带盖批发【原价】34.90元【券后】9.9元【省】25元【复制此信息打开手机淘宝即可查看并下单】¥Tnsx0E77pFs¥【必买理由】新品预售80抽*3仙女联盟,更多优惠fd.loliloli.pro , userPassword: ww
INFO[0671] user exist.
I have a problem with doing by myself a http.post() request.
I want to perform API requests on NodeMCU based on ESP8266 with Lua language.
The first problem that i met was "Plain HTTP on HTTPS adress".
For now it says "bad token", so, it means that he didn't receive my post parameters.
How it need to be correct?
http.post("http://www.2level.1level/api.php","Content-Type: text/plain","token=mytokenhere&arg1=argumentforrequest",
function(code, data)
if (code < 0) then
print("HTTP request failed")
else
print(code, data)
end
end)
Usually i use GMod Lua for making requests. Code there will be easy:
http.Post("https://www.2level.1level/api.php",{token=mytokenhere,arg1=argumentforrequest},function(txt) end,function(txt) end)
http.Post on GMod Lua Wiki
==================
Update. I made my own code.
function ghttp.Post(url, parameters, onSuccess, onFailure, headers)
local add = ""
if parameters then
local temp = {}
for k,v in pairs(parameters) do
table.insert(temp,ghttp.encode(k).."="..ghttp.encode(v))
end
add = "?"..table.concat(temp, "&")
end
http.post(url..add,"Content-Type: application/json\r\n"..(headers or ""),"",function(code, data)
if (code < 0) then
if onFailure then onFailure() end
else
if onSuccess then onSuccess(code,data) end
end
end)
end
But now i have new problem:
Some API's request only HTTPs connection.
You didn't give us a URL to verify (would be hard because of the token, I know). So, untested the correct code would be like this:
http.post('https://www.2level.1level/api.php',
'Content-Type: application/json\r\n',
'{token=mytokenhere,arg1=argumentforrequest}',
function(code, data)
if (code < 0) then
print("HTTP request failed")
else
print(code, data)
end
end)
Make sure your {token=mytokenhere,arg1=argumentforrequest} is valid JSON by verifying with e.g. jsonlint.com.
In case you run into problems with HTTPS then this might be https://github.com/nodemcu/nodemcu-firmware/issues/1707.
I am a Golang api that accept multipart/form-data requests. For some clients, however, it fails to parse the form because it doesn't like the boundary being used by the client.
The header from the client is:
Content-Type:[multipart/form-data; boundary================1648430772==]
I've narrowed this down to the ParseMediaType function in the mime package.
If I call:
bad := "multipart/form-data; boundary=1650458473"
d, params, err := mime.ParseMediaType(v)
if err != nil {
fmt.Println("err", err)
}
fmt.Println(d, params)
I get the err: mime: invalid media parameter.
Note that if I do this call with
multipart/form-data; boundary=3fc88aad6d1341a4921fd5ac9efe607c
it succeeds no problem.
According to the https://www.w3.org/Protocols/rfc1341/7_2_Multipart.html spec, it looks to me like these are all valid characters for a boundary.
Is this a bug in the Go mime library? Or is this really an invalid boundary?
The rfc you linked to contains BNF for the boundary and multipart body, it does not contain the BNF for the Content-Type Header Field. So while = in boundary is just fine it's not fine in the parameter value of the Content-Type header. At least not unquoted.
So to fix your first example change the Content-Type to this:
multipart/form-data; boundary="===============1648430772=="
https://play.golang.org/p/3Iuk_ACZaQ
Your second example multipart/form-data; boundary=1650458473 seems to work fine.
https://play.golang.org/p/xJWwBa_QiP
Finally found the answer. In the RFC 2045 doc (https://www.ietf.org/rfc/rfc2045.txt) it states that certain values cannot be used as parameter values in the Content-Type header.
The pertinent section:
tspecials := "(" / ")" / "<" / ">" / "#" /
"," / ";" / ":" / "\" / <">
"/" / "[" / "]" / "?" / "="
; Must be in quoted-string,
; to use within parameter values
So you can use an equal sign, but only if it's quoted, so Go fails on the parsing. The client in this case is sending a technically-incorrect value for the boundary param.
I've been banging my head against a wall for 3 days now trying to get this to work.
POST /oauth2/v3/token HTTP/1.1
Host: www.googleapis.com
Content-length: 495
Content-type: application/x-www-form-urlencoded
Authorization: Bearer ya29.cgEcY6meBrvaH6oe0nD_PtsFyMVqskiUYi7iJxapKHeEgPoIw8gMt0BJdIvRn1MfcEgzTS3_gTwI1w
grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer&assertion=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiI5MDgyOTgxNjA1NTktc2R1bGFpbWhsaGpxOTY5M2s1Z2E4c25pZjhh%0D%0ANzhlZ3BAZGV2ZWxvcGVyLmdzZXJ2aWNlYWNjb3VudC5jb20iLCJzY29wZSI6Imh0%0D%0AdHBzOi8vd3d3Lmdvb2dsZWFwaXMuY29tL2F1dGgvYW5hbHl0aWNzLnJlYWRvbmx5%0D%0AIiwiYXVkIjoiaHR0cHM6Ly9hY2NvdW50cy5nb29nbGUuY29tL28vb2F1dGgyL3Rv%0D%0Aa2VuIiwiZXhwIjoxNDMxNTE0MDUyLCJpYXQiOjE0MzE1MTEwNTJ9.[Cert]
HTTP/1.1 400 Bad Request
Content-length: 67
X-xss-protection: 1; mode=block
X-content-type-options: nosniff
Expires: Wed, 13 May 2015 10:08:00 GMT
Vary: Origin,X-Origin
Server: GSE
Cache-control: private, max-age=0
Date: Wed, 13 May 2015 10:08:00 GMT
X-frame-options: SAMEORIGIN
Content-type: application/json; charset=UTF-8
{
"error_description": "Bad Request",
"error": "invalid_grant"
}
So, I'm creating this in PL/SQL. Oracle 12c. I've managed to get the JWT header and and JWT Claim set to produce an output the same as the google documentation. When i create the cert I think is were the problem occurs.
Do I need the "[-----BEGIN PRIVATE KEY-----" and -----END PRIVATE KEY-----\n] as part of the SHA-256 encryption. Should I do anything with the "New lines"? \n Should I surround it with brackets?
At what point should I be URL encoding?
Is the code in the following example sufficient for the encryption?: http://jastraub.blogspot.co.uk/2009/07/hmacsha256-in-plsql.html
I've attached the a function below to see if you can identify any issues?
Thanks for your help!
FUNCTION get_JWT (p_token_id ga_app_user.ID_TOKEN%TYPE)
RETURN VARCHAR2
IS
--Plain text
baseJWTheader VARCHAR2 (20000);
baseclaimSet VARCHAR2 (20000);
baseSigKey VARCHAR2 (20000);
--Seconds
sysSeconds NUMBER;
--Base64 Encoded
JWTheader VARCHAR2 (20000);
claimSet VARCHAR2 (20000);
sigKey VARCHAR2 (20000);
sigContent VARCHAR2 (20000);
--Returned value
output RAW (20000);
BEGIN
SELECT JWT_HEADER, JWT_CLAIM_SET, SIGNATURE
INTO baseJWTheader, baseclaimSet, baseSigKey
FROM dwman.ga_app_user au
WHERE AU.ID_TOKEN = p_token_id;
--DBMS_OUTPUT.PUT_LINE ('Base claim Set ' || baseclaimSet);
JWTheader :=
TRANSLATE (
UTL_RAW.cast_to_varchar2 (
UTL_ENCODE.BASE64_ENCODE (UTL_RAW.CAST_TO_RAW (baseJWTheader))),
'+/',
'-_');
SELECT ( SYSDATE
- TO_DATE ('01-01-1970 00:00:00', 'DD-MM-YYYY HH24:MI:SS'))
* 24
* 60
* 60
INTO sysSeconds
FROM DUAL;
baseclaimSet :=
REPLACE (baseclaimSet, '#EXPIRE#', ROUND (sysSeconds + 3000));
baseclaimSet := REPLACE (baseclaimSet, '#START#', ROUND (sysSeconds));
--DBMS_OUTPUT.PUT_LINE ('Claim Set ' || baseclaimSet);
claimSet := UTL_RAW.cast_to_varchar2 (
UTL_ENCODE.BASE64_ENCODE (UTL_RAW.CAST_TO_RAW (baseclaimSet)));
sigKey := baseSigKey;
sigContent := JWTheader || '.' || claimSet;
--DBMS_OUTPUT.PUT_LINE('Sig Content '||sigContent);
sigContent := REPLACE (sigContent, CHR (10), '');
sigContent := REPLACE (sigContent, CHR (13), '');
/*
FOR V_TR in 1..length(sigContent)
LOOP
DBMS_OUTPUT.PUT_LINE (substr(sigContent,V_TR,1)||'='||to_char(ASCII(substr(sigContent,V_TR,1))));
END LOOP;
*/
sigContent :=
sigContent
|| '.'
|| google_signature (sigContent, sigKey);
RETURN UTL_URL.ESCAPE(sigContent, TRUE, 'UTF-8');
END get_JWT;
Your code snippet might only be a portion of what you did, but it seemed to be lacking much of the OAUTH steps required by Google to connect.
You can get more detail on what those steps are by looking at this URL:
https://developers.google.com/accounts/docs/OAuth2WebServer#offline
For the remainder of this answer, I have described my own experiences, doing something similar (downloading data from GA, and uploading it to a database using SQL Statements).
Start by getting a consumerKey and consumerSecret for your Google project. You'll need to have a URL that Google can redirect to, both when you request your consumer Key, but also to supply to Google during your OAuth calls. They have to match.
The next step is to send a GET request to Google. Here is an example in c# which you can build using SQL string concatenation.
String URL_AUTH_FIRST = "https://accounts.google.com/o/oauth2/auth";
String URL_TOKEN_ENDPOINT_SECOND = "https://accounts.google.com/o/oauth2/token";
String url = String.Format(
"{0}?client_id={1}&redirect_uri={2}&access_type=offline&scope={3}&response_type=code&state={4}&approval_prompt=force",
URL_AUTH_FIRST,_consumerKey_web_app,redir_url,scope,state);
You will need to have an embedded browser to do this. Google.com will redirect that browser to a site that's under their control so that the user has to be able to login or refuse to authorize your app. After Google has the information they need, they redirect back to your embedded browser. You can do some of the steps using copy/paste in your own browser, but at some point (described below) you have to POST back some data which I wouldn't know if you can do that from a browser application.
Google will respond by redirecting your embedded browser to a url. The URL has data on it. You have to parse the parameters on the URL and look for the parameter "code". If you get a url with "code" as a parameter, you have to POST a format in a certain format back to Google.
WebClient client = get_WebClient(); // proprietary to include things like proxy info
try {
NameValueCollection values = new NameValueCollection();
values.Add("client_id", _consumerKey_web_app);
values.Add("client_secret", _consumerSecret_web_app_offline);
values.Add("grant_type", "authorization_code");
values.Add("redirect_uri", URL_GOOGLE_REDIRECTS_TO_THIS_URL_AFTER_URL_AUTH);
values.Add("code", authorization_code);
Byte[] responseBytes = client.UploadValues(URL_TOKEN_ENDPOINT_SECOND, values);
}
Google will return "responseBytes" which would be a json formatted string, resembling:
{
"access_token":"1/fFAGRNJru1FTz70BzhT3Zg",
"expires_in":3920,
"token_type":"Bearer",
"refresh_token":"1/6BMfW9j53gdGImsixUH6kU5RsR4zwI9lUVX-tqf8JXQ"
}
The access_token gets appended to your REST API calls.
You can supply a GA Query via REST API, get the data back, and upload it into your database using SQL statements. That's what my App does.
You can save that refresh_token and supply it in future connections. Indeed, this whole sequence needs to be done with a browser or browser control, interactively with a user logging in. After it's done, and you've received the refresh_token, then your SQL can use and re-use the refresh_token basically in-definitely, at least until the user's password changes.
Google will also return an error 401 on a regular basis. This just means you have to re-request your access token by Posting a new set of values to Google:
NameValueCollection values = new NameValueCollection();
values.Add("client_id", _consumerKey_web_app);
values.Add("client_secret", _consumerSecret_web_app_offline);
values.Add("refresh_token", refresh_token);
values.Add("grant_type", "refresh_token");
Byte[] responseBytes = client.UploadValues(URL_TOKEN_ENDPOINT_SECOND, values);