I use Axios to get cookies from http://localhost/sanctum/csrf-cookie
My app doing requests from localhost:3000 which is configured by Nuxt.
My backend configured by Laravel sanctum at localhost:80
After the GET request, I have the following cookies set in my headers:
Set-Cookie: XSRF-TOKEN=eyJpdiI6InkvSWhzeUtnYzNpck5NSGozS09IVVE9PSIsInZhbHVlIjoiRmR1RVdmYW8zaXYxeWZUNFNjZmkyNjRVKzZQMGk4MExsK3JmOVRPN0s3M3FGK3V1eFpLaTNRYnhhbExvTW5BbmFqVGN2SWRBdUVZcUJkWEJabnJQakEwN1pYNUk1NDBtRFhRSllkTk45ZHZuRWFUZmc5NHViK21JUTVkWFZhZDEiLCJtYWMiOiI5NjBkMWY5YWFmZTgwODE4ZjIzMzdjMjkxMzk3Zjk3YWU0YmI1ZGUzNzAyMmQzZWVhMWQzM2NmYWEwYjdhYTcxIiwidGFnIjoiIn0%3D; expires=Sat, 01-Oct-2022 16:14:59 GMT; Max-Age=7200; path=/; domain=localhost:3000; samesite=lax
Set-Cookie: laravel_session=eyJpdiI6InhNRVBDT1ovanR4QVdzakNHd1YxekE9PSIsInZhbHVlIjoiajNGUGdxa1NJemxiSGIrc1pwZ3VrNFJBbmd6QnFMZkZmZHdWK3ZPSzVWdGZydHBQTGNPRmpocVN3d1lTcTE1d0RLdWFNNEJPbjhLKzVPaEpvSTZzUm5RQWZaQ0ZHVlAxeElBVkErN2hOUnFRTm8wVGJrUllaNXNmTm50N1plTFoiLCJtYWMiOiIxNGZmNTYzYmFkMmY2NjAzNGQwMTIwMzhlYWNjYTI4MjQzNTM0N2Y4Mzk3MzkwYTdmYzU4MDFiMGVkZGU3NjVjIiwidGFnIjoiIn0%3D; expires=Sat, 01-Oct-2022 16:14:59 GMT; Max-Age=7200; path=/; domain=localhost:3000; httponly; samesite=lax]
But there are no cookies in the application tab:
What is wrong?
In your .env file add
SESSION_DOMAIN=.localhost
Check CORS
'paths' => ['*'],
'allowed_methods' => ['*'],
'allowed_origins' => ['*'], // All origins
'allowed_origins_patterns' => [],
'allowed_headers' => ['*'],
'exposed_headers' => [],
'max_age' => 0,
'supports_credentials' => true,
For your nuxt project, use #nuxtjs/auth-next package.
The real problem was wrong credentials support in axios module.
This should be the credentials: true, rather than withCredentials: true
axios: {
credentials: true,
baseURL: "http://localhost", // Used as fallback if no runtime config is provided
// withCredentials: true,
headers: {
common: {
"Access-Control-Allow-Origin": "*",
'Access-Control-Allow-Methods':'GET,PUT,POST,DELETE,PATCH,OPTIONS',
'Access-Control-Allow-Credentials': true
},
delete: {},
get: {},
head: {},
post: {},
put: {},
patch: {},
},
},
Related
I recently just deployed my Nuxt Js application and i am having trouble with authentication.
I have set the stateful and session domain in my .env as seen below
SESSION_DRIVER=file
SESSION_LIFETIME=120
SESSION_DOMAIN=api.example.com
SANCTUM_STATEFUL_DOMAINS=example.com
i also have this in my cors.php
'paths' => ['api/*', 'sanctum/csrf-cookie',],
'allowed_methods' => ['*'],
'allowed_origins' => ['*'],
'allowed_origins_patterns' => [],
'allowed_headers' => ['*'],
'exposed_headers' => [],
'max_age' => 0,
'supports_credentials' => true,
In my nuxt.config.js i have the following
auth: {
strategies: {
cookie: {
endpoints: {
csrf: {
url: '/sanctum/csrf-cookie'
},
login: {
url: '/api/login',
},
register: {
url: '/api/register',
},
logout: {
url: '/api/logout',
},
user: {
url: '/api/user',
}
},
user: {
property: 'data'
},
}
},
redirect: {
login: '/auth/login',
logout: '/auth/login',
home: '/dashboard',
},
plugins: [
'~/plugins/axios'
]
},
// Axios module configuration: https://go.nuxtjs.dev/config-axios
axios: {
// Workaround to avoid enforcing hard-coded localhost:3000: https://github.com/nuxt-community/axios-module/issues/308
baseURL: 'http://api.example.com',
credentials: true
},
I do not know what i am doing wrong could someone please help
In your .env file, Change your SESSION_DOMAIN from api.example.com to .example.com
And change your axios baseUrl from http://localhost to your api endpoint
I have problem in a Axios Laravel crossite, which returns this erorr:
Access to XMLHttpRequest at 'http://localhost/moddle/moodle/login/index.php' from origin 'http://localhost:8000' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.
I have done follow like laravel-cors and answer on stackoverflow, none of them worked.
This is my Axios:
let axiosConfig = {
headers: {
"Access-Control-Allow-Origin": "*",
'Content-Type': 'application/json;',
"Content-Type": "application/x-www-form-urlencoded",
}
};
axios.post('http://localhost/moddle/moodle/login/index.php', response.data, {
crossOrigin: null,
}, axiosConfig).then(
response => {
console.log(true);
window.location.href = "http://localhost/moddle/moodle/my/";
}).catch(erorr => {
});
In my cors.php
'paths' => ['api/*', 'sanctum/csrf-cookie'],
'allowed_methods' => ['*'],
'allowed_origins' => ['*'],
'allowed_origins_patterns' => [],
'allowed_headers' => ['*'],
'exposed_headers' => [],
'max_age' => 0,
'supports_credentials' => false,
In my middleware
protected $middleware = [
// \App\Http\Middleware\TrustHosts::class,
\Fruitcake\Cors\HandleCors::class,
\App\Http\Middleware\TrustProxies::class,
\App\Http\Middleware\PreventRequestsDuringMaintenance::class,
\Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
\App\Http\Middleware\TrimStrings::class,
\Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
//
// \App\Http\Middleware\Cors::class,
];
You can add into TrustHosts.php Middleware without doing anything extra. read the complete solution here: https://stackoverflow.com/a/70361284/2612926
Please do the following to resolve the issue.
php artisan config:cache
I'm using Laravel Passport to handle my oauth2 login implementation. Following videos from Dre Himself. Everything works fine when on localhost but had issues logging in the real site now I want to deploy my application to DigitalOcean, and after setting up nginx, .env file, composer, npm, setup passport (php artisan passport:install) and database using (php artisan migrate). It all seem to work fine until my login returned 200 with a response payload of
<!DOCTYPE html>\n<html lang=\"en\">\n <head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n\n <title>Resume Workshop</title>\n\n <!-- Fonts -->\n <link href=\"https://fonts.googleapis.com/css2?family=Montserrat:wght#400;700;900&display=swap\" rel=\"stylesheet\"> \n <link rel=\"stylesheet\" href=\"https://myresumeworkshop.com/css/app.css\">\n </head>\n <body>\n <main id=\"app\"></main>\n <script src=\"https://myresumeworkshop.com/js/app.js\"></script>\n </body>\n</html>\n
Instead of the correct response the bearer access token. The funny thing is when I use insomnia to do the post request to the /oauth/token endpoint it returns the token and I can manually insert the token into my localstorage and the application will work perfectly normal.
Here's my login code on the backend:
public function login(Request $request) {
$http = new \GuzzleHttp\Client;
try {
$response = $http->post(config('services.passport.login_endpoint'), [
'form_params' => [
'grant_type' => 'password',
'client_id' => config('services.passport.client_id'),
'client_secret' => config('services.passport.client_secret'),
'username' => $request->username,
'password' => $request->password,
]
]);
// return json_decode((string) $response->getBody(), true)
return $response->getBody();
}
catch (\GuzzleHttp\Exception\BadResponseException $e) {
if ($e->getCode() === 400) {
return response()->json('Incorrect username or password', $e->getCode());
} else if ($e->getCode() === 401) {
return response()->json('Your credentials are incorrect. Please try again', $e->getCode());
}
return response()->json('Something went wrong on the server.', $e->getCode());
}
}
and my code on the frontend side:
retrieveToken(context, data) {
return new Promise((resolve, reject) => {
axios
.post("/login", {
username: data.username,
password: data.password
})
.then(response => {
const token = response.data.access_token;
localStorage.setItem("access_token", token);
context.commit('retrieveToken', token);
resolve(response);
}).catch(error => {
// console.log(error)
reject(error)
});
})
}
When I console.log response.data it will output the !doctype html as above. I suspect maybe it's due to the Guzzle client header content-type: text/html instead of application/json. Or it could be due to my nginx config.
Here's a die and dump of the $response from the guzzle post request
GuzzleHttp\Psr7\Response {#334
-reasonPhrase: "OK"
-statusCode: 200
-headers: array:6 [
"Server" => array:1 [
0 => "nginx/1.14.0 (Ubuntu)"
]
"Content-Type" => array:1 [
0 => "text/html; charset=UTF-8"
]
"Connection" => array:1 [
0 => "close"
]
"Cache-Control" => array:1 [
0 => "no-cache, private"
]
"Date" => array:1 [
0 => "Sat, 01 Aug 2020 22:45:37 GMT"
]
"Set-Cookie" => array:3 [
0 => "XSRF-TOKEN=eyJpdiI6IlNlWWU4NzA2Q0ZLcFdQeWR0dHlra2c9PSIsInZhbHVlIjoiNTZWSlRmUk80UzViV2FXSVFPRGlmTHdGXC9kVmNxRG1oRHNwcllyNHhqOXNOZk0zTloxYlZ3bkwrbG03eTF1SkUiLCJtYWMiOiIyOTIyZWY5ODMyNTNiMDc5Y2JiOTI4ZDEwYjM0YTczYzcxNzgyZWJiYTA1NjM2YzM3MTBhN2U1MTEwZWE5ZjM5In0%3D; expires=Sun, 02-Aug-2020 00:45:37 GMT; Max-Age=7200; path=/"
1 => "resume_workshop_session=eyJpdiI6InlkWTlHWnBqVXZnOFwvRndUOGduQWV3PT0iLCJ2YWx1ZSI6IlRpaXlaTmJzZUl2MlVvRlBFakhVK0FIeDV3K0xXMnlVWmpVUTNjWDBHbFJ1UkNVbWZQSTVxcXg3RHNvZUF0NXYiLCJtYWMiOiJlNTI2YjQyNjRjMGZhODNhNjhkNjdhOWQ1MzQ5YWY2MmNlZTAwODk3M2NiYmI5YTJiNDQxMDUxNWQ3NzNiZmY3In0%3D; expires=Sun, 02-Aug-2020 00:45:37 GMT; Max-Age=7200; path=/; httponly"
2 => "LfsxdWN0U7YDplqYkRiAHeaRYayLUBKvUToCvfV3=eyJpdiI6IkdEU1NVVnk5VE9ESzNvNjNGTTYwdUE9PSIsInZhbHVlIjoidEZ3WjR5dDdSR0Zya1Z5UkpnVk5JU3FVWWJCczhXOHkxd0dud0FpVDhkK29cL2NKemNZNTAwS1N6alpxSm0yNU13bGRFREhRS3BnSWd6RDZFc05MNXJsN3FFWktNTjBzcjkyVHVHS1hNN0NuZEZvZFZVak85SUQrRWRPaFRQK3BtYTc1YU4wOHlVbmlkdnNrWGVYcUhQcmRPbjExXC8rUldyTFwvMFwvVUltaGk3QzZcL1d0RVA1SVFIbzgxVmZVSjE5SnFJazZqd1d4Z000QjB4MWxLVThJd1FYQjN2QTlCeGp3WjZIRlpFd0VHU3ZcL3p3N2lsRXdOOGhuXC9iUWJMd3Nmb0FwaENIOW1qTVU5dWJWZkVTXC9oeXoxRUpTODRxT3hmcExoWW85cG9DOGFGaDNyUmxRdDJGYlpBbkNpZXNSbGNIYlNZbTN4REhjREkrV0doK0c5Vk1kWW82RERCN1NDRlhWcFBLVU5tWDdXdGM9IiwibWFjIjoiMmRhYzlhZGVhZWIwMGVmZDdiYTJiNWUwYTAwNDQ0MTgxMTY1ZDY2MmU5MWE0MWE5ODA5ZGJlNWIyYjE2NzY5MSJ9; expires=Sun, 02-Aug-2020 00:45:37 GMT; Max-Age=7200; path=/; httponly"
]
]
-headerNames: array:6 [
"server" => "Server"
"content-type" => "Content-Type"
"connection" => "Connection"
"cache-control" => "Cache-Control"
"date" => "Date"
"set-cookie" => "Set-Cookie"
]
-protocol: "1.1"
-stream: GuzzleHttp\Psr7\Stream {#333
-stream: stream resource #26
wrapper_type: "PHP"
stream_type: "TEMP"
mode: "w+b"
unread_bytes: 0
seekable: true
uri: "php://temp"
options: []
}
-size: null
-seekable: true
-readable: true
-writable: true
-uri: "php://temp"
-customMetadata: []
}
}
This problem was simpler than I thought.
The token returns null because the object I'm pointing to doesn't exist. And it's because I wasn't hitting the right endpoint.
In my .env file I reduced the PASSPORT_LOGIN_ENDPOINT from the entire url http://myweb.com/oauth/token to just /oauth/token.
This wasn't an issue during development. Only during production when you already have a real domain instead of localhost it appears.
I am trying to do an axios GET call on my NativeScript-Vue application to an ASP.Net WebAPI backend.
On a VueJS web application, I am using the following code:
axios.get(url).then((response) => {
console.log(response)
}, (err) => {
console.log(err)
})
And the above code works fine. But as soon as I use it to my NativeScript Vue application, I am not getting anything.
The console.log shows the following:
status: null,
statusText: '',
headers: {},
config:
{ adapter: { [Function: xhrAdapter] [prototype]: [Object], [name]: 'xhrAdapter', [length]: 1 },
transformRequest: { '0': [Object] },
transformResponse: { '0': [Object] },
timeout: 0,
xsrfCookieName: 'XSRF-TOKEN',
xsrfHeaderName: 'X-XSRF-TOKEN',
maxContentLength: -1,
validateStatus: { [Function: validateStatus] [prototype]: [Object], [name]: 'validateStatus', [length]: 1 },
headers: { Accept: 'application/json, text/plain, */*' },
method: 'get',
url: 'THE_URL_HERE',
data: undefined },
request:
{ UNSENT: 0,
OPENED: 1,
HEADERS_RECEIVED: 2,
LOADING: 3,
DONE: 4,
_responseType: '',
textTypes:
[ 'text/plain',
'application/xml',
'application/rss+xml',
'text/html',
'text/xml',
[length]: 5 ],
_listeners: {},
_readyState: 4,
_options:
{ url: 'THE_URL_HERE',
method: 'GET',
headers: [Object] },
timeout: 0,
onreadystatechange: { [Function: handleLoad] [length]: 0, [name]: 'handleLoad', [prototype]: [Object] },
onerror: { [Function: handleError] [length]: 0, [name]: 'handleError', [prototype]: [Object] },
ontimeout: { [Function: handleTimeout] [length]: 0, [name]: 'handleTimeout', [prototype]: [Object] },
_errorFlag: true,
_response: null,
_responseTextReader: null,
_headers: null,
_status: null } }
Following the instructions posted on this article: Make Http Requests
I am able to get some data (pretty much I replaced my url in my sample code above to point to the URL in the article.
I did some investigation and I also found out that in the Chrome Debugging Tools, this is what's being returned by Make HTTP Requests article:
status: 200,
statusText: 'OK',
headers:
{ 'content-type': 'application/json',
'access-control-allow-origin': '*',
'set-cookie':
[ '__cfduid=d0755ff1a9e3a35137412056bfab86b221539838285; expires=Fri, 18-Oct-19 04:51:25 GMT; path=/; domain=.pokeapi.co; HttpOnly; Secure',
[length]: 1 ],
server: 'cloudflare',
'access-control-allow-methods': 'GET, OPTIONS',
'content-encoding': 'br',
'access-control-allow-headers': 'Authorization, Origin, X-Requested-With, Content-Type, Accept',
'expect-ct': 'max-age=604800, report-uri="https://report-uri.cloudflare.com/cdn-cgi/beacon/expect-ct"',
date: 'Thu, 18 Oct 2018 04:51:25 GMT',
'cf-ray': '46b863c5a8552a4f-SEA',
vary: 'Accept-Encoding',
'last-modified': 'Sat, 22 Sep 2018 23:55:29 GMT' },
config:
{ adapter: { [Function: xhrAdapter] [length]: 1, [name]: 'xhrAdapter', [prototype]: [Object] },
transformRequest: { '0': [Object] },
transformResponse: { '0': [Object] },
timeout: 0,
xsrfCookieName: 'XSRF-TOKEN',
xsrfHeaderName: 'X-XSRF-TOKEN',
maxContentLength: -1,
validateStatus: { [Function: validateStatus] [length]: 1, [name]: 'validateStatus', [prototype]: [Object] },
headers: { Accept: 'application/json, text/plain, */*' },
method: 'get',
url: 'https://pokeapi.co/api/v2/pokemon/?limit=151',
data: undefined },
request:
{ UNSENT: 0,
OPENED: 1,
HEADERS_RECEIVED: 2,
LOADING: 3,
DONE: 4,
_responseType: 'json',
textTypes:
[ 'text/plain',
'application/xml',
'application/rss+xml',
'text/html',
'text/xml',
[length]: 5 ],
_listeners: {},
_readyState: 4,
_options:
{ url: 'https://pokeapi.co/api/v2/pokemon/?limit=151',
method: 'GET',
headers: [Object] },
timeout: 0,
onreadystatechange: { [Function: handleLoad] [length]: 0, [name]: 'handleLoad', [prototype]: [Object] },
onerror: { [Function: handleError] [length]: 0, [name]: 'handleError', [prototype]: [Object] },
ontimeout: { [Function: handleTimeout] [length]: 0, [name]: 'handleTimeout', [prototype]: [Object] },
_errorFlag: false,
_response: { count: 949, next: null, previous: null, results: [Object] },
_responseTextReader:
{ [Function]
[arguments]: null,
[caller]: null,
[length]: 0,
[name]: '',
[prototype]: [Object] },
_headers:
{ 'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
'Set-Cookie': '__cfduid=d0755ff1a9e3a35137412056bfab86b221539838285; expires=Fri, 18-Oct-19 04:51:25 GMT; path=/; domain=.pokeapi.co; HttpOnly; Secure',
Server: 'cloudflare',
'access-control-allow-methods': 'GET, OPTIONS',
'Content-Encoding': 'br',
'access-control-allow-headers': 'Authorization, Origin, X-Requested-With, Content-Type, Accept',
'expect-ct': 'max-age=604800, report-uri="https://report-uri.cloudflare.com/cdn-cgi/beacon/expect-ct"',
Date: 'Thu, 18 Oct 2018 04:51:25 GMT',
'cf-ray': '46b863c5a8552a4f-SEA',
Vary: 'Accept-Encoding',
'Last-Modified': 'Sat, 22 Sep 2018 23:55:29 GMT' },
_status: 200 } }
I am already quite stuck on what to do next for this issue. Notice there are some difference on the console.log(response) using my URL, and the URL provided on the article.
I tried my API call in Postman and I am getting some data.
I also tried the Pokemon API call in Postman and getting some data as well. So not really sure which thing to investigate next.
UPDATE: For those asking the URL of my WebAPI, I have it here:
http://angeloaa-001-site1.itempurl.com/menucategory
You can try that link even in the browser (or postman) and you would see the data being returned as well.
Looking forward to some insights and responses,
One thing I notice is that your api does not return the Access-Control-Allow-Origin header in the response, it's just an empty object headers: {}
If you look at the response from the Make HTTP Requests article, it's includes the header 'access-control-allow-origin': '*'
And why do you need this header?
By default, browser implements the same origin policy which basically means that your browser will only allow your code to fetch data from the same origin. So if your api and your client is under different domains, you need to tell the browser to allow communication between the two. This is done by enabling CORS, cross origin request sharing, which basically is controlled by the Access-Control-Allow-Origin header in the api response.
To enable the cors in the api, follow this
I am trying to perform an upload to BigQuery from Perl with a sample schema and some sample data. I ran into dead ends following the documentation they provide, and so now I'm trying to mimic what the bq command line client successfully does.
I am tracing what bq does by adding a debug print (method, uri, headers, body) to the request method in httplib2. I am tracing what my Perl library is doing by doing a Dumper on the response, which also includes the _request that I sent. The pattern in bq is that they POST to an upload URL, then get back a location to PUT data to. The corresponding job is monitored with a series of GET requests, and finally they respond.
In Perl my POST succeeds, and my GET fails with Invalid Upload Request (but no hint why it is invalid). I am trying to figure out what difference between the two could explain my failure. But I can't find it.
Here are (with the access_token, IP addresses and project_id elided) the traces that I get.
For the POST the information from Python is:
(
u'POST',
u'https://www.googleapis.com/upload/bigquery/v2/projects/<project ID>/jobs?uploadType=resumable&alt=json',
{
'content-length': '442',
'accept-encoding': 'gzip, deflate',
'accept': 'application/json',
'user-agent': u'bq/2.0 google-api-python-client/1.0',
'X-Upload-Content-Length': '84',
'X-Upload-Content-Type': 'application/octet-stream',
'content-type': 'application/json',
'Authorization': u'Bearer <access token>'
},
'{"configuration": {"load": {"sourceFormat": "NEWLINE_DELIMITED_JSON", "destinationTable": {"projectId": "<project id>", "tableId": "demo_api", "datasetId": "tmp_bt"}, "maxBadRecords": 0, "schema": {"fields": [{"type": "STRING", "mode": "required", "name": "demo_string"}, {"type": "INTEGER", "mode": "required", "name": "demo_integer"}]}}}, "jobReference": {"projectId": "<project id>", "jobId": "bqjob_r139e633b7e522cf7_0000014031d9fb49_1"}}'
)
The corresponding Perl gets an apparently successful response object (in which you can see the _request) of:
$VAR1 = bless( {
'_protocol' => 'HTTP/1.1',
'_content' => '',
'_rc' => '200',
'_headers' => bless( {
'connection' => 'close',
'client-response-num' => 1,
'location' => 'https://www.googleapis.com/upload/bigquery/v2/projects/<project id>/jobs?uploadType=resumable&upload_id=AEnB2Ur0mdwmZpMot6ftkgj1IkqK0f7oPbZrXWQekUDHK_E2o2HKznJO6DK2xPYCB-nhUGrMrEJJ7z1Tz9Crnka9e5EYGP1lWQ',
'date' => 'Tue, 06 Aug 2013 20:46:05 GMT',
'client-ssl-cert-issuer' => '/C=US/O=Google Inc/CN=Google Internet Authority',
'client-ssl-cipher' => 'RC4-SHA',
'client-peer' => '<some ip>:443',
'content-length' => '0',
'client-date' => 'Tue, 06 Aug 2013 20:46:05 GMT',
'content-type' => 'text/html; charset=UTF-8',
'client-ssl-cert-subject' => '/C=US/ST=California/L=Mountain View/O=Google Inc/CN=*.googleapis.com',
'server' => 'HTTP Upload Server Built on Jul 24 2013 17:20:01 (1374711601)',
'client-ssl-socket-class' => 'IO::Socket::SSL'
}, 'HTTP::Headers' ),
'_msg' => 'OK',
'_request' => bless( {
'_content' => '{"configuration":{"load":{"maxBadRecords":0,"destinationTable":{"datasetId":"tmp_bt","tableId":"perl","projectId":<project id>},"sourceFormat":"NEWLINE_DELIMITED_JSON","schema":{"fields":[{"mode":"required","name":"demo_string","type":"STRING"},{"mode":"required","name":"demo_integer","type":"INTEGER"}]}}},"jobReference":{"projectId":<project id>,"jobId":"perlapi_1375821964"}}',
'_uri' => bless( do{\(my $o = 'https://www.googleapis.com/upload/bigquery/v2/projects/<project id>/jobs?uploadType=resumable')}, 'URI::https' ),
'_headers' => bless( {
'user-agent' => 'libwww-perl/6.05',
'content-type' => 'application/json',
'accept' => 'application/json',
':X-Upload-Content-Type' => 'application/octet-stream',
'content-length' => 379,
':X-Upload-Content-Length' => '84',
'authorization' => 'Bearer <access token>'
}, 'HTTP::Headers' ),
'_method' => 'POST',
'_uri_canonical' => $VAR1->{'_request'}{'_uri'}
}, 'HTTP::Request' )
}, 'HTTP::Response' );
And then we have a PUT. On the Python side we sent:
(
'PUT',
'https://www.googleapis.com/upload/bigquery/v2/projects/<project id>/jobs?uploadType=resumable&alt=json&upload_id=AEnB2UpWMRCAOffqyR0d7zvGVtD-KWhrC9jGB-q_igecJgoyz_mIHgEFfs9cYoPxUwUxuflQScMzGxDsKKJ_CJPQq4Os-AkdZA',
{
'Content-Range': 'bytes 0-83/84',
'Content-Length': '84',
'Authorization': u'Bearer <access token>',
'user-agent': u'bq/2.0'
},
<apiclient.http._StreamSlice object at 0x10ce11150>
)
(I have verified that the stream slice object has the same 84 bytes as Perl.) And here is the Perl failure:
$VAR1 = bless( {
'_protocol' => 'HTTP/1.1',
'_content' => '{
"error": {
"errors": [
{
"domain": "global",
"reason": "badRequest",
"message": "Invalid Upload Request"
}
],
"code": 400,
"message": "Invalid Upload Request"
}
}
',
'_rc' => '400',
'_headers' => bless( {
'connection' => 'close',
'client-response-num' => 1,
'date' => 'Tue, 06 Aug 2013 20:46:07 GMT',
'client-ssl-cert-issuer' => '/C=US/O=Google Inc/CN=Google Internet Authority',
'client-ssl-cipher' => 'RC4-SHA',
'client-peer' => '<some IP address>:443',
'content-length' => '193',
'client-date' => 'Tue, 06 Aug 2013 20:46:07 GMT',
'content-type' => 'application/json',
'client-ssl-cert-subject' => '/C=US/ST=California/L=Mountain View/O=Google Inc/CN=*.googleapis.com',
'server' => 'HTTP Upload Server Built on Jul 24 2013 17:20:01 (1374711601)',
'client-ssl-socket-class' => 'IO::Socket::SSL'
}, 'HTTP::Headers' ),
'_msg' => 'Bad Request',
'_request' => bless( {
'_content' => '{"demo_string":"foo", "demo_integer":"2"}
{"demo_string":"bar", "demo_integer":"3"}
',
'_uri' => bless( do{\(my $o = 'https://www.googleapis.com/upload/bigquery/v2/projects/<project id>/jobs?uploadType=resumable&upload_id=AEnB2Ur0mdwmZpMot6ftkgj1IkqK0f7oPbZrXWQekUDHK_E2o2HKznJO6DK2xPYCB-nhUGrMrEJJ7z1Tz9Crnka9e5EYGP1lWQ')}, 'URI::https' ),
'_headers' => bless( {
'user-agent' => 'libwww-perl/6.05',
':Content-Length' => '84',
':Content-Range' => '0-83/84',
'content-length' => 84,
'authorization' => 'Bearer <access token>'
}, 'HTTP::Headers' ),
'_method' => 'PUT',
'_uri_canonical' => $VAR1->{'_request'}{'_uri'}
}, 'HTTP::Request' )
}, 'HTTP::Response' );
What should I try changing on the Perl side to make BigQuery respond to me like it does to bq?
Some of your PUT headers have colons in front of them, where the Python does not:
':Content-Length' => '84',
':Content-Range' => '0-83/84',
I suspect there is something malformed in the multipart upload request. The error "Invalid Upload Request" is in response to attempting to split the data payload out of the multipart mime message. Your logging doesn't include details on the body of the requests, so we can't side-by-side compare them for unexpected differences.
To make sure that the problem is the multipart upload, you could try a load request that loads data from Google Storage rather than including the data in the request payload itself. This would verify that the perl api request path is working for you.
FYI: There is an alpha Perl Google Apis client that might help you out. I haven't tried it and don't know whether it's actively in development or not, but you might find some useful hints there. Check out https://code.google.com/p/google-api-perl-client/