Related
I'm trying to access my WordPress site from a NodeJS program. Specifically, I'm trying to create a draft post from within a NodeJS program. I've never done this before, and I can't get it work.
I've tried a few ways of doing this. First I used WPAPI and went through the documentation for that. It seemed to connect and get a response from my site but no post was created.
WPAPI method and response log
I've gotten an application password through the Wordpress control panel and I'm using it in the program. The username is from the same page on my panel.
I had installed the JSON Basic Authentication plugin while working with WPAPI, but I deactivated it when I switched to another method.
(Following the documentation on GitHub for WPAPI, I thought I would need to install the superagent bundle so I could use create() I tried installing superagent, requiring wpapi/superagent, and changing the type to module in the package, but when I tried to run it I got an error message saying the module "wpapi/superagent" could not be found. After that I just went back to the general import.)
import WPAPI from "wpapi"
const wp = new WPAPI({
endpoint: 'https://www.example.com/wp-json',
username: 'username',
password: 'application_password'
})
wp.posts().create({
title: 'test post',
content: 'will this work?'
}).then((res) => {
console.log(res)
})
The log:
[
{
id: 1,
date: '2022-09-11T22:33:39',
date_gmt: '2022-09-11T22:33:39',
guid: { rendered: 'https://example.com/?p=1' },
modified: '2022-09-11T22:33:39',
modified_gmt: '2022-09-11T22:33:39',
slug: 'hello-world',
status: 'publish',
type: 'post',
link: 'https://example.com/2022/09/11/hello-world/',
title: { rendered: 'Hello world!' },
content: {
rendered: '\n' +
'<p>Welcome to WordPress. This is your first post. Edit or delete it, then start writing!</p>\n',
protected: false
},
excerpt: {
rendered: '<p>Welcome to WordPress. This is your first post. Edit or delete it, then start writing!</p>\n',
protected: false
},
author: 1,
featured_media: 0,
comment_status: 'open',
ping_status: 'open',
sticky: false,
template: '',
format: 'standard',
meta: {
bgseo_title: '',
bgseo_description: '',
bgseo_robots_index: '',
bgseo_robots_follow: ''
},
categories: [ 1 ],
tags: [],
aioseo_notices: [],
_links: {
self: [Array],
collection: [Array],
about: [Array],
author: [Array],
replies: [Array],
'version-history': [Array],
'wp:attachment': [Array],
'wp:term': [Array],
curies: [Array]
}
},
_paging: {
total: 1,
totalPages: 1,
links: { 'https://api.w.org/': 'https://example.com/wp-json/' }
}
]
Using Fetch to send the post
Now I'm trying code from a guide I found on Medium, and I'm having pretty much the same problem.
I receive a response that I believe means the connection was successful, but no posts have been created on my site, draft or otherwise.
Here's the code:
import fetch from "node-fetch"
const createdPost = await fetch(`https://www.example.com/wp-json/wp/v2/posts`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Basic ${Buffer.from("username:application_password", "utf-8").toString("base64")}`
},
body: JSON.stringify({
title: 'New post title',
content: 'New content'
})
})
console.log(createdPost)
And this is what is logged:
Response {
size: 0,
[Symbol(Body internals)]: {
body: PassThrough {
_readableState: [ReadableState],
_events: [Object: null prototype],
_eventsCount: 6,
_maxListeners: undefined,
_writableState: [WritableState],
allowHalfOpen: true,
[Symbol(kCapture)]: false,
[Symbol(kCallback)]: null
},
stream: PassThrough {
_readableState: [ReadableState],
_events: [Object: null prototype],
_eventsCount: 6,
_maxListeners: undefined,
_writableState: [WritableState],
allowHalfOpen: true,
[Symbol(kCapture)]: false,
[Symbol(kCallback)]: null
},
boundary: null,
disturbed: false,
error: null
},
[Symbol(Response internals)]: {
type: 'default',
url: 'https://example.com/wp-json/wp/v2/posts',
status: 200,
statusText: 'OK',
headers: {
'access-control-allow-headers': 'Authorization, X-WP-Nonce, Content-Disposition, Content-MD5, Content-Type',
'access-control-expose-headers': 'X-WP-Total, X-WP-TotalPages, Link',
allow: 'GET',
'cache-control': 'max-age=172800',
connection: 'Upgrade, close',
'content-type': 'application/json; charset=UTF-8',
date: 'Mon, 03 Oct 2022 14:43:35 GMT',
expires: 'Wed, 05 Oct 2022 14:43:35 GMT',
link: '<https://example.com/wp-json/>; rel="https://api.w.org/"',
server: 'Apache',
'transfer-encoding': 'chunked',
upgrade: 'h2',
vary: 'Accept-Encoding,Cookie,Origin,User-Agent',
'x-content-type-options': 'nosniff',
'x-robots-tag': 'noindex',
'x-wp-total': '1',
'x-wp-totalpages': '1'
},
counter: 1,
highWaterMark: 16384
}
}
Summary
Both methods seem to connect but no post is created on my site. Can anyone help? I'm stuck and would really appreciate it.
Intro
I am developing an login application using Nextjs on frontend and springboot on backend.
Problem
I am able to login from the frontend which calls the loginAPI named /authenticate developed in springboot and it is successfully returning the authToken.After login I got redirects to home (/allcoupons) page.
In login , I am passing the value of token as response.data like this
const handleSubmit = async (e) => {
// e.preventDefault();
let requestbody = {
username: credentials.username,
password: credentials.password,
};
try {
const response = await axios({
method: "post",
headers: {},
url: "http://localhost:8081/authenticate",
data: requestbody,
});
//console.log("credentials for login = ", credentials);
//console.log("response from api = ", response);
if (response.status === 200) {
router.push(
{
pathname: "/allcoupons",
query: { auth: JSON.stringify(response.data) },
},
undefined,
{
shallow: true
}
);
} else alert("invalid credentials");
} catch (error) {
setTimeout(() => {
setShowAlert(true);
}, 2000);
setShowAlert(false);
}
};
Then I redirects to /allcoupons component
In this component I am using getServerSideProps(). I am able to access the authtoken value from context.query.auth but now I am unable to send this authtoken value as bearer token with another API /allcoupons in the headers.
home.js
import React, { Fragment, useState, useEffect } from "react";
import { useRouter } from "next/router";
import Footer from "./components/footer";
import { Alert } from "flowbite-react";
import axios from "axios";
const Allcoupons = ({ datafromAPI }) => {
const router = useRouter();
//console.log("datafromAPI in components = ", datafromAPI.coupons)
//let data = datafromAPI.coupons;
const val = router.query.auth ? JSON.parse(router.query.auth) : {};
console.log("authtoken form login = ", val);
const [showModal, setShowModal] = useState(false);
.
.
return (
<>
.
.
.
</>
);
};
export async function getServerSideProps(context) {
//Fetch data from get API
//console.log("context = ", context.query.auth);
const token = context.query.auth;
console.log("token = ",context.query.auth)
const res = await axios.get(
`http://localhost:8081/couponstore/v1.0/coupons`,
//using like this
{ headers: { Authorization: `Bearer ${token}` } }
);
const datafromAPI = res.data;
console.log("data from API server = ",datafromAPI);
return { props: { datafromAPI } };
}
Observation
But as soon as I redirects to this page , it endpoint change from /allcoupons to /allcoupons?auth={authtokenvalue} .
Logs/StackTrace
Also this log comes in the terminal
token = "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhcnVuYWJoIiwiZXhwIjoxNjY0NTQwODYwLCJpYXQiOjE2NjQ1MzM2NjB9.Ib3X-6TMyQ3YfoGz-PuS4hehzuHq-N6XHwzLF6cXD2U"
error - [AxiosError: Request failed with status code 403] {
code: 'ERR_BAD_REQUEST',
config: {
transitional: {
silentJSONParsing: true,
forcedJSONParsing: true,
clarifyTimeoutError: false
},
adapter: [Function: httpAdapter],
transformRequest: [ [Function: transformRequest] ],
transformResponse: [ [Function: transformResponse] ],
timeout: 0,
xsrfCookieName: 'XSRF-TOKEN',
xsrfHeaderName: 'X-XSRF-TOKEN',
maxContentLength: -1,
maxBodyLength: -1,
env: { FormData: [Function] },
validateStatus: [Function: validateStatus],
headers: {
Accept: 'application/json, text/plain, */*',
Authorization: 'Bearer "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhcnVuYWJoIiwiZXhwIjoxNjY0NTQwODYwLCJpYXQiOjE2NjQ1MzM2NjB9.Ib3X-6TMyQ3YfoGz-PuS4hehzuHq-N6XHwzLF6cXD2U"',
'User-Agent': 'axios/0.27.2'
},
method: 'get',
url: 'http://localhost:8081/couponstore/v1.0/coupons',
data: undefined
},
request: <ref *1> ClientRequest {
_events: [Object: null prototype] {
abort: [Function (anonymous)],
aborted: [Function (anonymous)],
connect: [Function (anonymous)],
error: [Function (anonymous)],
socket: [Function (anonymous)],
timeout: [Function (anonymous)],
prefinish: [Function: requestOnPrefinish]
},
_eventsCount: 7,
_maxListeners: undefined,
outputData: [],
outputSize: 0,
writable: true,
destroyed: false,
_last: true,
chunkedEncoding: false,
shouldKeepAlive: false,
maxRequestsOnConnectionReached: false,
_defaultKeepAlive: true,
useChunkedEncodingByDefault: false,
sendDate: false,
_removedConnection: false,
_removedContLen: false,
_removedTE: false,
_contentLength: 0,
_hasBody: true,
_trailer: '',
finished: true,
_headerSent: true,
_closed: false,
socket: Socket {
connecting: false,
_hadError: false,
_parent: null,
_host: 'localhost',
_readableState: [ReadableState],
_events: [Object: null prototype],
_eventsCount: 7,
_maxListeners: undefined,
_writableState: [WritableState],
allowHalfOpen: false,
_sockname: null,
_pendingData: null,
_pendingEncoding: '',
server: null,
_server: null,
parser: null,
_httpMessage: [Circular *1],
write: [Function: writeAfterFIN],
[Symbol(async_id_symbol)]: 285969,
[Symbol(kHandle)]: [TCP],
[Symbol(lastWriteQueueSize)]: 0,
[Symbol(timeout)]: null,
[Symbol(kBuffer)]: null,
[Symbol(kBufferCb)]: null,
[Symbol(kBufferGen)]: null,
[Symbol(kCapture)]: false,
[Symbol(kSetNoDelay)]: false,
[Symbol(kSetKeepAlive)]: true,
[Symbol(kSetKeepAliveInitialDelay)]: 60,
[Symbol(kBytesRead)]: 0,
[Symbol(kBytesWritten)]: 0,
[Symbol(RequestTimeout)]: undefined
},
_header: 'GET /couponstore/v1.0/coupons HTTP/1.1\r\n' +
'Accept: application/json, text/plain, */*\r\n' +
'Authorization: Bearer "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhcnVuYWJoIiwiZXhwIjoxNjY0NTQwODYwLCJpYXQiOjE2NjQ1MzM2NjB9.Ib3X-6TMyQ3YfoGz-PuS4hehzuHq-N6XHwzLF6cXD2U"\r\n' +
'User-Agent: axios/0.27.2\r\n' +
'Host: localhost:8081\r\n' +
'Connection: close\r\n' +
'\r\n',
_keepAliveTimeout: 0,
_onPendingData: [Function: nop],
agent: Agent {
_events: [Object: null prototype],
_eventsCount: 2,
_maxListeners: undefined,
defaultPort: 80,
protocol: 'http:',
options: [Object: null prototype],
requests: [Object: null prototype] {},
sockets: [Object: null prototype],
freeSockets: [Object: null prototype] {},
keepAliveMsecs: 1000,
keepAlive: false,
maxSockets: Infinity,
maxFreeSockets: 256,
scheduling: 'lifo',
maxTotalSockets: Infinity,
totalSocketCount: 1,
[Symbol(kCapture)]: false
},
socketPath: undefined,
method: 'GET',
maxHeaderSize: undefined,
insecureHTTPParser: undefined,
path: '/couponstore/v1.0/coupons',
_ended: true,
res: IncomingMessage {
_readableState: [ReadableState],
_events: [Object: null prototype],
_eventsCount: 4,
_maxListeners: undefined,
socket: [Socket],
httpVersionMajor: 1,
httpVersionMinor: 1,
httpVersion: '1.1',
complete: true,
rawHeaders: [Array],
rawTrailers: [],
aborted: false,
upgrade: false,
url: '',
method: null,
statusCode: 403,
statusMessage: '',
client: [Socket],
_consuming: false,
_dumped: false,
req: [Circular *1],
responseUrl: 'http://localhost:8081/couponstore/v1.0/coupons',
redirects: [],
[Symbol(kCapture)]: false,
[Symbol(kHeaders)]: [Object],
[Symbol(kHeadersCount)]: 18,
[Symbol(kTrailers)]: null,
[Symbol(kTrailersCount)]: 0,
[Symbol(RequestTimeout)]: undefined
},
aborted: false,
timeoutCb: null,
upgradeOrConnect: false,
parser: null,
maxHeadersCount: null,
reusedSocket: false,
host: 'localhost',
protocol: 'http:',
_redirectable: Writable {
_writableState: [WritableState],
_events: [Object: null prototype],
_eventsCount: 3,
_maxListeners: undefined,
_options: [Object],
_ended: true,
_ending: true,
_redirectCount: 0,
_redirects: [],
_requestBodyLength: 0,
_requestBodyBuffers: [],
_onNativeResponse: [Function (anonymous)],
_currentRequest: [Circular *1],
_currentUrl: 'http://localhost:8081/couponstore/v1.0/coupons',
[Symbol(kCapture)]: false
},
[Symbol(kCapture)]: false,
[Symbol(kNeedDrain)]: false,
[Symbol(corked)]: 0,
[Symbol(kOutHeaders)]: [Object: null prototype] {
accept: [Array],
authorization: [Array],
'user-agent': [Array],
host: [Array]
},
[Symbol(kUniqueHeaders)]: null
},
response: {
status: 403,
statusText: '',
headers: {
'x-content-type-options': 'nosniff',
'x-xss-protection': '1; mode=block',
'cache-control': 'no-cache, no-store, max-age=0, must-revalidate',
pragma: 'no-cache',
expires: '0',
'x-frame-options': 'DENY',
'content-length': '0',
date: 'Fri, 30 Sep 2022 10:27:40 GMT',
connection: 'close'
},
config: {
transitional: [Object],
adapter: [Function: httpAdapter],
transformRequest: [Array],
transformResponse: [Array],
timeout: 0,
xsrfCookieName: 'XSRF-TOKEN',
xsrfHeaderName: 'X-XSRF-TOKEN',
maxContentLength: -1,
maxBodyLength: -1,
env: [Object],
validateStatus: [Function: validateStatus],
headers: [Object],
method: 'get',
url: 'http://localhost:8081/couponstore/v1.0/coupons',
data: undefined
},
request: <ref *1> ClientRequest {
_events: [Object: null prototype],
_eventsCount: 7,
_maxListeners: undefined,
outputData: [],
outputSize: 0,
writable: true,
destroyed: false,
_last: true,
chunkedEncoding: false,
shouldKeepAlive: false,
maxRequestsOnConnectionReached: false,
_defaultKeepAlive: true,
useChunkedEncodingByDefault: false,
sendDate: false,
_removedConnection: false,
_removedContLen: false,
_removedTE: false,
_contentLength: 0,
_hasBody: true,
_trailer: '',
finished: true,
_headerSent: true,
_closed: false,
socket: [Socket],
_header: 'GET /couponstore/v1.0/coupons HTTP/1.1\r\n' +
'Accept: application/json, text/plain, */*\r\n' +
'Authorization: Bearer "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhcnVuYWJoIiwiZXhwIjoxNjY0NTQwODYwLCJpYXQiOjE2NjQ1MzM2NjB9.Ib3X-6TMyQ3YfoGz-PuS4hehzuHq-N6XHwzLF6cXD2U"\r\n' +
'User-Agent: axios/0.27.2\r\n' +
'Host: localhost:8081\r\n' +
'Connection: close\r\n' +
'\r\n',
_keepAliveTimeout: 0,
_onPendingData: [Function: nop],
agent: [Agent],
socketPath: undefined,
method: 'GET',
maxHeaderSize: undefined,
insecureHTTPParser: undefined,
path: '/couponstore/v1.0/coupons',
_ended: true,
res: [IncomingMessage],
aborted: false,
timeoutCb: null,
upgradeOrConnect: false,
parser: null,
maxHeadersCount: null,
reusedSocket: false,
host: 'localhost',
protocol: 'http:',
_redirectable: [Writable],
[Symbol(kCapture)]: false,
[Symbol(kNeedDrain)]: false,
[Symbol(corked)]: 0,
[Symbol(kOutHeaders)]: [Object: null prototype],
[Symbol(kUniqueHeaders)]: null
},
data: ''
},
page: '/allcoupons'
}
Whats the mistake here??
This how do usually when I create react apps
I create a global context
import { createContext } from "react";
import { GlobalContextType } from "../types";
const defaultValue: GlobalContextType = {
dispatch: () => {},
state: {
user: { authToken: "", username: "" }
},
};
export const GlobalContext = createContext(defaultValue);
create a hook for my global context
import { useContext } from "react";
import { GlobalContext } from "../contexts";
export function useGlobalContext () {
const context = useContext(GlobalContext)
if (!context) throw new Error("Missing Context - Global context");
return context;
}
Create state reducer hook
import { Dispatch, useMemo, useReducer } from "react";
import {
GlobalContextActions,
GlobalContextState,
} from "../types";
export function globalContextReducer(
state: GlobalContextState,
action: ReducerAction<GlobalContextActions>
): GlobalContextState {
switch (action.type) {
case GlobalContextActions.setUser:
return {
...state,
user: action.payload
};
default:
return state;
}
}
export function useGlobalContextReducer(reducer = globalContextReducer) {
const [state, dispatch] = useReducer(reducer, {
user : {authToken: "", username: ""}
});
return useMemo(() => {
return {
dispatch,
state,
};
}, [d, state]);
}
Wrapped the whole App with my global context
import { GlobalContext } from "../src/contexts";
import { useGlobalContextReducer } from "../src/hooks/use-global-context-reducer";
export default function MyApp(props: MyAppProps) {
const { Component pageProps } = props;
const globalContextReducer = useGlobalContextReducer();
return (
<GlobalContext.Provider value={globalContextReducer}>
<Component {...pageProps} />
</GlobalContext.Provider>
);
}
this just my setup
then in my login page
import {GlobalContextActions, useGlobalContext} from "../../context"
import {Routes} from "../routes"
function Login() {
const { dispatch} = useGlobalContext();
const router = useRouter();
const [form, updateForm] = useState({
email: "",
password: "",
});
const onFieldChange = (event: ChangeEvent<HTMLInputElement>) => {
const { target } = event;
const { id, value } = target;
updateForm((current) => ({ ...current, [id]: value }));
};
const isLoginDisabled = !validateEmail(form.email) || !form.password;
const onLogin = async () => {
const dataService = container.resolve(DataService);
const result = await dataService.getLoginData(form.email, form.password);
if (result.error) {
return dispatchError(result?.errorMessage as string);
}
useGlobalContextReducer({action:GlobalContextActions.setUser, payload: result.data});
router.push(Routes.home);
};
return (
<Box>
<form action="" onSubmit={(event) => event?.preventDefault()}>
<TextField
id="email"
label={"email"}
variant="standard"
required
value={form.email}
onChange={onFieldChange}
type="email"
fullWidth
data-testid="email"
/>
<TextField
id="password"
label={"password"}
variant="standard"
type="password"
value={form.password}
required
fullWidth
onChange={onFieldChange}
data-testid="password"
/>
<Button type="submit" variant="contained" disabled={isLoginDisabled} onClick={onLogin}>
Login
</Button>
</form>
</Box>
);
}
export default Login;
and this how I use the global state
function MyPage() {
const {state} = useGlobalContext
return (<div>{JSON.stringinfy(state)}</div>)
}
First of all I do not recommend returning authtoken in response of successful login. You should use httpOnly cookies to set authtoken. It is more secure and you can set it from backend like this.
Second axios provide a way to set Authorization token in request. I mean you can alter default headers like below:
axios.defaults.headers.common['Authorization'] = AUTH_TOKEN;
Read more in doc
Hi I am writing a unit test case of my function. this function returns a json response in case request is successful else return an error.
describe("Operations", async () => {
it("user info", async () => {
let accessToken = 'dfsf;
const result = await authConnectors.getUser(accessToken);
});
});
How to assert http error and response using chai in nodejs ?
{ Error: Unauthorized
at Request.callback (/vagrant/node_modules/superagent/lib/node/index.js:699:13)
at IncomingMessage.parser (/vagrant/node_modules/superagent/lib/node/index.js:903:18)
at emitNone (events.js:91:20)
at IncomingMessage.emit (events.js:185:7)
at endReadableNT (_stream_readable.js:974:12)
at _combinedTickCallback (internal/process/next_tick.js:80:11)
at process._tickCallback (internal/process/next_tick.js:104:9)
status: 401,
response:
Response {
domain: null,
_events: {},
_eventsCount: 0,
_maxListeners: undefined,
res:
IncomingMessage {
_readableState: [Object],
readable: false,
domain: null,
_events: [Object],
_eventsCount: 4,
_maxListeners: undefined,
socket: [Object],
connection: [Object],
httpVersionMajor: 1,
httpVersionMinor: 1,
httpVersion: '1.1',
complete: true,
headers: [Object],
rawHeaders: [Object],
trailers: {},
rawTrailers: [],
upgrade: false,
url: '',
method: null,
statusCode: 401,
statusMessage: 'Unauthorized',
client: [Object],
_consuming: true,
_dumped: false,
req: [Object],
text: '',
read: [Function] },
request:
Request {
domain: null,
_events: {},
_eventsCount: 0,
_maxListeners: undefined,
_agent: false,
_formData: null,
method: 'GET',
url: 'https://id-dev',
_header: [Object],
header: [Object],
writable: true,
_redirects: 0,
_maxRedirects: 5,
cookies: '',
qs: {},
_query: [],
qsRaw: [],
_redirectList: [],
_streamRequest: false,
req: [Object],
protocol: 'https:',
host: 'id-dh',
_endCalled: true,
_callback: [Function],
res: [Object],
response: [Circular],
called: true },
req:
ClientRequest {
domain: null,
_events: [Object],
_eventsCount: 3,
_maxListeners: undefined,
output: [],
outputEncodings: [],
outputCallbacks: [],
outputSize: 0,
writable: true,
_last: true,
upgrading: false,
chunkedEncoding: false,
shouldKeepAlive: false,
useChunkedEncodingByDefault: false,
sendDate: false,
_removedHeader: {},
_contentLength: 0,
_hasBody: true,
_trailer: '',
finished: true,
_headerSent: true,
socket: [Object],
connection: [Object],
_header: 'GET oqcnM_4xWNbmv-WaoJ63UOjcZSHW-KrULLRUFzXrQKS6iuPWp6poHLjop1WsRtw5Y-KQCTZVI783awnn3_utEw6yqD6qpHHZwhxiPeM8mFZgN_zH4xX0ChbQ3ayao9Ms6ZHQT0X00N467HPfq5hVAW4D3a-3DURwnUvvVTIsYTShhZAsag2P26WDbKapPdgfVpjQKV-4GP--cLWbAZpEWJT3yS31YiY-VEcdfjdDU\r\nAccept: application/json\r\nConnection: close\r\n\r\n',
_headers: [Object],
_headerNames: [Object],
_onPendingData: null,
agent: [Object],
socketPath: undefined,
timeout: undefined,
method: 'GET',
path: '/userinfo',
_ended: true,
parser: null,
res: [Object] },
text: '',
body: {},
files: undefined,
buffered: true,
headers:
{ 'content-length': '0',
'strict-transport-security': 'max-age=31536000; includeSubDomains; preload',
'x-xss-protection': '1; mode=block',
'x-content-type-options': 'nosniff',
'content-security-policy': 'default-src \'self\';script-src \'self\' https://track.abc.net;style-src \'self\' \'unsafe-inline\' https://track.abc.net;img-src \'self\' https://track.abc.net;frame-src \'self\' https://track.abc.net;object-src \'none\';',
'www-authenticate': 'Bearer error="invalid_token"',
date: 'Wed, 28 Mar 2018 11:57:56 GMT',
connection: 'close' },
header:
{ 'content-length': '0',
'strict-transport-security': 'max-age=31536000; includeSubDomains; preload',
'x-xss-protection': '1; mode=block',
'x-content-type-options': 'nosniff',
'content-security-policy': 'default-src \'self\';script-src \'self\' https://track.abc.net;style-src \'self\' \'unsafe-inline\' https://track.abc.net;img-src \'self\' https://track.abc.net;frame-src \'self\' https://track.abc.net;object-src \'none\'',
'www-authenticate': 'Bearer error="invalid_token"',
date: 'Wed, 28 Mar 2018 11:57:56 GMT',
connection: 'close' },
statusCode: 401,
status: 401,
statusType: 4,
info: false,
ok: false,
redirect: false,
clientError: true,
serverError: false,
error:
{ Error: cannot GET /sts/connect/userinfo (401)
at Response.toError (/vagrant/node_modules/superagent/lib/node/response.js:94:15)
at ResponseBase._setStatusProperties (/vagrant/node_modules/superagent/lib/response-base.js:123:16)
at new Response (/vagrant/node_modules/superagent/lib/node/response.js:41:8)
at Request._emitResponse (/vagrant/node_modules/superagent/lib/node/index.js:739:20)
at IncomingMessage.parser (/vagrant/node_modules/superagent/lib/node/index.js:903:38)
at emitNone (events.js:91:20)
at IncomingMessage.emit (events.js:185:7)
at endReadableNT (_stream_readable.js:974:12)
at _combinedTickCallback (internal/process/next_tick.js:80:11)
at process._tickCallback (internal/process/next_tick.js:104:9)
status: 401,
text: '',
method: 'GET',
path: '/sts' },
accepted: false,
noContent: false,
badRequest: false,
unauthorized: true,
notAcceptable: false,
forbidden: false,
notFound: false,
type: '',
links: {},
setEncoding: [Function: bound ],
redirects: [] } }
Using expect and should you can test as below:
describe('Testing https responses', () => {
it("user info", async (done) => {
.... request here...
.catch((err) => {
err.response.should.have.status(404);
err.response.body.should.have.property('error');
err.response.body.error.should.eql('User could not be found');
done()
});
})
it('testing using expect', (done) => {
request.get('http://localhost:8000', function (err, res, body) {
expect(res.statusCode).to.equal(400);
expect(res).to.have.property('body');
expect(res.body).to.equal('wrong header');
done();
});
});
})
These tests are asynchronous, so they need the promise to be solved at the end with done()
Edit - Added once more example
.property(name[, val[, msg]])
Asserts that the target has a property with the given key name.
expect({a: 1}).to.have.property('a');
Here three links that can be helpful:
How to properly test for HTTP failure responses
Testing HTTP Responses in Node.js
Michael Herman - Testing Node.js with Mocha and Chai
Chai Doc - method property using expect
You could use try-catch and handle errors =)
it("user info", async () => {
try{
let accessToken = 'dfsf;
const result = await authConnectors.getUser(accessToken);
} catch(e) {
// check error
}
});
I cannot figure out how. I make an HTTPS get request, but I just get a long list of information, but no SessionId
Here is the code I have written in node.js I have replaced my API with a fake one, but other things are the same.
var http = require('http');
//var port = process.env.PORT || 1337;
var https = require('https')
var readline = require('readline');
var url = require('url')
var request = require('request')
var body = ""
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
var options = {
url: "https://SOMEAPI.com/us/oauth/v2/authorize?SOMEPATH",
host: "SOMEAPI.com"
}
var cookie = https.get(options, (response) => {
response.on(('data'), (chunk) => {
body += chunk
})
response.on('end', () => {
//printSession(response)
//console.log(response)
})
})
console.log(cookie)
This is my console output:
Debugger listening on [::]:5858
ClientRequest {
domain: null,
_events:
{ response: { [Function: g] listener: [Function] },
socket: { [Function: g] listener: [Function: onSocket] } },
_eventsCount: 2,
_maxListeners: undefined,
output: [ 'GET / HTTP/1.1\r\nHost: [https://SOMEAPI.com]\r\nConnect
ion: close\r\n\r\n' ],
outputEncodings: [ 'latin1' ],
outputCallbacks: [ [Function: finish] ],
outputSize: 77,
writable: true,
_last: true,
upgrading: false,
chunkedEncoding: false,
shouldKeepAlive: false,
useChunkedEncodingByDefault: false,
sendDate: false,
_removedHeader: {},
_contentLength: 0,
_hasBody: true,
_trailer: '',
finished: true,
_headerSent: true,
socket: null,
connection: null,
_header: 'GET / HTTP/1.1\r\nHost: [https://SOMEAPI.com]\r\nConnecti
on: close\r\n\r\n',
_headers: { host: '[https://SOMEAPI.com]' },
_headerNames: { host: 'Host' },
_onPendingData: null,
agent:
Agent {
domain: null,
_events: { free: [Function] },
_eventsCount: 1,
_maxListeners: undefined,
defaultPort: 443,
protocol: 'https:',
options: { path: null },
requests: {},
sockets: { 'https://SOMEAPI.com:443::::::::[https': [Object] },
freeSockets: {},
keepAliveMsecs: 1000,
keepAlive: false,
maxSockets: Infinity,
maxFreeSockets: 256,
maxCachedSessions: 100,
_sessionCache: { map: {}, list: [] } },
socketPath: undefined,
timeout: undefined,
method: 'GET',
path: '/',
_ended: false }
events.js:160
throw er; // Unhandled 'error' event
^
Error: getaddrinfo ENOTFOUND https://SOMEAPI.com https://SOMEAPI.com:443
at errnoException (dns.js:28:10)
at GetAddrInfoReqWrap.onlookup [as oncomplete] (dns.js:76:26)
Press any key to continue...
Edit: Here is what I am at now:
Debugger listening on [::]:5858
<?xml version="1.0" encoding="UTF-8"?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
<soapenv:Body>
<soapenv:Fault>
<faultcode>soapenv:Server</faultcode>
<faultstring>Policy Falsified</faultstring>
<faultactor>https://SOMEAPI.com/</faultactor>
<detail>
<l7:policyResult
status="Service Not Found. The request may have been sent t
o an invalid URL, or intended for an unsupported operation." xmlns:l7="http://ww
w.layer7tech.com/ws/policy/fault"/>
</detail>
</soapenv:Fault>
</soapenv:Body>
</soapenv:Envelope>
<?xml version="1.0" encoding="UTF-8"?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
<soapenv:Body>
<soapenv:Fault>
<faultcode>soapenv:Server</faultcode>
<faultstring>Policy Falsified</faultstring>
<faultactor>https://SOMEAPI.com/</faultactor>
<detail>
<l7:policyResult
status="Service Not Found. The request may have been sent t
o an invalid URL, or intended for an unsupported operation." xmlns:l7="http://ww
w.layer7tech.com/ws/policy/fault"/>
</detail>
</soapenv:Fault>
</soapenv:Body>
</soapenv:Envelope>
I believe this is header info that might be important:
REQUEST
GET https://SOMEAPI.com/us/oauth/v2/authorize?SOMEPATH HTTP/1.1
Accept: text/html, application/xhtml+xml, application/xml
Host: SOMEAPI.com
Connection: Keep-Alive
I try to build a chat room webpage with node.js/express/redis on the server side.
Following this snips of code : A Message Wall With Long Poll Properties in Node.JS and Express , I succeed to make one node server running correctly. In this example, the res objects are saved in a list, no any transformation is needed.
But I want to run the node app with pm2 cluster mode(-i 4), so I have to save res object into some place that shared between 4 nodes.
I already used redis in my node project, for the express.session. So I want to cache res into redis.
But the problem occured: when I try to sting-lized res object with JSON.stringify(res), I got :
TypeError: Converting circular structure to JSON
My problem is:
how could I save one res object for later use across node cluster, with redis or something else.
Appreciate.
I use util.inspect to print out my container of res (chatroom_id:res) object:
{ '0': null,
'1390640136999':
{ domain: null,
_events:
{ finish: [Object],
header: [Function],
close: [Function: logRequest] },
_maxListeners: 10,
output: [],
outputEncodings: [],
writable: true,
_last: false,
chunkedEncoding: false,
shouldKeepAlive: false,
useChunkedEncodingByDefault: false,
sendDate: true,
_headerSent: false,
_header: '',
_hasBody: true,
_trailer: '',
finished: false,
_hangupClose: false,
socket:
{ _connecting: false,
_handle: [Object],
_readableState: [Object],
readable: true,
domain: null,
_events: [Object],
_maxListeners: 10,
_writableState: [Object],
writable: true,
allowHalfOpen: true,
onend: [Function],
destroyed: false,
errorEmitted: false,
bytesRead: 894,
_bytesDispatched: 0,
_pendingData: null,
_pendingEncoding: '',
server: [Object],
_idleTimeout: 120000,
_idleNext: [Object],
_idlePrev: [Object],
_idleStart: 1390640145289,
parser: [Object],
ondata: [Function],
_paused: false,
_httpMessage: [Circular],
_peername: [Object] },
connection:
{ _connecting: false,
_handle: [Object],
_readableState: [Object],
readable: true,
domain: null,
_events: [Object],
_maxListeners: 10,
_writableState: [Object],
writable: true,
allowHalfOpen: true,
onend: [Function],
destroyed: false,
errorEmitted: false,
bytesRead: 894,
_bytesDispatched: 0,
_pendingData: null,
_pendingEncoding: '',
server: [Object],
_idleTimeout: 120000,
_idleNext: [Object],
_idlePrev: [Object],
_idleStart: 1390640145289,
parser: [Object],
ondata: [Function],
_paused: false,
_httpMessage: [Circular],
_peername: [Object] },
_headers: { 'x-powered-by': 'Express' },
_headerNames: { 'x-powered-by': 'X-Powered-By' },
req:
{ _readableState: [Object],
readable: true,
domain: null,
_events: {},
_maxListeners: 10,
socket: [Object],
connection: [Object],
httpVersion: '1.0',
complete: true,
headers: [Object],
trailers: {},
_pendings: [],
_pendingIndex: 0,
url: '/robot/chat/query/99/1390640136999/270125/',
method: 'GET',
statusCode: null,
client: [Object],
_consuming: false,
_dumped: false,
httpVersionMajor: 1,
httpVersionMinor: 0,
upgrade: false,
originalUrl: '/robot/chat/query/99/1390640136999/270125/',
_parsedUrl: [Object],
query: {},
res: [Circular],
next: [Function: next],
secret: undefined,
cookies: [Object],
signedCookies: {},
sessionStore: [Object],
sessionID: '4PACUldyCHhT8NgdGY1yz9Pk',
session: [Object],
_startTime: Sat Jan 25 2014 16:55:45 GMT+0800 (CST),
_remoteAddress: '127.0.0.1',
body: {},
originalMethod: 'GET',
_route_index: 2,
route: [Object],
params: [Object] },
locals: [Function: locals],
end: [Function],
student_id: '99',
channel_id: '1390640136999',
last_msg_id: '270125' } }
There are three [Circular].
My pseudocode:
/*
* query from http client
* url: /chat/query/:student_id/:channel_id/:last_msg_id/
*/
exports.query = function(req, res){
// if find some new msg
// return them as json, immediately
// else
// set participator info into res object
// read the res_list from redis
// put this res into res_list
// write back res_list into redis
};
/*
* notification from other web server: one new msg been created
* url: /chat/notify/:new_msg_id/
*/
exports.notify = function(req, res){
// get new_msg from database by id
// read the res_list from redis
// for old_res in res_list
// if this old_res is releated with the new_msg (participator)
// old_res.sent(json_content)
// remove this old_res from res_list
// write back res_list into redis
};
how could i implement those 'read-and-write-back' part?
The short answer: it's not possible to put the res object into a cache and obtain it again from another process, in any meaningful way.
If you think about how long poll works, each HTTP client maintains an open connection to the server, waiting (a long time) for something to be sent back. In other words, when you come to push out new messages, you're just sending data down an already open connection. If you cache your res into redis, what would happen to the connection?
It may not matter though, as long as you have a way to pass the content between your 4 backend processes, they can each update their own set of res connections. New connections would still be load balanced.