Sunday, January 17, 2016

Ionic Angular App as a Website Front-end - AJAX/CORS solutions

Ionic and Angular are great frameworks for building single page web apps.  If you want to create an app that you can then deploy on almost any mobile device out there based on HTML5 and Javascript (JS), then you are starting in a good place.

If you are just building an app that does not communicate to other websites and deals only with information stored locally or already on the app, then you will have no issues.  What the guides do not explain well is all the issues you will have communicating with other websites.  These are JS features (or limitations) that protect users from malicious js code.  Native Android (dalvik) and iOS (Objective-C/Swift) apps do not have that limitations, so native developers may be unfamiliar with these restrictions.

This article will help you identify and solve some common pitfalls when building an ionic app.  The source code to the sample ionic app is available on github.

Typical architecture for an Ionic App as a web site front-end.


Make sure your server allows remote access by Javascript:

If you are communicating to another server, you will need to set it up to allow for Cross-Origin Resouce Shared (CORS) access or JS will not be able to read the responses.  If the server you want to talk to is not your own and does not have Access-Control-Allow-Origin in its response headers set to where you need it (generally '*' or 'file://'), then you'll need to find a different way such as a proxy or a native app.
In Apache's httpd.conf file, you will need something like this on the directory you want to allow access to:
<Directory "/your_www_dir">
  #You may prefer "file://" instead of "*" because it is more restrictive
  Header set Access-Control-Allow-Origin "*"
Or this can be done in your host language.  For PHP:
//When testing app locally, it will be run from localhost:8100
$client_origin = "";
if (array_key_exists('HTTP_ORIGIN', $_SERVER)) {
    $client_origin = $_SERVER['HTTP_ORIGIN'];
if (stristr($client_origin, 'localhost') !== false) {
    $origin = "http://localhost:8100"; //test host
} else {
    $origin = "file://";
//Allow only from two specific locations: testing, and from JS app
header("Access-Control-Allow-Origin: $origin");
//To be less restrictive, you can use this line below
//header("Access-Control-Allow-Origin: *");
header("Access-Control-Allow-Methods: POST, GET, OPTIONS");

Make sure your Ionic project has whitelists enabled and configured:

Newer versions of Ionic projects seem to have this enabled by default, but if you do not have the whitelist plugin already, you will need to install it.

Check if the whitelist plugin is installed:
$ ionic plugin list
cordova-plugin-console 1.0.2 "Console"

cordova-plugin-whitelist 1.2.0 "Whitelist"

If not, install it:
$ ionic plugin add
Configure the app's config.xml file to allow access to outside sites (i.e. beyond 'file://'), add after <content src="index.html"/>.  Add the last two lines to allow Android/iOS apps to open sites other than you own (if desired):
<access origin="*"/>
<allow-intent href="*"/>
<allow-navigation href="*"/>

For iOS apps, check your App Transport Security (ATS) settings:

When you do an app build, a platforms/ios/{app_name}/{app_name}-Info.plist file is created.  If your app ONLY accesses HTTPS, you won't need to set this.  Otherwise add the bolded lines to the file:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "$
<plist version="1.0">

If this is not set, you may have communication issues and see the following error:
App Transport Security has blocked a cleartext HTTP (http://) resource load since it is insecure. Temporary exceptions can be configured via your app's Info.plist file.

Load your code or try the sample project:

The sample project uses the Angular $http service to communicate to a sample website.  I use as an example because they send an Access-Control-Allow-Origin = '*' and allows us to test our app.  You can see the response in the Message section and any errors in the Error section.

iOS app publishing tips

Getting an app running in the Apple App Store has its own set of challenges.  Check out these Ionic iOS App Publishing tips: Publishing an Ionic Angular App for iOS - The Hidden Steps & Pitfalls.

Something you should never ever do - have Allow Origin set to * and Allow Credentials to true

According to the spec you can't have Allow Origin = "*" and Allow Credentials set to "true".  There is a really good reason for this.  It prevents malicious Javascript from using your credentials saved in cookies against you.  Say you logged into a website and it knows who you are through a cookie.  If in another window you ran some malicious JS code and both of these settings were enabled, the JS code could look like a logged in user to the remote server.  Not so bad if it is a sports website, but horrendous if it is your banking site.  With all that said there is a way to do this in Apache.  Just echo the given origin back to the user.  The browser never sees it as Allow Origin=* so it allows the request.

This code in your httpd.conf completely exposes your users to malicious JS code:

   <Directory "/somedir">
       SetEnvIf Origin "^(.*)$" ORIGIN_SUB_DOMAIN=$1
       Header set Access-Control-Allow-Origin "%{ORIGIN_SUB_DOMAIN}e" env=ORIGIN_SUB_DOMAIN
       Header set Access-Control-Allow-Credentials "true"

Publishing an Ionic Angular App for iOS - The Hidden Steps & Pitfalls

The manual for Ionic makes it seem so easy to get started - and it generally is... that is until you go to publish your app to the Apple store and run on real devices.  It can be hard getting started if you don't know you need any of these crucial elements.  Later versions of Ionic already include some of these steps making future builds easier.  Here is a list of some solutions to pitfalls I've run across publishing Ionic apps to the Apple App Store.

Make sure you have your provisioning license

In using iOS, you'll need to create an AppId and with it an associated provisioning license at  This needs to match the widget id in the Ionic config.xml.

Create and Set the widget id for a provisioning license, then update config.xml:
<widget id="com.company_name.app_name" version="x.x.x" ...

Make sure the system can find your provisioning license

In my system it initially was not able to find the license.  So, I needed to perform this step in order to properly deploy
$ sudo cp -r ~/Library/MobileDevice/ /Library/
If this is not set correctly, you can get the following error when deploying your app to a device:
Check dependencies
Code Sign error: No provisioning profiles found: No non–expired provisioning profiles were found.

When accessing non-HTTPS endpoints, check your App Transport Security

When you perform ionic build ios, a platforms/ios/{app_name}/{app_name}-Info.plist file is created.  If your app ONLY accesses HTTPS, you won't need to set this.  Otherwise add the bolded lines to the file:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "$
<plist version="1.0">

If this is not set, you may have communication issues and see the following error:
App Transport Security has blocked a cleartext HTTP (http://) resource load since it is insecure. Temporary exceptions can be configured via your app's Info.plist file.

Building your app in Xcode

In order to send your app to the Apple App Store, you will need to load your app in Xcode.  When you perform ionic build ios it creates a file in your platforms/ios/{app_name}.xcodeproj file that you open in Xcode.

In my version of the project I needed to make some modifications to the project in order to build.  Under Build Settings / Search Paths / Header Search Paths, I added:
To both Debug & Release for Any SDK.

Select the correct orientation and display settings

Under General / Deployment Info, Select Add device Orientations.  Do not select a Main Interface - this should be left blank.  Make sure you have selected all desired orientations and 'Requires full screen'.  It will put these keys in the plist file, although that does not seem to be the only location they are required because setting these without doing so in Xcode does not set the values properly.

Error if Main Interface .xib is selected:
2015-10-28 08:29:57.260 AppName[89398:1651075] *** Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[<UIApplication 0x787193c0> setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key view.'

Setup the custom icons for the app

Set your icon & splash screen in the resources folder.  Use a minimum of 192x192 for the icon and 2208x2208 for the splash screen (it will be cropped for portrait/landscape).  Run ionic resources to automatically compile the icons into your project so it will appear on the device:
$ ionic resources
This section came from help from:

Export the app from Xcode and upload the app to the Apple App Store

Initially the Project / Archive will not be available.  Next to triangle play button and the square stop button, the current device your can deploy to is selected.  Click the area to change this from iPhone to Generic iOS device.  It does not look like an input combo box, but you can click on it to change it.

Click here to change the deploy target and activate the Archive selection under the Product menu

You'll need to export the project to an archive to publish to iTunes connect and then run have processed through the app store.  Open Xcode.  Select Project / Archive from the Xcode menu.

Dealing with Cross-Origin Resource Sharing

Getting communications working to remote sites can have many potential issues to be worked out.
Check for my full post on settings needed for making CORS requests: Ionic Angular App as a Website Front-end - AJAX/CORS solutions.

Thursday, January 15, 2015

Resolved - Apache Alias or Symlinks Not Working with Unexpected 403 Forbidden Error

Apache Forbidden Access Issues

Linking files to Apache on a new install of Cent OS 7 with Apache 2.4 was not quite as smooth as I imagined.

The Setup

I'm working a new server and I want to link to another location so I can use Dropbox to work on files locally and have them automatically updated to my development system.  Since it is easiest to install in the root directory, the Dropbox files automatically get setup in the root user's home directory.  That is not a good place to link the document root to in Apache.  The most logical thing to do was to copy those files out to another better location where they can be served by Apache.  These files were still owned by root and I didn't want to change that, so I creating a symlink to get it to work.

As root (note: my document root was /var/html)
cd /var/html
ln -s /usr/demo/html/ demo

When trying to run in Apache, I would still get the Forbidden error message.

NOTE: When Apache follows symlinks, the path must be accessible all the way down by the calling user (this means you need execute access in the folder you are linking and the parent folders above it).  To make sure this folder is accessible by others, I would use the following command:
chmod o+x /usr /usr/demo /usr/demo/html 

That didn't work for me, but it should work.  I just didn't realize the underlying problem I was experiencing which I will get to in a minute.  So now, I'm thinking I'll try to use an alias and edited and saved the new config file.  

Opening Apache config, I edited it as follows:

sudo nano /etc/httpd/conf/httpd.conf

Alias /demo /usr/demo/html

<Directory "/usr/demo/html">
    Options FollowSymLinks
    AllowOverride None
    Order allow,deny
    allow from all


All the online literature was pointing me in this direction.
Since I am using the new CentOS7 I need to restart the service using the system control program.
sudo systemctl restart httpd.service
#but, on most servers this is: 
#sudo /etc/init.d/httpd restart

The server restarted properly but I am still not able to access the page and still the Forbidden error pops up in my browser.  Looking in the /var/log/httpd/error_log was somewhat helpful:

[Thu Jan 15 14:37:07.549412 2015] [authz_core:error] [pid 30582] [client x.x.x.x:yyyy] AH01630: client denied by server configuration: /usr/demo/html/test.php

This was telling me that I didn't have a linux permission error accessing the file, but that I had an Apache configuration file error.  Back into the httpd.conf file.

The Solution

After a little digging, I found that Apache 2.4 (that I had on the new server) handles permissions differently that the previous version 2.2 that I was used to using.

Finally modifying my httpd.conf file resulted in:

Alias /demo /usr/demo/html

<Directory "/usr/demo/html">
    Options FollowSymLinks
    AllowOverride None
    Require all granted


Now everything works.  I just hadn't been aware that setting aliases in new Apache 2.4 installtion requires a couple changes in the httpd.conf file to get things working properly.  All this time it was just I was using: 
Order allow,deny
allow from all

when I should have been using:
Require all granted

Hopefully this helps someone else save some time.

Sunday, January 4, 2015

Don't Look Bad - Test Your Website

Don't be caught looking like a fool when your website fails unexpectedly.  Find out errors ahead of time using automatic testing techniques.  In this article we discuss automatic regression testing.  Use PHP to setup your own continuous integration server.

When you are responsible for a updating a website that is in active use,  you want to feel confident that the changes you make to the code do not break any currently working pages.  Nothing is worse that making a small change to fix one problem and find out later through a customer that the unintended consequence causes another page to stop working.  Prevent yourself from experiencing that embarrassment and setup automated webpage checking.  It's not too difficult and you will thank yourself later when you identify errors before pushing changes made on your development server to the production server.

Types of Testing

  • Unit Testing - Using a tool like PHPUnit, tests the individual components of your programs and be run each time code changes are made.  
  • Regression Testing - The subject of this article are tests help verify that the webpage works as a whole.  It is designed to supplement unit testing and provide early notification when any page or web service breaks.  Scheduling this test to run repeatedly is the next best thing to having your own continuous integration service, but is much easier to setup.
  • Integration/UI Testing - Using a UI simulator like Selenium tests the end-user's experience on the website.  While useful, this is time consuming to setup and is easily defeated when site layout is changed.  This testing is beyond the scope of this article. 


First Things First, the Overall Process

  1. Separate development servers from production.  It is recommended to have a separate development server and production server.  Development servers can be low power and low memory machines which are not only inexpensive to maintain, but have the added benefit of exposing any performance issues early.  
  2. Perform unit testing on the development server each time you make code changes.
  3. Perform automatic regression tests from a different computer to the one you are checking.  If the computer you are testing goes down, you want to be notified.  If your testing is done on the same computer, then if that computer fails, you run the risk of not being notified when there is a serious problem.  Run an automated task on the production server that checks the development server.  
  4. Test in both directions.  Run an automated task on the development server that checks the production server.  
  5. Confidently push code changes to the production server after testing successfully passes.


Writing a Regression Test - Get what you expect, not what you don't 

Get the first page you want to check and identify three things, the URL of the page, the content that is expected to be on the page (ie. page title), and content that should never be on the page (error messages, etc).  The test will verify that content you expect to be there is present and that content you don't want is not present.  This could be any page you want and I am using's homepage (a site I develop and maintain).  The error messages I use are geared towards a Linux, PHP, and Apache server, but the same basics apply to other systems.  Make sure error reporting is on in your programming language (for PHP this can be done by setting error_reporting = E_ALL in /etc/php.ini).
URL - ""
Expected content - "Checklists ToGo", "Popular Checklists", " WorxForUs &copy;"
Error content -"Parse error: syntax error", "{local filesystem root path to your site}", in my case: "/home/ec2-user/www/htdocs/"

Using the local filesystem page path in the check for error content combined with the web server language displaying errors is an extremely powerful and easy way to check for site errors.  Syntax errors, database errors, run-time errors, and all kinds of problems are all easily detected since errors report a filesystem trace including the root path when errors occur.  NOTE: The relative path below the filesystem root should not be used in the error detection since those strings will be found in page links.

Running the Regression Test - Main Code

Now that we know the site and what strings to check for we can build the program to run the actual test in a file called sample_index_test.php.


    $url = "";
    //These strings must not be in the page content to pass testing
    $err_arr = array();
    $err_arr[] = "Parse error: syntax error";
    $err_arr[] = "/home/ec2-user/www/htdocs/"; //This is probably the best detector in this group

    //These strings are required to be in the page content to pass testing
    $pass_arr = array();
    $pass_arr[] = "Checklists ToGo"; //check page title is on page
    $pass_arr[] = "Popular Checklists"; //check sample header
    $pass_arr[] = "WorxForUs &copy;"; //check copyright

    //Check the site
    $result = validate_site_helper::check_site($url, $err_arr, $pass_arr, basename(__FILE__));

    //Report the test result
    if (!$result->success) {
        $message = "Page {$url} testing failed - {$result->error}";
    } else {
        //(optional) let developer know the site was ok
        echo ("Site {$url} is ok");

    //This is your custom module to send the display to the administrator or developer
    function handle_error_notification($message) {
        //Notify admin of failure - email, print to screen, etc.
        //Please see other blog entries on sending emails which are a great notifier
        echo ("ERROR: {$message}");

The validate site helper encapsulates all this checking and returns a result object that lets you know how the testing went.

The error notification is going to be different for each system and is beyond the scope of this article.  In my case, I like to use Amazon Simple Email Service (tutorial here) to send emails to myself when errors are detected and find that works very well.  

Validate Site Helper - Code

The validate_site_helper does all the hard work of getting the URL page content, parsing the text for the expected and error strings and then returns the result.


class validation_result {
        public $success = true;
        public $error = ""; //for passing errors
        public $subject = ""; //for providing a quick summary to email

 *  validate_site_helper - this is a tool to capture and parse a specific web site page
 * @author sbossen
class validate_site_helper {

        protected static function check_site_helper($site_content, $host_url, $err_indicators_arr, $pass_indicators_arr, $calling_file) {
                $result = new validation_result();
                //using try here so any parse errors will be caught by this script
                try {
                        $ctg_content = $site_content;
                        //check for the errors
                        foreach ($err_indicators_arr as $err_str) {
                                if (stristr($ctg_content, $err_str)) {
                                        $result->success = false;
                                        $result->error .= "Suspected error indication: '$err_str' found in generated page content.\r\n";
                        //check for the required items
                        foreach ($pass_indicators_arr as $pass_str) {
                                if (!stristr($ctg_content, $pass_str)) {
                                        $result->success = false;
                                        $result->error .= "Validation indication: '$pass_str' was not found in generated page content.\r\n";

                        if (!$result->success) {
                                $result->subject = "$host_url - Warning - $calling_file";
                } catch (Exception $e) {
                        $result->success = false;
                        //email user
                        $body = $e->getMessage()."\r\n".$e->getTraceAsString();
                        $result->error .= $body;
                        $result->subject = "$host_url - Execution Error - $calling_file";
                return $result;

        public static function check_site($host_url, $err_indicators_arr, $pass_indicators_arr, $calling_file) {
                $result = new validation_result();
                //using try here so any network errors will be caught by this script
                try {
                        $ctg_content = file_get_contents($host_url);
                        $result = validate_site_helper::check_site_helper($ctg_content, $host_url, $err_indicators_arr, $pass_indicators_arr, $calling_file);
                } catch (Exception $e) {
                        $result->success = false;
                        //email user
                        $body = $e->getMessage()."\r\n".$e->getTraceAsString();
                        $result->error .= $body;
                        $result->subject = "$host_url - Execution Error - $calling_file";
                return $result;

        public static function check_site_with_post($host_url, $post_params_array, $err_indicators_arr, $pass_indicators_arr, $calling_file) {
                $result = new validation_result();
                //using try here so any errors will be caught by this script and emailed
                try {
                        // use key 'http' even if you send the request to https://...
                        $options = array(
                                'http' => array(
                                        'header'  => "Content-type: application/x-www-form-urlencoded\r\n",
                                        'method'  => 'POST',
                                        'content' => http_build_query($post_params_array),
                        $context  = stream_context_create($options);
                        $ctg_content = file_get_contents($host_url, false, $context);

                        $result = validate_site_helper::check_site_helper($ctg_content, $host_url, $err_indicators_arr, $pass_indicators_arr, $calling_file);
                } catch (Exception $e) {
                        $result->success = false;
                        //email user
                        $body = $e->getMessage()."\r\n".$e->getTraceAsString();
                        $result->error .= $body;
                        $result->subject = "$host_url - Execution Error - $calling_file";
                return $result;



Validation Results


This works by using PHP's built in file_get_contents function which grabs the contents of the URL from the server.  That is handled beneath a try function that captures errors such as the page not being found and allows the script to continue and report the error back to the user.  Otherwise, if the page could not be retrieved the notification code would not execute which would be a big problem.

The returned validation_result object is just a holder to pass along the results of the validation.  When you get the results, you'll want to pass them on somewhere to let the developer know that an error has occurred.  In the sample code here we are just outputting to the screen.  

This code was tested under multiple failure scenarios, including:
  • Server is offline (IP address could not be resolved)
  • Page is not authorized
  • Page does not exist
  • Page is blank

    ERROR: Page http:// testing failed - Validation indication: 'Checklists ToGo' was not found in generated page content. Validation indication: 'Popular Checklists' was not found in generated page content. Validation indication: 'WorxForUs ©' was not found in generated page content.
  • Page is OK

    Site is ok

Automating the Testing

When the validation code is ready, you'll want to continually run it.  An easy way to do this in Linux is using the cron tool or Task Scheduler for Windows.

For me, I just run this test every hour on the 14th minute:
    sudo crontab -e
    14 * * * * php {path to file}/sample_index_test.php
To finalize the change and write the updated task to the system

Of course, you will need to have added the email notification (or other system) since cron will only output to the console and you will not see it directly.

If you find this code useful, please let me know in the comments, give a +1, or send a smiley cat picture.

Tuesday, December 30, 2014

Pass a Verified Entrepreneur Course, Get $1000 AWS Credit

Take a $50 class and get $1000 AWS credit

Right now, and for a limited time (though I'm not sure how limited) you can take the edX course Entrepreneurship 101 or Entrepreneurship 102 by MITx and you can get $1000 in Amazon Web Services credit.  

My Thoughts

The course costs only $50, so stop being a cheap ass and sign up.  If you are already using AWS, this is a no-brainer.

Take advantage of this offer from edX.





Test that New Startup Idea

I'm already a fan of AWS, so this is an easy decision for me.  If you are not already a customer of AWS, then for a $50 investment and some time in the class, you can use that $1000 to run a decent Linux M3 server for about 18 months.  That's a decent amount of time to get a demo up and running for a new startup.

The classes are by MITx, so expect this class to be of high quality and be ready to put some time in it to pass the class.  I've taken other classes on edX (CS169.1 & 2 - the Berkeley Software as a Service classes) and found it to be an extremely good learning experience and highly recommend it.

Monday, December 15, 2014

Game Design Bundle - Hours of Training for only $8

Get Hours of Training in Unity and 3D Modeling for an Extremely Low Price

This is such a good deal I figured others may be interested in it.  Over at Slashdot Deals there is a 'name your own price' bundle for some pretty extensive game development courses.  The courses are hosted by Udemy.  If you want to learn about Unity or want to improve your artistic ability this may be the bundle for you - or a good gift for that developer you know.  Make sure to pay the average price (or above) to get the best deal by getting all four courses.  It's not really worth it unless you pay the extra to unlock all the courses.

Get the Game Design Bundle on Slashdot Deals.

Why to the Courses

Professional Video Game Art School - This is the real meat of what I am interested in and if this is the only course I use in the group I will have easily gotten my money's worth.  This is a course on getting 3D fundamentals with 3DS Max.   These skills will be useful, not only for gaming, but to build 3D modeling skills which can be applied to other areas such as 3D printing.  Getting started with anything 3D is a real challenge.  For the eight dollars I paid for this package, this price is a steal and it is highly rated by people who have taken the course.  I'm in.

Unity3D Course - I've taken a lot of Unity tutorials, and most seem to cover the same old ground.  I'm not particularly excited about this course and the reviews haven't been good, but sometimes you can pick up new things that another instructor missed.  I'll probably spend some time going through the second half of this course.

Gamification Course - This class could be interesting and is geared towards taking an existing business (non-game) and making it more fun for the the users by adding game elements.  Not my primary focus, but could be useful on some other projects I am working on.

Photoshop Hand Painted Textures Course - I've always wondered how to be able to make great looking textures to apply to gaming models so I'm looking forward to taking this short class.

Tuesday, December 9, 2014

Get Licensed - Tips for preparing for the PE Exam in Computer Engineering

What is this NCEES exam and why would I want to take it?

The NCEES is a governing body that is allowed to license people and companies to practice engineering.  Any company with "Engineer" in the title needs to have a professional engineer (PE) or they may get sued by the professional engineering society for the state it is in.  The Principal and Practice exam is one requirement  prior to becoming licensed as an engineer.  This is an eight hour test that once you pass it, you are on your way to be licensed to officially perform the practice of engineering and run you own engineering company.  

People with a PE license are highly regarded in their field of expertise as it is no small feat to getting licensed.

My Tips

This applies to the Electrical and Computer : Computer Engineering exam, but the same methods and most books should work for the Software Exam.  Although the book list will need to be adjusted somewhat.

I spent many long hours studying for both this FE exam and then the PE exam, and now I have an engineering license to practice software engineering.  I wish I had some help along the way and someone to clue me in to what I needed to do.  Maybe my study methods can help you prepare for this exam.

To prepare for the PE test in October, I started studying five months in advance.  So, to take the test in October, I began studying in May a couple hours a night for most nights.  I figure I put about 300 hours of studying to get ready for this test.  Your needs may be different than mine as I wanted to make sure I did not have to take this eight hour test again.  Really, who wants to take this exam again.

Tips for passing the PE Exam

  1. Start studying five to six months in advance.
  2. Try for at least 300 hours of studying.
  3. Get to know your reference materials well.  Get everything you need early.  You will need to use them under stress and finding what you need quickly is key.  
  4. Use a calculator that you are allowed to take to the test.  Learn it well.  I used the Casio Fx-115ES.
  5. Spend the time to mark the Table of Contents, and where the Index is in all your books with sticky tabs.  You will be thankful later when you can just grab a book and start looking through the index quickly instead of fumbling for it. 
  6. Take all the sample tests you can until you feel comfortable with the material.

Here you can see the yellow tabs.  It is a good idea to mark the table of contents,
the start and end of the index for quick access.

The Books (Computer Engineering version)

To get the booklist, I started with the exam specifications from NCEES and made sure the books I selected covered most of those topics.  If you are taking the software only exam, you will not find as much use for the Electronics Engineers' Handbook.
  1. The Computer Science and Engineering Handbook, Tucker - This book was the best single resource for the test, it is a hefty beast, though.
  2. Electronics Engineers' Handbook, Christiansen - This book also was a great resource for the test.  It's big, but covers a lot of ground.
  3. Operating Systems: Internals and Design Principle, William Stallings  
  4. The Camara Books - Some people don't like his books.  I didn't either at first, but it turned out they were actually useful for me - especially the sample problems:
    1. Electrical Engineering Practice Problems for the Power, Electrical/Electronics, and Computer PE Exams, Camara
    2. Electrical Engineering Quick Reference for the Power, Electrical and Electronics, and Computer PE Exams - This one is expensive and not as good as the Electronic or Computer Science Engineering Handbook.
    3. Electrical Engineering Sample Examinations for the Power, Electrical and Electronics, and Computer PE Exams - The sample problems are a must.  You should be able to solve these using just your reference materials.
  5. PE Electrical and Computer: Computer Engineering Practice Exam - The sample test from NCEES.
  6. Guide to the Software Engineering Body of Knowledge (SWEBOK) - Absolutely necessary.  I used the 2004 version and this book was quite handy for the project management details.  A newer version is available.
  7. Schaum's Outlines - I got these books but didn't really need them for the test. Your mileage may vary.  If you need to target specific holes in your knowledge, they may be a good resource.
    1. Schaum's Outline of Digital Principles
    2. Schaum's Outline of Computer Architecture
    3. Schaum's Outline of Software Engineering
    4. Schaum's Outline of Operating Systems
  8. Microelectronic Circuit Design, Jaeger
  9. Introduction to Computer Engineering: Hardware and Software Design, Booth
  10. Microelectronics (Mcgraw Hill Series in Electrical and Computer Engineering), Jacob Millman

Good luck!

If this list does help you, please consider clicking on +1, post a LOL cat photo, or whatever.