Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Getting "The response is not writable." error #148

Closed
gage117 opened this issue Jun 3, 2020 · 22 comments
Closed

Getting "The response is not writable." error #148

gage117 opened this issue Jun 3, 2020 · 22 comments
Labels
working as designed This is working as intended

Comments

@gage117
Copy link

gage117 commented Jun 3, 2020

This is my first time using Oak, and I'm running into an error that is making me scratch my head quite a bit. I can provide more code if need but it seems isolated to this chunk. I'm getting this error:

error: Uncaught Error: The response is not writable.
      throw new Error("The response is not writable.");
            ^
    at Response.set body (https://deno.land/x/oak/response.ts:120:13)
    at file:///C:/Users/TARS/projects/deno-elixir/src/app.ts:31:24

but I don't even have to adjust my response to get it to go away. I can just comment out the line above setting the response and it works. Returns "random string" in the body like expected.:

.get('/games', async (context) => {
		// const gamesResponse = await API.get("/games");
		context.response.body = "random string";
	})

But if I uncomment that line back into the code, don't change the response body and keep it as that random string, it crashes with that error.
This errors out:

.get('/games', async (context) => {
		const gamesResponse = await API.get("/games");
		context.response.body = "random string";
	})

I have no idea what could be causing that line to be affecting the next line, but the API.get request is fulfilled, I get a 200 and the data I'm expecting to store in the variable gamesResponse, but something about context.response.body doesn't like that line.

Apologies if the problem is obvious, I'm still a junior dev and this is my first dive into Deno and using this package.

@kitsonk kitsonk added the need info Further information requested label Jun 3, 2020
@kitsonk
Copy link
Collaborator

kitsonk commented Jun 3, 2020

I am afraid I can't guess at what API.get("/games") does.

The error occurs when the response has been processed, most likely something is calling .toServerResponse() before you set the context.response.body.

@gage117
Copy link
Author

gage117 commented Jun 3, 2020

It's just a library for cleaning up HTTP fetches to an API (https://api-v3.igdb.com/), here's exactly what it does in a fetch call instead:

	.get('/games', async (context) => {
		//! const gamesResponse = await API.get("/games");
		const gamesResponse = await fetch("https://api-v3.igdb.com/games", {
			headers: {
				"user-key": "my-user-key"
			}
		})
		context.response.body = gamesResponse;
	})

That's what I thought too after looking at the files in the repo for where the error was occurring and seeing that's the only function that could make #writable false. Not sure what could be triggering that function though.

@gage117
Copy link
Author

gage117 commented Jun 3, 2020

Just in case you need it for reference here's the repo, the error is in src/app.ts:25:24 currently.

@hviana
Copy link
Contributor

hviana commented Jun 4, 2020

this error also occurs when I call: await Deno.readFile ....

@kitsonk kitsonk added needs investigation Something that needs to be looked at and removed need info Further information requested labels Jun 4, 2020
@magichim
Copy link
Contributor

magichim commented Jun 6, 2020

In post route case, this problem is same.

const { value } = await ctx.request.body();
ctx.response.status = Status.OK;
ctx.response.body = { result: value };

It looks await timing issue.
if ctx.response.body is called synchronous, it is not failed. but called async it showed like a error msg. "throw new Error("The response is not writable.")"

@wongjiahau
Copy link
Contributor

wongjiahau commented Jun 8, 2020

Apparently, for me the problem is that I didn't return a Promise in the middleware, my code was something like this:

app.use((ctx, next) => {
  doSomething((result) => {
    ctx.response.body.result
    next()
  })
})

However, since I'm not return a promise, this line was immediately invoked even before doSomething finished, which will in turn turn the response unwritable.

So, by returning a promise as follows, I don't face this problem anymore.

app.use((ctx, next) => {
  return new Promise(resolve => {
    doSomething((result) => {
      ctx.response.body.result
      resolve()
    })
  })
  .then(next)
})

@GideonMax
Copy link

it seems that the problem occurs because the function returns before the body is written, what I'm really confused by is that when using promises, oak awaits the promise and then disables writing but when using async/await, even though the function still returns a Promise, it is not awaited

@wongjiahau
Copy link
Contributor

@GideonMax I think this PR #156 might help solve your problem, which is to allow Typescript to guide you from making these mistakes. Basically it works by making Typescript complaining when you're writing a middleware that will potentially cause the response is not writable error.

Unfortunately it's being closed at the moment, hopefully I can convince the owner that the PR is worthwhile.

@austin362667
Copy link

I was in the same error for a week.
Change Deno.writeFile() to Deno.writeFileSync() work for me. Or whatever the same filesystem process.
Response will be sent while doing file system process "Synchronously".

@tonymartin-dev
Copy link

tonymartin-dev commented Jun 15, 2020

Same error here.

When I try to fetch asynchronously some data from my DB with mongo@v0.7.0, I get The response is not writable error.

.post('/uploadFile/:projectId', upload('uploads'), async(context: any)=>{
  // Do stuff
  const project: Project | undefined = await projects.findOne({
    _id: { "$oid": context.params.projectId}
  })
  // Do stuff
  context.response.body = 'Any response'
})

If I remove this DB request, everything works fine, but this await seems to cause the problem. Could be upload middleware doing somthing here?

BTW, I found a way to make it work, moving this request to a middleware and passing the information I need through the context.

const checkProject = async(context: any, next: any) => {
  const project = await projects.findOne({_id: { $oid: context.params.projectId}})
  if(project){
    context.project = project
    await next()
  }
}

 // ...
router
  .post('/uploadFile/:projectId', checkProject, upload('uploads'), async(context: any)=>{
  // Do stuff
  const project: Project | undefined = context.project
  // Do stuff
  context.response.body = 'Any response'
}))

}

@tiagostutz
Copy link

tiagostutz commented Jun 16, 2020

I was having a similar issue and what worked for me was to add a return statement to a function on the route handler function. I haven't figured it out yet why, but here's the scenario:

DOESN'T WORK:

router.get(
  "/user/:userId/device/:deviceId/active-time",
  (ctx) => {
    const paramsAndQuery = helpers.getQuery(ctx, { mergeParams: true });
    getActiveTime(
      {
        response: ctx.response,
        params: {
          userId: paramsAndQuery.userId,
          deviceId: paramsAndQuery.deviceId,
          interval: paramsAndQuery.interval,
        },
      },
    );
  },
);

DOES WORK:

router.get(
  "/user/:userId/device/:deviceId/active-time",
  (ctx) => {
    const paramsAndQuery = helpers.getQuery(ctx, { mergeParams: true });
    return getActiveTime( /** <<<---- notice the `return` statement **/
      {
        response: ctx.response,
        params: {
          userId: paramsAndQuery.userId,
          deviceId: paramsAndQuery.deviceId,
          interval: paramsAndQuery.interval,
        },
      },
    );
  },
);

The getActiveTime function:

const getActiveTime = async (
  { params, response }: {
    params: {
      userId: string;
      deviceId: string;
      interval: string;
    };
    response: any;
  },
) => {
    try {

      const netTime = await getUserDeviceNetActiveTime(
        params.userId,
        params.deviceId,
        interval,
      );
      response.status = 200;
      response.body = netTime;

    } catch (error) {
      console.error("Error fething getUserDeviceNetActiveTime:", error);
      throw error;
    }
}

@austin362667
Copy link

I have the similar problem before.
But it's ok while I upgrade oak to the latest version.
And do not use another third party upload middleware.
Use oak build-in support.
To read form data and write it to the file system.
As the sample code below.

const result = await context.request.body(
 {      
     contentTypes: {
          json: ['application/json'],
          form: ['multipart', 'urlencoded'],
          text: ['text']
        }
})
var obj
if( result.type === 'form-data' ){
      obj = await result.value.read()
}
console.log(obj.fields)
console.log(obj.files)

@Nick-Pashkov
Copy link

I had the same problem on routes that used a JWT auth middleware, my solution was to append await before my next() call, so instead of having:

if(token && csrfHeader) {
    if((await validateJwt(token, key)).isValid && validateCSRF(csrfCookie, csrfHeader)) {
       next()
    }

...

}

I changed it to:

if(token && csrfHeader) {
    if((await validateJwt(token, key)).isValid && validateCSRF(csrfCookie, csrfHeader)) {
       await next()
    }

...

}

It works fine, and no errors ;)

@wongjiahau
Copy link
Contributor

wongjiahau commented Jun 17, 2020

@kitsonk I think it might be worthwhile to re-evaluate #156, considering that most of the users that face this error is due to human error rather than library bug, which can be avoided with the stricter middleware types.

@michael-spengler
Copy link

I also replaced
next()

by
await next()

--> this works fine in my case.

after this the controller content can be something like:

    getChatResponse: async ({ request, response }: { request: any; response: any }) => {
        const apiResult:any = await ResponseProvider.getResponse()
        response.body = apiResult
    }

@Srabutdotcom
Copy link

Dear All,

I have the similar problem when using some middleware. The problem exist inside async await function, where if await is missed before next(). Please don't forget to use await before next() or other method inside asycn function.

Regards

@salemalem
Copy link

It happens to me when I try to change the JS object inside the route.

.get('/test', async (context) => {
let jsonBodyOutput = {};

jsonBodyOutput[status] = 404;

context.response.body = jsonBodyOutput;
});

The response isn't writable error is thrown.
However when I try just

.get('/test', async (context) => {
let jsonBodyOutput = {};
context.response.body = jsonBodyOutput;
});

@kluzzebass
Copy link

I had the same problem on routes that used a JWT auth middleware, my solution was to append await before my next() call, so instead of having:

if(token && csrfHeader) {
    if((await validateJwt(token, key)).isValid && validateCSRF(csrfCookie, csrfHeader)) {
       next()
    }

...

}

I changed it to:

if(token && csrfHeader) {
    if((await validateJwt(token, key)).isValid && validateCSRF(csrfCookie, csrfHeader)) {
       await next()
    }

...

}

It works fine, and no errors ;)

This is the response that solved it for me. I went hunting up the middleware chain, and found a next() with a missing await, which solved the problem. This is a very peculiar gotcha that deserves a special mention in the documentation.

@lukeshay
Copy link

I have create a repo with a small example of where I have been hitting this issue.

https://github.com/LukeShay/oak-response-not-writable

@Toxxy1
Copy link

Toxxy1 commented Jan 4, 2023

Anybody got solution to this problem?

I am having the same issue, when I try to read values with .read() or .stream() from FormDataReader, the response becomes unwritable.

@colinricardo
Copy link

for others looking at this, check that if you are using cors, that you do it after your other middleware.

this solved the problem for me.

not working:

app.use(oakCors());
app.use(Session.initMiddleware(store));

working:

app.use(Session.initMiddleware(store));
app.use(oakCors())

hope this helps!

@kitsonk kitsonk added working as designed This is working as intended and removed needs investigation Something that needs to be looked at labels Apr 25, 2023
@kitsonk
Copy link
Collaborator

kitsonk commented Apr 25, 2023

The most common cause of this is "dropping" the flow control of the middleware.
Dropping the flow control is typically accomplished by not invoking next()
within the middleware. When the flow control is dropped, the oak application
assumes that all processing is done, stops calling other middleware in the
stack, seals the response and sends it. Subsequent changes to the response are
what then cause that error.

For simple middleware, dropping the flow control usually is not an issue, for
example if you are responding with static content with the router:

import { Application, Router } from "https://deno.land/x/oak/mod.ts";

const router = new Router();

router.get("/", (ctx) => {
  ctx.response.body = "hello world";
});

const app = new Application();
app.use(router.routes());
app.use(router.allowedMethods());

app.listen();

A much better solution is to always be explicit about the flow control of the
middleware by ensuring that next() is invoked:

import { Application, Router } from "https://deno.land/x/oak/mod.ts";

const router = new Router();

router.get("/", (ctx, next) => {
  ctx.response.body = "hello world";
  return next();
});

const app = new Application();
app.use(router.routes());
app.use(router.allowedMethods());

app.listen();

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
working as designed This is working as intended
Projects
None yet
Development

No branches or pull requests