Force download string as text file I'm trying to return the results of a db_query as a raw text file. The php solution uses header() to instruct browsers to download content. I've attempted using header() in an AJAX callback function, but the following error code is always returned:

An AJAX HTTP error occurred.
HTTP Result Code: 200
Debugging information follows.
Path: /system/ajax
StatusText: OK
ResponseText: this is just a test


I'm looking for a way to save a php string value to a file client-side, ideally without requiring the file to first be saved on the server. Alternatively, if I could pass a value from an AJAX callback directly to javascript for post-processing, that would work too (haven't found an AJAX callback with this capability).

Here's my test code (that produces the above error):

&lt;?php

function test_download_form($form, &amp;$form_state) {

  $form['form_wrapper'] = array(
    '#prefix' =&gt; '&lt;div id="form_wrapper"&gt;',
    '#suffix' =&gt; '&lt;/div&gt;',
  );

  $form['form_wrapper']['download_submit'] = array(
    '#type' =&gt; 'button',
    '#value' =&gt; 'test download',
    '#id' =&gt; 'download_submit',
    '#ajax' =&gt; array(
      'callback' =&gt; 'test_download',
    ),
  );

  return $form;
}

function test_download($form, $form_state) {  
  $dateFile = rand(10000000, 99999999)."_test.txt";
  $dataString = "this is just a test";

  //header() and drupal_add_http_header() behave the same
  header('Content-Type: application/text');
  header('Content-Disposition: attachment; filename="'.$dateFile.'"');
/*  
  drupal_add_http_header('Content-Type', 'application/text; utf-8');
  drupal_add_http_header('Content-Disposition', 'attachment; filename="'.$dateFile.'"');
  drupal_send_headers();
*/
  echo $dataString;
  exit;
}
I ended up making some modifications to the Drupal CSV module (code). The module code demonstrates that you can stream information directly to the client with header() designations. From what I was able to find, you aren't able to use form headers within form callbacks. An alternative method involved passing information with sessioning to an empty page (that doesn't have header restrictions). This had several undesirable consequences:

1.) Session information is saved temporarily server-side, increases time to load to client due to having to write twice.

2.) Finite memory allotment for sessions.

The solution below does not have these limitations:

&lt;?php

function test_download_form($form, &amp;$form_state) {

  $form['form_wrapper'] = array(
    '#prefix' =&gt; '&lt;div id="form_wrapper"&gt;',
    '#suffix' =&gt; '&lt;/div&gt;',
  );

  $form['form_wrapper']['download_submit'] = array(
    '#type' =&gt; 'submit',
    '#value' =&gt; 'test download',
    '#name' =&gt; 'retrieve',
  );

  return $form;
}

function test_download_form_submit($form, &amp;$form_state) {
  $characters = 'ACGT';
  $randomString = '';
  $n = 50000000;

  for ($i = 0; $i &lt; $n; $i++) { 
    $index = rand(0, strlen($characters) - 1); 
    $randomString .= $characters[$index]; 
  }

  //https://git.drupalcode.org/project/csv/blob/7.x-1.x/csv.module
  drupal_add_http_header('Content-Type', 'text; charset=utf-8');
  drupal_add_http_header('Content-Disposition', 'attachment; filename="test_header_method.txt"');
  $fp = fopen('php://output', 'w');
  fwrite($fp, $randomString);
  fclose($fp);
  drupal_exit();
}