SlideShare a Scribd company logo
Nodejs For Php Developers Porting Php To Nodejs
1st Edition Daniel Howard download
https://ebookbell.com/product/nodejs-for-php-developers-porting-
php-to-nodejs-1st-edition-daniel-howard-4078222
Explore and download more ebooks at ebookbell.com
Here are some recommended products that we believe you will be
interested in. You can click the link to download.
Nodejs For Php Developers Daniel Howard
https://ebookbell.com/product/nodejs-for-php-developers-daniel-
howard-61579382
Nodejs For Beginners A Comprehensive Guide To Building Efficient
Fullfeatured Web Applications With Nodejs 1st Edition Ulises Gascn
https://ebookbell.com/product/nodejs-for-beginners-a-comprehensive-
guide-to-building-efficient-fullfeatured-web-applications-with-
nodejs-1st-edition-ulises-gascn-57082746
Nodejs For Net Developers 1st Edition David Gaynes
https://ebookbell.com/product/nodejs-for-net-developers-1st-edition-
david-gaynes-4991070
Nodejs For Embedded Systems Using Web Technologies To Build Connected
Devices Patrick Mulder
https://ebookbell.com/product/nodejs-for-embedded-systems-using-web-
technologies-to-build-connected-devices-patrick-mulder-5903438
Nodejs For Net Developers Developer Reference David Gaynes
https://ebookbell.com/product/nodejs-for-net-developers-developer-
reference-david-gaynes-167564452
Nodejs For Beginners A Comprehensive Guide To Building Efficient
Fullfeatured Web Applications With Nodejs Ulises Gascn
https://ebookbell.com/product/nodejs-for-beginners-a-comprehensive-
guide-to-building-efficient-fullfeatured-web-applications-with-nodejs-
ulises-gascn-231447746
Nodejs For Beginners A Comprehensive Guide To Building Efficient
Fullfeatured Web Applications With Nodejs Ulises Gascn
https://ebookbell.com/product/nodejs-for-beginners-a-comprehensive-
guide-to-building-efficient-fullfeatured-web-applications-with-nodejs-
ulises-gascn-57113056
Nodejs For Embedded Systems Patrick Mulder
https://ebookbell.com/product/nodejs-for-embedded-systems-patrick-
mulder-170703072
Ultimate Nodejs For Crossplatform App Development Learn To Build
Robust Scalable And Performant Serverside Javascript Applications With
Nodejs English Edition 1st Edition Kumar
https://ebookbell.com/product/ultimate-nodejs-for-crossplatform-app-
development-learn-to-build-robust-scalable-and-performant-serverside-
javascript-applications-with-nodejs-english-edition-1st-edition-
kumar-55500574
Nodejs For Php Developers Porting Php To Nodejs 1st Edition Daniel Howard
Nodejs For Php Developers Porting Php To Nodejs 1st Edition Daniel Howard
Nodejs For Php Developers Porting Php To Nodejs 1st Edition Daniel Howard
Daniel Howard
Node.js for PHP Developers
ISBN: 978-1-449-33360-7
[LSI]
Node.js for PHP Developers
by Daniel Howard
Copyright © 2013 Daniel Howard. All rights reserved.
Printed in the United States of America.
Published by O’Reilly Media, Inc., 1005 Gravenstein Highway North, Sebastopol, CA 95472.
O’Reilly books may be purchased for educational, business, or sales promotional use. Online editions are
also available for most titles (http://my.safaribooksonline.com). For more information, contact our corporate/
institutional sales department: 800-998-9938 or corporate@oreilly.com.
Editors: Simon St. Laurent and Meghan Blanchette
Production Editor: Kara Ebrahim
Copyeditor: Jasmine Kwityn
Proofreader: Kara Ebrahim
Indexer: Potomac Indexing, LLC
Cover Designer: Karen Montgomery
Interior Designer: David Futato
Illustrator: Rebecca Demarest
December 2012: First Edition
Revision History for the First Edition:
2012-11-28 First release
See http://oreilly.com/catalog/errata.csp?isbn=9781449333607 for release details.
Nutshell Handbook, the Nutshell Handbook logo, and the O’Reilly logo are registered trademarks of O’Reilly
Media, Inc. Node.js for PHP Developers, the image of the Wallachian sheep, and related trade dress are
trademarks of O’Reilly Media, Inc.
Many of the designations used by manufacturers and sellers to distinguish their products are claimed as
trademarks. Where those designations appear in this book, and O’Reilly Media, Inc., was aware of a trade‐
mark claim, the designations have been printed in caps or initial caps.
While every precaution has been taken in the preparation of this book, the publisher and author assume no
responsibility for errors or omissions, or for damages resulting from the use of the information contained
herein.
Table of Contents
Preface. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . v
1. Node.js Basics. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
The node and npm Executables 1
Stack Traces 7
Eclipse PDT 9
2. A Simple Node.js Framework. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
An HTTP Server 21
Predefined PHP Variables 29
A PHP Example Page 42
3. Simple Callbacks. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47
Linearity 49
Making Code Linear 57
4. Advanced Callbacks. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65
Anonymous Functions, Lambdas, and Closures 66
PHP 5.3 69
PHP 4 73
5. HTTP Responses. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89
Headers 90
Body 92
A PHP Example Page 97
6. Syntax. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 107
String Literals 109
Syntax Differences 112
iii
PHP Alternative Syntax 117
7. Variables. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 125
Simple Variables 126
Array Variables 128
Other Variable Types 143
Undefined Variables 144
Scope 148
8. Classes. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 157
Encapsulation 157
Inheritance 166
PHP parent and static Keywords 173
9. File Access. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 177
Reading and Writing Files 177
PHP file() API Function 183
Low-Level File Handling 186
Filenames 191
10. MySQL Access. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 199
Database Approaches 200
node-mysql 203
11. Plain Text, JSON, and XML. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 219
Plain Text 221
JSON 223
XML 226
12. Miscellaneous Functions. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 241
Array 242
Time and Date 246
File 247
JSON 247
Math 248
String 249
Type 253
Text 254
MySQL 257
Variable 257
php.js License 258
Index. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 261
iv | Table of Contents
Preface
Why bother with this book?
PHP is an old language, as Internet languages go, invented in 1995. Node.js is new, very
new, invented in 2009. Looking at PHP side by side with Node.js gives you a bird’s eye
view of where web servers started, how far they have come, and what’s changed. But,
more importantly, it shows what hasn’t changed—what the industry as a whole has
agreed are good practices—and a little bit of what the future holds.
The biggest difference between PHP and Node.js is that PHP is a blocking language,
relying on APIs that don’t return until they are done, and Node.js is a nonblocking
language, relying on APIs that use events and callbacks when they are done. But, except
for that, they are surprisingly similar. Both use the curly bracket notation ( { and } ) for
blocks of code, just like the C programming language. Both have the function keyword,
which serves the exact same purpose and has the exact same syntax in both languages.
If Node.js shows that blocking APIs are the past, it also shows that a pretty specific
variation of the C programming language is the past, present, and future. Callbacks may
be an evolution, but syntax is almost frozen.
But beyond just, “oh, isn’t that interesting,” why bother with this book?
PHP is supported by a zillion cPanel website hosting services. If you develop a web
application and want to give it to other people to run, they can install it almost anywhere
if it is written in PHP. They can buy web hosting for $10 per month, install your PHP
web application, and be on their way.
Node.jsisnotsupportedbyazillioncPanelwebsitehostingservices.Infact,Idon’tknow
even one web hosting service that supports it. But I know that a lot of developers are
v
interested in it and are writing Node.js code. By writing Node.js code, you make your
web application code interesting and useful to a lot of developers. If you develop a web
application and want to give it to other developers to improve and reuse, they can get
your Node.js web application from GitHub or wherever else the source code is hosted.
In a perfect world, you could appeal to both sets of people.
Ours isn’t a perfect world, but you can still achieve this goal by porting your PHP code
to Node.js code and simultaneously having and developing two working codebases in
two different languages.
The Mission
The mission of this book—and when I write “mission,” I mean it in the “I really, really,
really, really want you to do it” kind of mission—is to convince you to convert some of
your PHP code to Node.js code. I don’t want you to just read this book. I want you to
actually sit down at a computer and take some of your most tired, annoying PHP 4 code
and convert it to Node.js using this book as a guide. I want you to see for yourself that
PHP and Node.js are not that different. I want you to see for yourself that your PHP
code does not need to be thrown away and rewritten in Node.js from scratch. I want
you to see for yourself that you don’t have to surrender to just living with your PHP
code, being a prisoner of the past.
As you will see, converting your PHP code to Node.js code isn’t just about Node.js. It is
also about improving your PHP code. An important step throughout this book is re‐
factoring and improving your PHP code such that it is easier to convert it to Node.js
code. This book isn’t just about making a new Node.js codebase. It is about improving
your PHP codebase and creating a new Node.js codebase. It is about both codebases:
your PHP codebase and your Node.js codebase. Converting your PHP codebase to
Node.js can make you a better PHP developer.
If you are a PHP developer, this book is perfect for you because you can learn how to
develop Node.js code by using your existing PHP knowledge. You can see how certain
code works in PHP, such as reading a text file, and in the next few paragraphs, you can
see how exactly the same thing is accomplished in Node.js. Unlike other Node.js books,
this book does not describe file handling in general. It specifically compares it to PHP
so you can see the nuts and bolts of what it looks like in the language that you know as
well as in the language you are learning. You might even find a few corners of PHP you
weren’t previously aware of, because a few of those PHP corners are central concepts in
Node.js.
If you are a Node.js developer already, you have a decent chance of learning PHP from
this book. After all, if PHP developers can figure out Node.js by looking at PHP code
vi | Preface
sidebysidewithNode.js,thereisgoodreasontothinkthatNode.jsdeveloperscanfigure
out PHP by looking at the same code. Even better, by comparing Node.js to a specific
different language, such as PHP, it will give you a good idea as to how much of Node.js
is the same as PHP.
Comparing two languages or, even better, showing how to convert or port from one
language to another, is a powerful way to become an expert in both languages. Other
books, which deal with only one language, mostly read like step-by-step tutorials or
encyclopedias. “This is this,” they read, “that is that.” They can describe concepts only
as abstractions. Other books can’t use the powerful explanation of an ongoing compar‐
ison of two languages that this book does.
Besides being more effective, a book such as this one can also be more interesting and
focus on only the interesting topics. In a run-of-the-mill Node.js programming book,
time is spent explaining what a statement is and why every Node.js statement ends in a
semicolon (;). That’s dull. But when a book is explaining how to program in Node.js in
a vacuum without any point of reference (such as the PHP language), there is no alter‐
native. With this book, I can assume that you already know what a PHP statement is
and that a PHP statement ends in a semicolon (;). All that needs to be said is that Node.js
is exactly the same way. With this book, I can assume that the reader has a specific
background—PHP development—instead of needing to write more broadly for people
who come with a Python or Microsoft Office macro background.
By proselytizing the conversion of PHP code to Node.js code, I am not saying that PHP
code is bad. In fact, I think PHP is a very capable and pretty good language. I am not
saying that you should convert your PHP code to Node.js code and then throw away
theoriginalPHPcode.IamencouragingyoutokeeptheoriginalPHPcodeandimprove
it while, at the same time, becoming a skilled Node.js developer. PHP and Node.js are
both important.
When first setting out to write this book, I made a very important decision early on: I
was going to focus on real-life, practical, existing PHP code. PHP 5 is the current PHP
version, but there is still a lot of PHP 4 code out there. This book has explicitly avoided
the easy prescription: convert your PHP 4 code to PHP 5 code, then use this book to
convert your PHP 5 code to Node.js. No, despite the fact that PHP 4 support is rapidly
fading in favor of PHP 5 support, this book takes the much harder road of showing how
PHP 4 code can be improved upon and converted to Node.js code without requiring
PHP 5 features. Although this book does show how to convert PHP 5 code to Node.js,
let me assure you that PHP 4 code is readily convertible to Node.js using this book.
VerysoonaftermakingthedecisiontoembraceandaddressPHP4code,Imadeanother
decision related to this book: I was going to describe a system of conversion such that
the PHP code and the Node.js code would be kept synchronized and working through‐
out the conversion process. At the end of the conversion process, both the PHP and
Node.js codebases would be fully functional and, going forward, new features and bug
Preface | vii
fixescouldbedevelopedonbothcodebasessimultaneously.Thisdecisionavoidsamuch
easier approach, which would have been a “convert-and-discard” conversion process
where the PHP codebase would be unsynchronized and possibly not working at the end
of the conversion process and the developer’s only option would be to proceed ahead
with the Node.js codebase by itself. This would have made a much shorter book, but
would have been a cheap trick—a way to make life easier for me, as the writer, and make
the book less useful to you, as the reader.
These two decisions, one to support PHP 4 and the other to support two synchronized
PHPandNode.jscodebasesasanendproduct,havemadethisbooklongerthanitwould
otherwise be, but have also made it eminently practical. This is not a book that you will
read once and put on the shelf as an “isn’t that nice to know” book. This is a book that
you can use for reference to quickly refresh yourself about important aspects of either
PHP or Node.js.
By now, you might understand what the mission is and why it might be worthwhile. But
maybe you are still doubtful.
Consider the following PHP code, which was taken from a real-world PHP web appli‐
cation that implemented instant message−style chatting:
function roomlist() {
$rooms = array();
$room_list = mysql_query(
'SELECT room FROM '.SQL_PREFIX.'chats GROUP BY room ORDER BY room ASC'
);
while ($row = mysql_fetch_assoc($room_list)) {
$room = $row['room'];
$rooms[] = $room;
}
print json_encode($r);
}
Now consider the equivalent code in Node.js:
function roomlist() {
var rooms = [ ];
link.query(
'SELECT room FROM '+SQL_PREFIX+'chats GROUP BY room ORDER BY room ASC',
function(err, rows, fields) {
for (var r=0; r < rows.length; ++r) {
var row = rows[r];
var room = row['room'];
rooms.push(room);
}
res.writeHead(200, {'Content-Type': 'text/plain'});
res.end(JSON.stringify(r));
}
});
};
viii | Preface
Sure, the syntax is a bit different. To concatenate strings, PHP uses the dot (.) operator
whereas JavaScript uses the plus (+) operator. PHP uses array() to initialize an array,
but JavaScript uses square brackets ( [ and ] ). It’s not identical.
Butforheaven’ssake,it’sstillprettydarnclose.Thisisn’t“fake”code,either:itusesarrays,
accesses a MySQL database, uses JSON, and writes output.
The similarities and the possibility of converting PHP source code to Node.js, and con‐
sequently the writing of this book for O’Reilly Media, are a direct result of my experience
with creating a Node.js implementation of my open source project.
Who I Am
I’m Daniel Howard, the founder and sole maintainer of ajaximrpg, a preeminent
browser-based instant messaging (IM) and chat system. ajaximrpg is specifically geared
toward playing tabletop role-playing games, such as Dungeons & Dragons, over the
Internet, although the role-playing specific features can be stripped away to reveal a
general-purpose client. ajaximrpg is completely open source and available via Source‐
Forge with a full range of supporting services such as a Twitter feed, a Google Group,
and a live demo.
ajaximrpg was originally written in PHP 4 with no inkling that it might someday be
ported to Node.js JavaScript. But it works on PHP 5 and, now, on Node.js.
Starting in January 2012, it took me a single week to come up to speed on Node.js and
do a proof of concept to have my client-side JavaScript code detect the installation status
of the server side running on Node.js. In a month, I had enough of a few thousand lines
converted to enable users to log in and IM each other. It dawned on me that there were
general principles at work here, and that these general principles could be laid out in a
book to explain how to convert any PHP source code to Node.js and, using these prin‐
ciples, the reader of the book could apply them to his PHP source code much quicker
and more accurately than just muddling along as I had.
I put aside my mostly working but not yet completed Node.js implementation and im‐
mediately set out to write this book that you now hold in your hands (or view on your
screen).
This Book
This book consists of 12 chapters, starting out with the basics and moving on to more
advanced topics.
Preface | ix
Chapter 1, Node.js Basics
This chapter describes how to install Node.js and use the Node.js executables, node
and npm. It also describes how to install the Eclipse PDT and configure it for use
for a PHP to Node.js conversion.
Chapter 2, A Simple Node.js Framework
This chapter presents a simple Node.js framework such that individual PHP pages
canbeconvertedtoNode.jsfilesandtheresultingNode.jsfileswillbeinvokedwhen
actions, such as visiting a URL, are taken against the Node.js web server.
Chapter 3, Simple Callbacks
This chapter explains how to refactor blocking PHP source code such that it can be
easily converted to nonblocking Node.js source code that uses callbacks. It presents
the concept of linearity as a simple way to analyze and improve PHP source code
such that it can be placed in Node.js callbacks when converted to Node.js.
Chapter 4, Advanced Callbacks
This chapter presents a more sophisticated and generic way to refactor blocking
PHP 4 source code to simulate anonymous functions, function variables, and clo‐
sure. For PHP 5 source code, it explains how to use PHP 5 features to actually
implement anonymous functions, function variables, and closure.
Chapter 5, HTTP Responses
This chapter explains how to convert PHP output, such as the print and echo
keywords, into HTTP responses in Node.js.
Chapter 6, Syntax
ThischapterexplainshowtoconvertPHPsyntax,suchasconcatenatingtwostrings,
into Node.js syntax.
Chapter 7, Variables
This chapter explains how to convert PHP single and array variables into Node.js,
as well as common operations, such as adding and deleting elements from array
variables. It also describes how to convert PHP types to Node.js types.
Chapter 8, Classes
This chapter presents a way to implement PHP classes and class inheritance in
Node.js with a step-by-step technique to perform the conversion.
Chapter 9, File Access
This chapter explains all the file reading and file writing APIs in both PHP and
Node.js. It explains how to convert the PHP file handling APIs into their Node.js
equivalents.
x | Preface
Chapter 10, MySQL Access
This chapter describes all the ways that a database, specifically a MySQL database,
canbeusedinawebapplication.Itprovidesastep-by-stepprocedureforconverting
database access code from the PHP MySQL APIs to use the node-mysql Node.js
npm package.
Chapter 11, Plain Text, JSON, and XML
This chapter explains three data formats: plain text, JSON, and XML. It explains
how to convert PHP source code that uses PHP JSON or XML APIs into Node.js
source code that uses similar Node.js npm packages.
Chapter 12, Miscellaneous Functions
This chapter provides Node.js implementations for a large number of PHP API
functions. These Node.js implementations can be used to speed along conversion
and provide an interesting way to contrast PHP and Node.js.
Now let’s get started with Node.js.
About This Book
This book is about how to take existing PHP source code and develop new Node.js
source code from it. PHP and Node.js have many similarities, but of course, there are
some significant differences. By leveraging the similarities and noting the differences,
you can use your PHP experience to learn Node.js and, ultimately, create a Node.js web
application that is a drop-in replacement for any existing PHP web application that you
have.
This book assumes that you are a developer who understands the basics of development,
suchascreatingandthenimplementingadesigninworkinglinesofprogrammingcode.
It assumes that you are already familiar with classes, functions, and looping constructs.
It also assumes that you are familiar with web development, including the basics of how
web browsers and web servers interact to create a web application.
Furthermore, this book assumes that you have significant expertise in the PHP pro‐
gramming language. If you do not have a background in the PHP programming lan‐
guage,itispossiblethatyoucanuseyourbackgroundinanotherprogramminglanguage
(e.g., Python, Ruby, or C) and, by reading this book and examining the intersection
between PHP, Node.js, and the programming language that is familiar to you, acquire
a good understanding of both PHP and Node.js. Not necessarily easy, but possible.
This book can be read straight through as a Node.js tutorial, consulted as a reference to
see how a specific PHP feature can be implemented in Node.js, or executed as a step-
by-step recipe to convert an arbitrary PHP web application into a Node.js web applica‐
tion. The book was written to serve all these purposes.
Preface | xi
No matter how you approach this book, as its author, I sincerely hope that it answers
the questions you have about PHP and Node.js.
Conventions Used in This Book
The following typographical conventions are used in this book:
Italic
Indicates new terms, URLs, email addresses, filenames, and file extensions.
Constant width
Used for program listings, as well as within paragraphs to refer to program elements
such as variable or function names, databases, data types, environment variables,
statements, and keywords.
Constant width bold
Shows commands or other text that should be typed literally by the user.
Constant width italic
Shows text that should be replaced with user-supplied values or by values deter‐
mined by context.
This icon signifies a tip, suggestion, or general note.
This icon indicates a warning or caution.
Using Code Examples
This book is here to help you get your job done. In general, you may use the code in this
book in your programs and documentation. You do not need to contact us for permis‐
sion unless you’re reproducing a significant portion of the code. For example, writing a
program that uses several chunks of code from this book does not require permission.
Selling or distributing a CD-ROM of examples from O’Reilly books does require per‐
mission. Answering a question by citing this book and quoting example code does not
require permission. Incorporating a significant amount of example code from this book
into your product’s documentation does require permission.
We appreciate, but do not require, attribution. An attribution usually includes the title,
author, publisher, and ISBN. For example: “Node.js for PHP Developers by Daniel Ho‐
ward (O’Reilly). Copyright 2013 Daniel Howard, 978-0-596-33360-7.”
xii | Preface
If you feel your use of code examples falls outside fair use or the permission given above,
feel free to contact us at permissions@oreilly.com.
Safari® Books Online
Safari Books Online (www.safaribooksonline.com) is an on-demand
digital library that delivers expert content in both book and video
form from the world’s leading authors in technology and business.
Technologyprofessionals,softwaredevelopers,webdesigners,andbusinessandcreative
professionals use Safari Books Online as their primary resource for research, problem
solving, learning, and certification training.
Safari Books Online offers a range of product mixes and pricing programs for organi‐
zations, government agencies, and individuals. Subscribers have access to thousands of
books, training videos, and prepublication manuscripts in one fully searchable database
from publishers like O’Reilly Media, Prentice Hall Professional, Addison-Wesley Pro‐
fessional, Microsoft Press, Sams, Que, Peachpit Press, Focal Press, Cisco Press, John
Wiley & Sons, Syngress, Morgan Kaufmann, IBM Redbooks, Packt, Adobe Press, FT
Press, Apress, Manning, New Riders, McGraw-Hill, Jones & Bartlett, Course Technol‐
ogy, and dozens more. For more information about Safari Books Online, visit us online.
How to Contact Us
Please address comments and questions concerning this book to the publisher:
O’Reilly Media, Inc.
1005 Gravenstein Highway North
Sebastopol, CA 95472
800-998-9938 (in the United States or Canada)
707-829-0515 (international or local)
707-829-0104 (fax)
We have a web page for this book, where we list errata, examples, and any additional
information. You can access this page at http://oreil.ly/nodejs-php. To comment or ask
technical questions about this book, send email to bookquestions@oreilly.com.
For more information about our books, courses, conferences, and news, see our website
at http://www.oreilly.com.
Find us on Facebook: http://facebook.com/oreilly
Follow us on Twitter: http://twitter.com/oreillymedia
Watch us on YouTube: http://www.youtube.com/oreillymedia
Preface | xiii
Acknowledgments
This book is the product of many months of effort by me, of course, but also by several
others.
I want to thank the editors at O’Reilly Media, Inc., specifically Simon St. Laurent and
Meghan Blanchette, for their encouragement and feedback.
I want to thank Neha Utkur, the book’s technical editor, for her enthusiasm and will‐
ingness to provide feedback on a whole range of areas that sorely needed her input. Her
contribution has made this a much better book.
Finally, I want to thank Shelley Powers for lending a second pair of eyes to review the
book for technical accuracy.
xiv | Preface
CHAPTER 1
Node.js Basics
Let’s assume you have a significant PHP codebase that you have decided to convert to
Node.js. You will provide both the PHP and Node.js codebases to your users for the
foreseeable future, meaning that you will update and improve both codebases simulta‐
neously. But you only know a little about Node.js; in fact, you have not really done any
serious development with Node.js yet. Where do you start?
The first thing to do is to download Node.js for your platform, probably Linux or Win‐
dows (yes, they have a Windows version now!). Since installation methods and installers
vary from version to version and change over time, this book will not spend time on
how to install the current version. Instead, if you need assistance with installation, you
should use the online documentation and, if that fails you, use Google or another search
engine to find web pages and forum postings where others have come across the same
installation issues you are having and have found solutions that you can use.
The node and npm Executables
Once installed, you will see that a Node.js installation is fairly simple and has two main
parts: the main node executable and the npm executable.
The node executable is simple to use. Although it has other arguments, usually you will
pass only one argument, the name of your main Node.js source file. For example:
node hello.js
The node executable will interpret the Node.js code within the source file (hello.js in this
case), execute the code, and when it finishes, exit back to the shell or command line.
Notice that hello.js uses the .js extension. The .js extension stands for JavaScript. Un‐
fortunately, files with the .js extension can contain either client-side JavaScript or server-
side Node.js code. Even though they both use the JavaScript language, they have nothing
1
else in common. Client-side JavaScript code needs to be served out to browsers, while
server-side Node.js code needs to have the node executable run on it or otherwise needs
to be accessible to the main Node.js code that is being run under the node executable.
This is a serious and unnecessary cause of confusion.
In some Node.js projects, the client-side JavaScript files are put in one folder, such as a
client folder, while the Node.js files are put in another folder named something like
server.Separatingclient-sideJavaScriptfilesfromNode.jsfilesviaafolderschemehelps,
but is still problematic because many source code editors show only the filename but
not the full path name in a title bar or tab.
Instead, I have adopted the .njs extension for Node.js files and reserved the .js extension
for client-side JavaScript files in my own projects. Let me be clear, though: the .njs
extension is not a standard! At least, not yet (and maybe not ever). I have diligently
searched using Google, and it is common to use the .js extension for Node.js code. To
avoid constant confusion between client-side and server-side JavaScript, I use the .njs
extension for Node.js code, and in your own PHP to Node.js conversion, I suggest that
you do the same.
So, instead of using the hello.js file given earlier, I would use hello.njs:
node hello.njs
The remainder of this book will use the .njs extension for Node.js files.
A simple hello.njs looks like:
console.log('Hello world!');
If you run node hello.njs on this source file, it prints “Hello world!” to the console
and then exits.
To actually get a web server running, use the following hellosvr.njs source file:
var http = require('http');
http.createServer(function (req, res) {
res.writeHead(200, {'Content-Type': 'text/plain'});
res.end('Hello World!n');
}).listen(1337, '127.0.0.1');
console.log('Server running at http://127.0.0.1:1337/');
If you run node hellosvr.njs, the command line will intentionally hang. The server
must continue to run so it can wait for web page requests and respond to them.
If you start a browser such as Firefox or Chrome and type http://127.0.0.1:1337/ into
the address bar, you will see a simple web page that says, “Hello world!” In fact, if you
go to http://127.0.0.1:1337/index.html or http://127.0.0.1:1337/abc or even http://
127.0.0.1:1337/abc/def/ghi, you will always see the same simple web page that says
“Hello world!” because the server responds to all web page requests in the same way.
2 | Chapter 1: Node.js Basics
For now, the important line in this source file is the first line that uses the Node.js
require() global function. The require() function makes a Node.js module available
for use. Node.js modules are what you might expect: a collection of data and functions
that are bundled together, usually providing functionality in some particular area. In
this case, the http Node.js module provides simple HTTP server functionality.
The node executable has a number of built-in modules: http, https, fs, path, crypto,
url, net, dgram, dns, tls, and child_process. Expect these built-in modules and their
functionality to vary from version to version.
By design, a module resides in a namespace. A namespace is an extra specification that
is added to the front of a data or function reference; for example, http is the namespace
that the createServer() function resides in. In Node.js, a namespace is just imple‐
mented as an object. When the http module is loaded, the require() function returns
an object and that object is assigned to the http variable. The variable does not have to
be called “http”; it could be called “xyzzybub” and, in that case, the server would be
created by calling the xyzzybub.createServer() function.
Why have a namespace? Why not just put all the data and functions as global variables?
Node.js anticipated that new modules with new functionality, such as a MySQL access,
would be developed by other people and need to be integrated into the node executable
after Node.js was already installed on a user’s computer. Since the names of data and
functions in those modules would be unpredictable, a developer might accidentally
choose the exact same name for a function in a module as a different developer might
choose for another module. But since a module is contained in a namespace, the name‐
space would distinguish between the two functions. In fact, an important improvement
over previous languages, such as C++ and Java, is that Node.js allows the user of the
module to specify the name of the namespace because the user himself assigns the
module to his variable, such as http or xyzzybub.
These new modules with new functionality are packages. A package is a module that
can be added to the node executable later and is not built into the node executable by
default. The difference between a module and a package is not very important; it is really
just a change of terminology.
The npm (node package manager) executable adds new packages to the node executable.
To install a package, first use Google or another search engine to find the npm package
that you want to install. Often, the package will be found on GitHub. An alternative to
using a search engine is to use the npm executable itself to find the package using the
search command.
Instead of the web server that always returns a “Hello world!” page, suppose we want to
create a web server that actually serves up static web pages from files on the hard disk.
To find a Node.js static file server module, a good search phrase to type into a search
The node and npm Executables | 3
engine is “nodejs static file web server”. Alternatively, “npm search static”, “npm search
file”, or “npm search server” will list the npm packages that have the words “static”, “file”,
or “server” in their names or descriptions. Using either of these two methods or both in
combination (and with a little extra reading and browsing), you will find that Alexis
Sellier, a.k.a. cloudhead, created a popular static file server module and hosted it here.
This package can be installed by running the following command line (additional op‐
tions, such as the -g or --global command line switch, are available to configure the
package installation):
npm install node-static
The npm executable will retrieve the package and, hopefully, install it successfully. Here’s
the output from a successful installation:
npm http GET https://registry.npmjs.org/node-static
npm http 200 https://registry.npmjs.org/node-static
npm http GET https://registry.npmjs.org/node-static/-/node-static-0.5.9.tgz
npm http 200 https://registry.npmjs.org/node-static/-/node-static-0.5.9.tgz
node-static@0.5.9 ./node_modules/node-static
The GET indicates that an HTTP GET was used to attempt to retrieve the package. The
200 indicates that the HTTP GET request returned “HTTP status 200 OK”, meaning
that the file was retrieved successfully.
There are hundreds of npm packages, but a few very popular ones are express, node-
static, connect, sockets.io, underscore, async, and optimist.
To implement a web server that serves up static web pages, use the following httpsvr.njs
source file:
var http = require('http');
var static = require('node-static');
var file = new static.Server();
http.createServer(function (req, res) {
file.serve(req, res);
}).listen(1337, '127.0.0.1');
console.log('Server running at http://127.0.0.1:1337/');
At a basic level, this is how Node.js development happens. An editor is used to create
and modify one or more .njs source files that contain Node.js code. When new func‐
tionality is needed that is not built into the node executable, the npm executable is used
to download and install the needed functionality in the form of an npm package. The
node executable is run on the .njs files to execute the Node.js code so the web application
can be tested and used.
4 | Chapter 1: Node.js Basics
At this point, three Node.js servers have been presented: hello.njs, hellosvr.njs, and
httpsvr.njs. These source files have been so simple that it did not matter how they were
created. You could have used any text editor to create them and they would work fine.
If you made a mistake, it was easily remedied by editing the source file.
It is safe to assume, though, that you already have a complicated PHP web application
with dozens of files and tens of thousands of lines of PHP that you want to convert to
Node.js. The conversion strategy will follow a straightforward but tedious step-by-step
routine.
The first step will be to create a boilerplate Node.js source file, as described in detail in
Chapter 2, that will support the new Node.js code. This boilerplate Node.js code will be
enhanced to respond to the specific URLs that are available to be invoked by the client.
A web application is, at its heart, a series of URL requests. The objective of conversion
is to make a Node.js server that responds to the client in the exact same way as the PHP
server. To make this happen, the boilerplate Node.js code is modified to handle each
HTTPcallandrouteittospecificNode.jscodethatwilllaterimplementthefunctionality
of the specific PHP page in Node.js.
The second step will be to refactor the PHP code, as described in detail in Chapter 3
and Chapter 4, to make it easier to convert to Node.js code—that is, make the PHP code
more Node.js friendly. It may come as a shock, but the conversion process is not just a
matter of freezing the PHP code in whatever form it currently is, copying the PHP code
into the Node.js source file, and then, line by line, converting the PHP code to Node.js
code. Since both the PHP and Node.js code will be improved and have new features
added going forward, it makes sense that both the PHP and Node.js code will need to
“give” a little in their purity to smooth over the differences between how the two lan‐
guages function. The PHP code will need to be refactored and make some sacrifices that
will allow functional Node.js code to be created later on. At the end of the conversion
process, both codebases will look very similar and will be written in a sort of hybrid
metalanguage, a collection of idioms and algorithms that are easily ported from PHP to
Node.js. The metalanguage will make both codebases look a little odd, but will be fully
functional and, with time, will become very familiar and understandable to the devel‐
opers who maintain and improve both codebases. Even if you plan to throw away the
PHP code in the end and want to have pristine Node.js code, it is best to refactor the
PHP code anyway, convert both the PHP and Node.js code into the odd hybrid meta‐
language, throw away the PHP code, and then refactor the hybridized Node.js code into
pure Node.js code. Refactoring PHP code is an essential step for any PHP to Node.js
conversion, no matter what your eventual goal is.
The third step is to copy and paste one of the PHP pages from the PHP source file into
the Node.js source file. Almost certainly, the Node.js server will then be broken; when
the node executable is run on it, it will immediately exit with a stack trace.
The node and npm Executables | 5
ThefourthstepistoconvertandfixthenewlyaddedcodeintheNode.jsfile,asdescribed
in detail in the remaining chapters, such that it becomes working Node.js code. Initially,
the Node.js server will not run and will immediately exit with a stack trace. The stack
trace will indicate the location of the error, which will be caused by some PHP code that
was not completely converted or was not converted correctly to Node.js code. After the
problem is analyzed, a conversion technique from one of the remaining chapters will be
applied to the entire Node.js file; for example, Chapter 7 shows the technique to convert
PHParrayinitializationusingthearray()functiontoNode.jsobjectinitializationusing
curly brackets ( { and } ). When the Node.js server is run again, it will get a little further
along, but will most likely continue to exit with a stack trace. Eventually, the Node.js
code will be good enough such that it will not immediately exit with a stack trace.
It is surprising how much unconverted PHP code can exist in a Node.js source file and
not cause the Node.js server to immediately exit with a stack trace. As you become
familiar with the conversion process, you will learn just how similar PHP and Node.js
are, even such that unconverted PHP code will be parseable by the node executable and
will allow the node executable to run and accept HTTP requests and fail only when it
needs to actually execute some unconverted PHP code.
Once the Node.js code is good enough that it does not immediately exit with a stack
trace, you can begin to test the client against it. The client will usually be a browser, like
Firefox or Google Chrome. Usually, when you start trying to use the client, the Node.js
code will exit with a stack trace at some point, and then you will need to analyze the
stack trace and apply a conversion technique to fix the problem. Over time, you will
develop an ad hoc series of test cases that you can execute with the client to reveal
unaddressedconversionissuesorhopefullytoconfirmthattheNode.jsserverisrunning
correctly.
At times, it will also help to use a visual diff tool to compare the PHP code and Node.js
code; by viewing it side by side with the original PHP code, you can more easily locate
issues in the new Node.js code. This will help remind you of conversion techniques that
you have not used yet but need to use. It will also help you keep the conversion process
on track and under control.
The rest of the PHP to Node.js conversion process is simply a matter of applying a
combination of previous steps many, many times until all the PHP code has been con‐
verted to Node.js code and the Node.js code works reliably and interchangeably with
the PHP version. Depending on the size of the PHP codebase, the conversion process
may take months, but—if you are determined—the conversion will be accomplished.
6 | Chapter 1: Node.js Basics
Stack Traces
During the conversion process, you will see a lot of stack traces. A lot. Here’s an example
stack trace that is generated because the node-static npm package was not installed
using the npm executable before the httpsvr.njs was run:
module.js:337
throw new Error("Cannot find module '" + request + "'");
^
Error: Cannot find module 'node-static'
at Function._resolveFilename (module.js:337:11)
at Function._load (module.js:279:25)
at Module.require (module.js:359:17)
at require (module.js:375:17)
at Object.<anonymous> (httpsvr.njs:2:14)
at Module._compile (module.js:446:26)
at Object..js (module.js:464:10)
at Module.load (module.js:353:31)
at Function._load (module.js:311:12)
at Array.0 (module.js:484:10)
The top of the stack trace shows the code that threw the error. This is not the code that
caused the error; this is the code that created and threw the error object.
Below that, the error message inside the Error object is shown. This error message
indicates that the node-static module could not be found.
The remainder is the “call stack,” a series of function calls indicated by the word “at” that
show the chain of function calls that arrived at the code that threw the error. The call
stack is listed from innermost call to outermost call. In this case, the Function._resol
veFilename() function is the call at the top of the call stack, which indicates that it is
the innermost call and thus the one that actually contains the code that threw the error.
The Function._resolveFilename() function was called by the Function._load()
function, which was called by the Module.require() function, which was called by the
require() function, which was called by the Object.<anonymous>() function, and
so on.
After each function call in the call stack, you will see the filename of the source file that
contains that function, the last line that was executed (which is either the line that called
the function above it or the line that actually threw the error object), and the position
in the line that was last executed. In the example, you can see that two source files are
involved: module.js and httpsvr.njs.
The module.js file resides inside the node executable; we can guess that because we do
not recognize it as one of our files. The httpsvr.njs file is part of our own source code.
Even though httpsvr.njs is referenced only once and is in the middle of the call stack, it
is safe to assume that the error was caused by our source code. In general, we can assume
that Node.js itself, its built-in modules, and even any installed npm modules are in
Stack Traces | 7
perfect working order. Even if they are not, we must assume that they are working until
we prove otherwise by eliminating all errors from our calling code. Even if we discover
that the error originates elsewhere, we have control only over our own code, not over
any other code. The solution would likely be to create a workaround in our own code
rather than take on the long and slow process of lobbying other developers to fix their
code. So, in the end, regardless of where the ultimate fault may be, the first place to focus
our attention is on the httpsvr.njs file.
The part of the call stack to focus our attention on is:
Object.<anonymous> (httpsvr.njs:2:14)
This function call is on line 2 at position 14 in the httpsvr.njs file. Here’s the httpsvr.njs
file:
var http = require('http');
var static = require('node-static');
var file = new static.Server();
http.createServer(function (req, res) {
file.serve(req, res);
}).listen(1337, '127.0.0.1');
console.log('Server running at http://127.0.0.1:1337/');
By cross-referencing the call stack with the source code, the require() function that
attempts to load the node-static module is the function call in which the error occur‐
red. This is consistent with the error message: “Cannot find module ‘node-static’”.
If we look up the call stack, we see the Function._load() function and the Function
._resolveFilename() function at the top. Looking at the name of these two functions,
we guess that the Node.js environment is having difficulty loading the module because
it cannot find the file that is associated with the module. We can guess that the module
file (probably the npm package) is missing because it has not been installed yet. Again,
this is consistent with the error message: “Cannot find module ‘node-static’”.
The Object.<anonymous> so-called function probably indicates that the require()
function call was made in the global space, instead of within a user-defined function in
httpsvr.njs.Butthatisnotalwaysthecase.Ananonymousobjectmaybegeneratedinside
a user-defined function. But farther down the call stack, below the Object.<anony
mous> function call, we see that the caller was the Module._compile function in the
module.js file. The require() function call was made in the global space.
Usingallthisinformation,onesolutionistotrytoinstallthenode-static npmpackage:
npm install node-static
8 | Chapter 1: Node.js Basics
Admittedly, you won’t need to do all this analysis every time you see a Node.js call stack.
But since you will be seeing many, many call stacks, you should understand how to
thoroughlyanalyzeone—especiallybecausecatchingandfixingerrorsiswhattakes95%
of the time in a PHP to Node.js conversion.
In summary, here’s the process to analyze a call stack: read the error, look at the error
message (if any), take a guess and focus on a particular function call in your own code,
look at the code and find the line and perhaps even the position of the error, look up
the stack to see if it indicates more detail about what the error might be, and look down
the stack to see how the execution of the server got to that particular function call.
Eclipse PDT
Learning how to fully analyze a stack trace is one helpful skill for doing a successful PHP
to Node.js conversion. A stack trace is a diagnostic tool for figuring out what is wrong
with the code, like an x-ray is used by a doctor to figure out what is wrong with his
patient. From a certain point of view, converting PHP to Node.js can be seen as similar
to a complex surgery on a patient. You will be performing surgery on PHP and Node.js
code. Like performing surgery, it takes a lot of skill and tenacity, but having a good
environment can really help, too. Just like the x-ray is a tool used in the operating room,
the stack trace will be a tool in the development environment for the conversion. Next,
we will discuss integrated development environments, which will provide a sort of “op‐
erating room theater” for the conversion process.
Since you will probably be dealing with dozens of PHP files and tens of thousands of
lines of PHP and, very soon, dozens of Node.js files and tens of thousands of lines of
Node.js, a simple plain text editor will probably not be good enough to keep track of
everything and keep the conversion process efficient. A plain text editor will be fine
when you are typing in some simple examples to learn how to program using Node.js,
but when you are dealing with a large amount of PHP and Node.js code, you will need
something more effective.
If you were developing PHP or Node.js code by itself, you could choose a single language
integrated development environment (IDE) and use it nearly straight out of the box.
Eclipse PDT (PHP Development Tools) is a popular PHP IDE written in Java that is
produced by the Eclipse Foundation. Some others are Zend Studio, PHPEdit, and
Dreamweaver.OntheNode.jsside,therearefewerchoices,andtheyareofmoredubious
popularity and effectiveness. At the time of this writing, I found Komodo Edit, nide,
and Cloud9.
Eclipse PDT | 9
However, your objective is to convert PHP code to Node.js code while simultaneously
improving and adding features to both codebases. To do this effectively, I recommend
using the Eclipse PDT, but with some modifications to help it support Node.js code.
Additional knowledge on how to easily compare PHP and Node.js code will be needed
to support the conversion process.
Now, before I describe how to set up Eclipse PDT for PHP to Node.js conversion, I
should briefly address developers who reject such tools and insist on using simple plain
text editors. They say, “I only use vi!” If you are somebody who feels this way, you are
free to skip the rest of this chapter and set up your conversion environment in any way
that works for you. I am describing the installation and modification of Eclipse PDT
hereonlybecauseitwasanessentialtoolformetodomyownPHPtoNode.jsconversion
project and it will be an essential tool for a lot of other developers as well.
To install Elipse PDT, first download Java. All the Eclipse IDEs are developed in Java
and need Java to run, including the Eclipse PDT. I prefer to install the Java JDK instead
of the JRE. At the time of this writing, I am using jdk-6u29-windows-i586.exe.
Next, browse to here. Consider using the Zend Server Community Edition (CE) instal‐
lation, which includes Eclipse PDT, the Zend Server HTTP server with built-in PHP
debugging support, and even the MySQL database. I assume that your PHP web appli‐
cation uses the MySQL database or at least has the MySQL database as an option.
As of this writing, there is a PDT and Zend Server Community Edition link on the
Eclipse PDT downloads page. If the link does not exist or you have a different web server
already running, download the latest stable Eclipse PDT version that is appropriate for
your operating system. Then, skip the next few paragraphs until the text describes in‐
stalling and configuring the Eclipse PDT. Otherwise, follow the link and download the
Eclipse PDT for Zend Server CE. For now, I am using zend-eclipse-php-helios-win32-
x86.zip. Unzip but do not run the Eclipse PDT yet.
From the same web page, download Zend Server CE itself. At this time, I am using
ZendServer-CE-php-5.3.8-5.5.0-Windows_x86.exe.
Install Zend Server CE. In brief, choose sensible, mostly default, selections until the
Setup Type page. Select the Custom radio button on the Setup Type page, instead of the
Typical radio button, and press the Next button. Check the “MySQL Server (separate
download)” checkbox from the Custom Setup page. Then finish the installer.
Currently, Zend Server CE shows a browser to configure the way that it operates. In our
case, no special configuration is needed for the server itself.
The MySQL database server is installed and configured as part of the Zend Server CE
installer. By default, the root password for the MySQL database server is the empty
string (a.k.a. “”).
10 | Chapter 1: Node.js Basics
Run the Eclipse PDT. Zend Server CE is built on Apache 2 and has an htdocs folder.
When the Eclipse PDT runs, find and select the htdocs folder as the Eclipse PDT Work‐
space folder. If you are using a different web server than Zend Server CE or Apache,
select the document root as the Eclipse PDT Workspace folder so the PHP files that are
deployed to the web server can be edited in place.
It is beyond the scope of this book, but if you wish, try to experiment with using the
PHP debugger on your existing PHP codebase.
The Eclipse PDT and your web server will be the foundation of your “conversion de‐
velopment environment.” Now, let’s make some modifications and learn how to use the
Eclipse PDT to effectively manage and implement the conversion process.
The Eclipse PDT, by itself, already supports JavaScript files, and since Node.js is Java‐
Script, it supports Node.js. But because the .njs file extension is nonstandard, Eclipse
PDT does not recognize a .njs file as a Node.js file. So if a .njs file (e.g., httpsvr.njs) is
opened in Eclipse PDT, it is shown as plain text with no syntax coloring or popup code
completion like in a regular JavaScript (.js) file.
To modify Eclipse PDT to recognize .njs files as Node.js files, open the Window menu
from the Eclipse PDT main menu and select the Preferences menu item. When you do
this, you will see the Preferences dialog box with two inset panes (Figure 1-1). In the
left pane, you will see a tree control with a hierarchically organized group of categories
and subcategories of preferences. In the right pane, you will see a dialog that allows you
to view and edit the preference items for the currently selected category in the left pane.
In the left pane, open the General tree folder item, then select the Content Types tree
item. In the right pane, you will see a list of content types. Open the Text tree folder item
in the “Content types” tree control in the right pane. Beneath the Text tree folder item,
select the JavaScript Source File tree item. When you select the JavaScript Source File
tree item, you should see a list box with a single item, “*.js (locked)”, in the “File asso‐
ciations” list box along with an Add… button on the middle-right of the pane. Press the
Add… button. Once the Add… button is pressed, the Add Content Type Association
dialog box should pop up (Figure 1-2). You will type *.njs into the “Content type” edit
box in that new dialog box.
Then, press the OK button on all the open dialog boxes to store the modifications.
When that modification is saved, JavaScript syntax coloring and code completion will
work for Node.js source files that are stored as .njs files.
Eclipse PDT | 11
Figure 1-1. Eclipse PDT Preferences dialog box
With syntax coloring working for .njs files, you can spot simple Node.js syntax errors
by noticing that some words have the wrong color. Visual inspection is an important
part of any programming project, particularly in a PHP to Node.js conversion project.
Another useful visual inspection technique is comparing the PHP and Node.js code‐
bases using an advanced and very visual diff viewer to find out all kinds of things about
the quality and progress of the conversion.
A diff program shows the difference between two files. Simple text-based diff programs
usually print out the differences as monochrome lines of text, each line from a single
file. That kind of diff program is useless for analyzing a PHP to Node.js conversion. A
sophisticated visual diff program is needed. Instead of showing files as alternating lines
of text, the files will be shown side by side. Instead of monochrome, color will be used.
Insteadofshowingonlywhichlinesaredifferent,thedifferenceswithinthelines—down
to the character level—will be reconciled and shown.
12 | Chapter 1: Node.js Basics
Figure 1-2. Eclipse PDT Add Content Type Association dialog box
Eclipse PDT has an advanced visual diff viewer built in. We can use this viewer to
compare a .php file to its corresponding .njs file. To use the viewer of a .php file and
a .njs file, select both files. Then, right-click one of them and select the Compare With
submenu and then the Each Other menu item within that submenu. Figure 1-3 shows
a screenshot of the Eclipse PDT viewer comparing a simple .php file with its corre‐
sponding .njs file.
Eclipse PDT | 13
Figure 1-3. Eclipse PDT Compare view
You do not need to look at the figure in detail to either understand how it works or see
just how similar PHP and Node.js are.
On the left is the rand.njs file. On the right is the rand.php file. The differences are in
gray; the identical sequences of characters that have been matched are in white.
Notice how many of the lines are almost completely in white, except for a stray dollar
sign ($) in gray. Both PHP and Node.js use the keyword function in the same place and
put the name of the function in the same place. Over the years, it has become common
for new languages to eschew variation in syntax structure and adopt a similar syntax
for things like defining functions. Also, notice that the while statement is very similar.
It benefits the developer to make it easy for the visual diff to compare the .php file to its
corresponding .njs file. The visual diff feature in Eclipse PDT is very good but it is not
infallible. Sometimes, moving around code in either file may allow the comparison
algorithm of the visual diff feature to find more matches; that is, the visual diff feature
will show more white in both files. Copying a function so that it is earlier or later in a
14 | Chapter 1: Node.js Basics
file might be irrelevant to the performance and functionality of the code, but might
make the visual diff feature match up the code much more accurately. It is worth spend‐
ing some time periodically throughout the conversion process to experiment with mov‐
ing code around in each file and seeing the effect on the comparison.
In Eclipse PDT, the code can be edited in a separate window, in the comparison window
itself, or in both. If it is edited in a separate window and saved, any comparison windows
that show the same file will be reloaded and recompared. Making some tweaks in a
separate window and saving the file so that the effect on the comparison can be deter‐
mined is a common technique.
Naturally, it really helps to keep the code in the same format in both files to use the same
names for everything (such as functions and variables), and even to refactor the code
in one or both files such that the visual diff feature will find as many matches as possible.
To keep the PHP and Node.js code synchronized and simultaneously improve and add
features to both codebases, you will often rely on the visual diff to make sure that the
PHP and Node.js code are correct. In time, a developer will develop a finely tuned sense
of what is not enough white and what is too much white.
When there isn’t enough white, the visual diff feature usually is getting off track and
trying to match PHP code in the .php file to Node.js code in the .njs file, which is not
meant to be matched. There will be a lot of gray in the comparison, indicating differ‐
ences, and not each matches. Experimentation will often correct this issue.
When there is too much white, it often means that there is some PHP code in the .njs
file that has not been converted completely to Node.js code. Even though the .njs file
can be parsed and run, too much white indicates that more conversion is needed. Often,
eyeballing the Node.js code will indicate specific conversions that have not been done
yet. One simple conversion that may be missed is that dollar signs ($) need to be added
to PHP variables; dollar signs are not used on Node.js variables. Adding dollar signs to
the PHP code will reduce the amount of white, bringing the comparison closer to having
the right amount of white.
Visualinspection,especiallyusingthevisualdifffeature,ismuchfasterthaninteractively
testing the PHP and the Node.js code. Visual inspection can act as a “smoke test” to
determine if the conversion is approximately correct. Automated test cases, which are
beyond the scope of this book, may also be used to quickly test the effectiveness of the
conversion so far.
Throughout the book, there will be opportunities to convert a particular code element
of a large amount of PHP code into the corresponding code element for Node.js code.
For example, a PHP associative array is created by calling the PHP array() function,
whereas in Node.js, it is often created by using the object literal notation, which uses
curly brackets ( { and } ). When the contents of an entire .php file are copied wholesale
into a .njs file at the start of the conversion of the code, the .njs file will then obviously
Eclipse PDT | 15
contain many PHP array() function calls that will need to be replaced by Node.js object
literals. A simple way to address this particular conversion issue might be to simply use
Eclipse PDT’s Find/Replace feature to do a global search for array( and universally
replace it with a left curly bracket ( { ); see Figure 1-4.
Figure 1-4. Eclipse PDT Find/Replace dialog box
The operation of this dialog box is straightforward.
Rather than including a screenshot of the Find/Replace dialog box every time that it is
needed, this book uses a text shorthand. For the Find/Replace dialog box options in the
figure, the text will have the following blurb inserted:
Operation: "Find/Replace" in Eclipse PDT
Find: array(
Replace: {
Options: Case sensitive
Action: Replace All
The Find/Replace dialog box can be used in two different ways.
One way is to do what I call a “blind” global find-and-replace action, like the example
find-and-replace blurb in Figure 1-4. I call it “blind” because it finds and replaces every
occurrence in the file all at once, with no warning and no manual inspection. If all the
Find/Replace dialog box values are tested and determined to be foolproof, a “blind”
global find-and-replace action is fast and accurate. Unfortunately, if the result causes an
error, there are only two options: undo the action or perform a new action that corrects
the previous action.
The second option for find-and-replace action repair work is worth pointing out. Some‐
times,itisbettertodoasimple-to-understandfind-and-replaceactionthatwillcorrectly
16 | Chapter 1: Node.js Basics
convert 298 code elements and incorrectly convert two code elements than it is to do a
complicatedfind-and-replaceactionthatcorrectlyconvertsthesame300codeelements.
Manually finding and fixing a few edge cases is a worthwhile technique; not everything
needs to be fully automatic. Even though PHP to Node.js conversion is a lengthy task,
it is not a task that you will be running over and over. This book is not describing
“continuous conversion”; it is describing conversion as a one-time event. So manually
finding and fixing a few edge cases is a perfectly acceptable technique to get the job
done.
A second way to use the Find/Replace dialog box is to do a step-by-step global find-
and-replace action. First, the Find/Replace dialog box is used to find the first instance.
The developer then examines the instance and decides whether to modify the code
manually (which he can do by clicking on the code and without dismissing the dialog
box), or to execute the replace (by pressing the Replace/Find button), or to skip to the
next instance without changing the current instance (by pressing the Find button again).
Here’s the blurb for a step-by-step global find-and-replace action:
Operation: "Find/Replace" in Eclipse PDT
Find: array(
Replace: {
Options: Case sensitive
Action: Find, then Replace/Find
The Find/Replace dialog box in the Eclipse PDT can also use regular expressions. Reg‐
ular expressions are a pattern matching technique: instead of finding an exact phrase,
aregularexpressiondescribesapatterntosearchfor.Eachtimethatthepatternisfound,
the exact phrase that matches the pattern can be applied to the Replace field. For ex‐
ample, if the array((.*)) regular expression matches array(id=>'name'), the (.*)
in the regular expression will save the id=>'name' text. This saved text is called a capture
field, or less commonly, a capture group. In the Eclipse PDT Find/Replace dialog box,
a capture field is captured by surrounding it with undelimited parentheses. To apply a
capture field to the Replace field, the capture fields are enumerated according to the
order that they were captured in the Find field. A dollar sign ($) indicates that a capture
field is being specified, followed by the capture field number. For example, $1 in the
Replace field indicates the first capture field, which, in the example earlier in this para‐
graph, would contain the id=>'name' text. Very often, there is only one capture field,
so it is very common to only see $1 and rarely to see $2, $3, or beyond.
Here’s a blurb for a blind global find-and-replace action using regular expressions:
Operation: "Find/Replace" in Eclipse PDT
Find: array((.*))
Replace: {$1}
Options: Case sensitive, Regular expressions
Action: Replace All
Eclipse PDT | 17
In converting PHP to Node.js, regular expressions are only tangential to the process, so
this book will not be giving a primer on how to understand and write your own regular
expressions. The regular expressions will be provided as part of blurbs for find-and-
replace actions that can be copied to the appropriate fields of the Find/Replace dialog
boxintheEclipsePDT,usuallyverbatim,withoutrequiringyoutounderstandormodify
them. If you need additional help with regular expressions or need to understand the
rules and how they work, you are encouraged to consult the Eclipse PDT and to use
Google or a similar search engine to find websites, blogs, and forums that will answer
your questions.
Find-and-replace actions with regular expressions are often more comprehensive and
effective than literal find-and-replace actions (i.e., actions where only one specific string
is matched). A regular expression allows more variation in what it can match, and with
capture fields, it can transport that variation to the Replace field. Often, a literal find-
and-replace will be able to match only the beginning of a code element or the end of a
codeelementatonetimebecausethecodeelementcanvaryinthemiddle.Witharegular
expression, the middle can be matched to a pattern that allows the entire code element
to be matched in a single find-and-replace action. When the conversion of a code ele‐
ment can be done in a single find-and-replace action, instead of multiple ones, the
chances for errors are reduced.
Until now, this chapter has described a range of activities and knowledge about how to
set up a development environment for doing a PHP to Node.js conversion. The first
thing to do was to download and install Node.js itself and become familiar with the two
executables that it comes with. After that, we dug into Node.js stack traces to learn how
to read them and how to use them to find what the real, underlying problem is such that
the coding issue can be addressed and repaired. Then, we set up the Eclipse PDT as a
foundation for a development environment, including a modification for it to under‐
stand .njs files, geared toward PHP to Node.js conversion. And finally, we learned how
to use the visual diff feature and find-and-replace actions that will be very important
when doing the conversion.
A capable development environment is essential to efficiency and is the way that big
efforts get done. Too often, amateur developers will leap into coding with an inefficient
orevenanannoyingdevelopmentenvironment.Atfirst,thedevelopmentwillgoquickly
in any environment because a small amount of code is simple to improve upon. But as
the codebase grows larger, the complexity of the code will also grow and the pace of
development will slow down. An inefficient or annoying development environment will
do nothing to help the developer with the complexity, but a capable development envi‐
ronment will simplify the knowledge needed and help the developer such that the pace
can be sustained and, ultimately, the project finished.
WithaPHPtoNode.jsconversion,itisassumedthatalargePHPcodebasealreadyexists.
At the end of the conversion, it is expected that the codebase will more than double in
18 | Chapter 1: Node.js Basics
size: the PHP code will be refactored for conversion, not brevity, so it will increase, and
of course, an entire Node.js codebase will be added. The initial PHP codebase might
have been created by many developers, but in conversions, there is often so much cou‐
pling between the activities that only a single developer will do the majority of the
conversion. Even though a primitive development environment might have been ac‐
ceptable for the creation of the original PHP codebase, a more sophisticated develop‐
ment environment will be needed to convert it to Node.js.
If a project already has an existing development environment, it may not be necessary
to adopt the Eclipse PDT. The Eclipse PDT is presented as a workable, prototypical
environment suitable only for conversion activities. Alternative development environ‐
ments can work if they can support and be coupled with additional tools that support
the features in this chapter. In summary, they need to be made to support the following
syntax coloring for both .php and .njs files, visual side-by-side comparison between two
files down to a word-by-word comparison and not just line-by-line comparison, and
find-and-replace actions that support regular expressions.
Now that all the infrastructure for the conversion is ready, we can move on to creating
the initial .njs file that will host the new Node.js code. In the next chapter, a template
for an initial .njs file will be presented such that, in subsequent chapters, PHP code can
be refactored for conversion and actual PHP code can be copied into Node.js files and
transformed into working Node.js code.
Eclipse PDT | 19
Nodejs For Php Developers Porting Php To Nodejs 1st Edition Daniel Howard
CHAPTER 2
A Simple Node.js Framework
In the previous chapter, I presented a development environment along with a general
education about how to use it to execute a conversion. In this chapter, we will start using
that development environment and begin the actual conversion.
An HTTP Server
In PHP, a PHP file represents an HTML page. A web server, such as Apache, accepts
requests and if a PHP page is requested, the web server runs the PHP. But in Node.js,
the main Node.js file represents the entire web server. It does not run inside a web server
like Apache; it replaces Apache. So, some bootstrap Node.js code is needed to make the
web server work.
The httpsvr.njs file was presented as an example in the previous chapter. Here’s the
Node.js code for the httpsvr.njs file:
var http = require('http');
var static = require('node-static');
var file = new static.Server();
http.createServer(function (req, res) {
file.serve(req, res);
}).listen(1337, '127.0.0.1');
console.log('Server running at http://127.0.0.1:1337/');
How does this work?
As described in the previous chapter, the Node.js require() API function makes a
module available for use. The first two lines show a built-in module and an external
module:
var http = require('http'); // built-in module
var static = require('node-static'); // external module
21
If you installed Node.js and followed the examples in the previous chapter, the node-
static npm package, which contains the node-static external module, will already be
installed. If not, install it now using the npm executable:
npm install node-static
The third line is a little tricky:
var file = new static.Server();
The node-static module wants to provide the Node.js server with as many file serving
objects as needed rather than limit the client to a single file serving object. So, instead
of using the module itself as the file serving object, the file serving object is created by
calling a constructor function. A constructor function is a function that is designed to
be used with the new keyword. Using the new keyword and invoking the constructor
function creates a new object.
In this case, the module object is named static. Inside the module object, there is a key
named Server, which has a value that is a constructor function. The dot (.) operator
indicates this relationship such that the new operator is properly applied. A newly con‐
structed file serving object is created and stored in the file variable.
The file serving object constructor can take zero or one arguments. If no arguments are
provided, the file serving object uses the current directory (folder) as the HTTP server’s
top-leveldocumentsdirectory.Forexample,ifthehttpsvr.njsisrunintheferrisdirectory
and a web browser such as Google Chrome goes to http://127.0.0.1:1337/hello.html, the
file serving object will look for the hello.html file in the ferris directory. However, if a
web browser goes to http://127.0.0.1:1337/exit/goodbye.html, the file serving object will
look for the goodbye.html file in the exit directory inside the ferris directory.
However, when one argument is passed to the file serving object constructor, the file
serving object will look for files in the directory specified in the argument. For example,
if the .. string is passed as the argument, the newly created file serving object will look
for files in the parent directory of the current directory.
Therequire()functiontakesonlyoneargument,thenameofthemoduletoload.There
is no flexibility to pass additional arguments to the module while loading. Since it is
desirable to specify a directory to load files from as an argument, it is best that loading
the module be completely separate from specifying where to load files from.
After creating a file serving object, the HTTP server object is created to accept HTTP
requests and return the file to the client, probably a web browser:
http.createServer(function (req, res) {
file.serve(req, res);
}).listen(1337, '127.0.0.1');
22 | Chapter 2: A Simple Node.js Framework
This code can be rewritten as three statements to make it easier to understand:
var handleReq = function(req, res) {
file.serve(req, res);
};
var svr = http.createServer(handleReq);
svr.listen(1337, '127.0.0.1');
The first statement takes up three lines. It defines a variable named handleReq that,
instead of containing a “normal” value like a string or a number, contains a function. In
Node.js, functions can be assigned to variables, just like strings and numbers. When a
function is assigned to a variable, the function itself is called a callback, and for our
convenience, the variable that it is assigned to is called a callback variable. A callback
variable is defined nearly the same as a regular function is defined, except that a callback
can be unnamed and is preceded by a variable assignment.
In this case, the callback expects two parameters. The first parameter, req, contains all
the data related to the HTTP request. The second parameter, res, contains all the data
related to the HTTP response. In this implementation, the file serving object, file,
decodes the HTTP request, finds the appropriate file on the hard disk, and writes the
appropriate data to the HTTP response such that the file will be returned to the browser.
The node-static module was specifically written with this in mind, so that the file
serving object could return hard disk files with only one line of code.
The fourth line creates an HTTP server and an HTTP request handling loop that will
continuously wait for new HTTP requests and use the handleReq callback variable to
fulfill them:
var svr = http.createServer(handleReq);
Inside the createServer() function, the handleReq callback variable will be invoked:
function createServer(handleReq) {
while (true) {
// wait for HTTP request
var req = decodeHttpRequest(); // somehow decode the request
var res = createHttpRequest(); // somehow create a response object
handleReq(req, res); // invoke handleReq()
// send HTTP response back to client based on "res" object
}
}
Like functions (but unlike other types of variables), a callback variable can invoke the
function that it contains. As you can see, invoking the handleReq callback argument is
identicaltocallingastandardfunction;itjustsohappensthathandleReq isnotthename
of a function but is the name of a callback variable or argument. Callback variables can
be passed to functions as arguments just like other kinds of variables.
Why not just hardcode the file.serve() call into the createServer() function? Isn’t
serving files what a web server does?
An HTTP Server | 23
function createServer(handleReq) {
while (true) {
// wait for HTTP request
var req = decodeHttpRequest(); // somehow decode the request
var res = createHttpRequest(); // somehow create a response object
file.serve(req, res); // hardcode a reference to the "node static" module
// send HTTP response back to the client based on "res" object
}
}
Yes, but passing a callback to the createServer() function is more flexible. Remember:
the http module is built into Node.js and the node static module is an npm package
that was installed separately. If the file.serve() call was baked into the create
Server() function, using a different module instead of the node static module or
adding additional custom HTTP request handling code would require copying and
pastingtheentirecreateServer()functionjustsoalineinthemiddlecouldbetweaked.
Instead, a callback is used. So, if you think about it, a callback is a way for some calling
code to insert some of its own code into a function that it is calling. It is a way to modify
the function that it is calling without having to modify the code of the function itself.
The function being called, createServer() in this case, has to expect and support the
callback, but if it is written with the callback in mind, a caller can create a callback that
matches what is expected and the called function can use it without knowing anything
aboutthecallingcode.Callbacksenabletwopiecesofcodetosuccessfullyworktogether,
even though they are written by different people at different times.
In this case, a callback function is passed to allow the caller to handle an HTTP request
in whatever way that it sees fit. But, in many other cases, a callback function is passed
as an argument so that the callback function can be invoked when an asynchronous
operation has been completed. In the next chapter, this use of callback functions will be
covered in detail.
The fifth line uses the svr object to listen on port 1337 on the '127.0.0.1' computer,
a.k.a. the “localhost” computer (meaning the computer that the Node.js server is run‐
ning on):
svr.listen(1337, '127.0.0.1');
It should be pointed out that it is much more likely that the HTTP request handling
loop is in the listen() function instead of the createServer() function. But for the
purposes of explaining callbacks, it really does not matter.
SincethesvrvariableandthehandleReqvariableareusedonlyonceandcanbereplaced
with more succinct code, the three statements are combined into one:
http.createServer(function(req, res) {
file.serve(req, res);
}).listen(1337, '127.0.0.1');
24 | Chapter 2: A Simple Node.js Framework
Thelaststatementofthehttpsvr.njsfilewritesamessagetotheconsolesothattheperson
who started the HTTP server will know how to access it:
console.log('Server running at http://127.0.0.1:1337/');
The httpsvr.njs file makes a basic Node.js HTTP server. Now we move all the files that
constitute the web application from the current web server that supports PHP to the
same directory that the httpsvr.njs file resides in. When the httpsvr.njs file is started,
these files—which include all the HTML, CSS, client-side JavaScript, image files (e.g.,
PNG files), and other assorted files—will be delivered to the client, probably a web
browser, and work just as they always have. The client needs to be directed only to the
correctport(e.g.,1337)toloadthesefilesfromNode.jsinsteadoftheoriginalwebserver.
The only reason that the web application will break is that it is still written in PHP, but
since HTML, CSS, client-side JavaScript, and image files are all handled and executed
exclusively on the client, they will work as much as they can until a PHP file is needed.
The .php files can be moved to the Node.js server, too, but they will not work because
Node.js cannot interpret .php files.
The key difference between the .php files and all the other files is that the .php files are
interpreted and the result of the interpretation is passed back to the client as an HTTP
response. For all other files, the contents of the file are read and then written directly
into the HTTP response with no interpretation. If the .php file is not interpreted, the
client receives the PHP source code, which it does not know how to use. The client needs
the output of the source code, not the source code itself, in order to work. A PHP to
Node.js conversion boils down to writing Node.js code that will produce the exact same
HTTP response in all cases that the PHP source code would have produced. It seems
simple, but it is still a ton of work.
To start with, a Node.js local module will be created for each .php file. For the purposes
of this book, a local module is a local .njs file that can be loaded by the main .njs file
(i.e., httpsvr.njs) using the Node.js require() API function. To create a Node.js local
module for a .php file, we will create an empty .njs file in the same directory as
the .php file using the same filename but a different extension. For example, for admin/
index.php, create an empty admin/index.njs file.
For your own conversion effort, you will have to use your own judgment. In some cases,
there may be so many .php files that creating the corresponding .njs files will be a lot of
busywork, so it may be better just to do a few at first. In other cases, there may be only
a few .php files, so it may make sense to create all the empty files at once.
Once there is a corresponding .njs file for some .php files, choose one of the .php files
and edit its corresponding .njs file:
exports.serve = function(req, res) {
res.writeHead(200, {'Content-Type': 'text/plain'});
res.end('admin/index.njs');
};
An HTTP Server | 25
Type this Node.js code into the .njs file and change the res.end() function call imple‐
mentation to indicate the correct path (in this case, admin/index.njs) to the particu‐
lar .njs file that is being edited.
With this simple code, the .njs file is now a Node.js module with an exports.serve()
stub function implementation. A stub implementation is a placeholder with some very
simple code that will be replaced with a serious implementation later. The exports
.serve() stub function takes two parameters that correspond to the parameters to the
callback function that is passed to the http.createServer() function in httpsvr.njs.
The req parameter is the HTTP request object and the res parameter is the HTTP
response object.
When the exports.serve() function is invoked, the stub implementation will return
an HTTP response with a Content-Type HTTP header set to “text/plain” with the con‐
tent containing the path name of the file that is being invoked. By customizing the stub
implementation, it will be a little easier to debug later, in case a coding error leads to an
HTTP request for one file accidentally ending up in another file.
Once you have a .njs file with a exports.serve() stub function, you should modify the
httpsvr.njsfiletoinvoketheexports.serve() functionattheappropriatetime.Butfirst,
the module with the exports.serve() function must be made accessible to the
httpsvr.njs file. The Node.js require() API function can load local modules as well as
built-in module and npm packages:
var admin_index = require('./admin/index.njs');
To load a local module, the argument must contain a path name with the ./ string at
the beginning. The ./ indicates that this is a path name to a local module and not a
name of a built-in module or npm package.
In this example, the local module is assigned to the admin_index variable. Depending
on the number of .php files that are in a web application (and, thus, the number of
corresponding .njs files), it may be convenient and less confusing to use a variation of
thepathnameasthenameofthevariablethatholdsthemodulethatcontainstheNode.js
code that implements the functionality of a particular .php file.
To route an HTTP request for a particular PHP page to the correct Node.js local module,
the callback passed to the http.createServer() function needs to be modified to
identify and pass the HTTP request to the appropriate Node.js local module. Using a
simple comparison in an if statement, the HTTP request is examined to see if it is
requesting a particular .php file and if it is, it is routed to the appropriate Node.js local
module instead of passing it to the node static npm package (i.e., the file variable):
http.createServer(function (req, res) {
if (url.parse(req.url).pathname == '/admin/index.php') {
admin_index.serve(req, res);
26 | Chapter 2: A Simple Node.js Framework
} else {
file.serve(req, res);
}
}).listen(1337, '127.0.0.1');
This source code uses the http and url Node.js built-in modules to implement a simple
but functional Node.js server. If desired, more sophisticated optional packages, such as
the express Node.js npm package, could be installed and used instead.
For this implementation, the url built-in module is needed to parse the URL in the
HTTP request. The following Node.js code uses the require() API function to access
the url built-in module:
var url = require('url');
Here we continue with our previous example. If the HTTP request is requesting
the /admin/index.php URL resource, the admin_index local module interprets the re‐
quest and provides the HTTP response instead of allowing the file variable to read the
file and return it as a static page.
You may notice that the URL in the HTTP request is tested against /admin/index.php
insteadof/admin/index.njs.Sincethebrowserhandlesrequestsfor.phpfileswithcustom
code, it can handle .php file requests in any way that it likes, including ignoring
the .php file on the hard disk and running Node.js code instead. An HTTP request is a
request, not a demand, and the Node.js server can force .php file requests to be handled
without using PHP at all. In Node.js, the .php file reference becomes a unique identifier
to perform a particular action; the .php extension no longer means PHP. The if-else
statements in the callback now just match a unique path with a specific piece of Node.js
code. The semantic meaning of the .php extension is ignored.
Most likely, the client is requesting .php files either because the user clicked on a link in
an HTML page that had the .php file as a hyperlink reference or because it is part of a
JavaScript AJAX call. To change this, the HTML and client-side JavaScript files would
need to be modified to refer to .njs files instead of .php files. Whether or not you make
these changes depends on your situation. On the plus side, removing the .php file ref‐
erences eliminates confusion by other developers accessing your code, who may wonder
why there are PHP references in a Node.js codebase. On the minus side, removing
the .php file references creates technically unnecessary differences between the PHP and
Node.js codebases.
Having walked through a step-by-step explanation with a single .php file, let’s put it all
together and do the same thing with multiple .php files:
var http = require('http');
var static = require('node-static');
var file = new static.Server();
var url = require('url');
var index = require('./index.njs');
var login = require('./login.njs');
An HTTP Server | 27
var admin_index = require('./admin/index.njs');
var admin_login = require('./admin/login.njs');
http.createServer(function (req, res) {
if (url.parse(req.url).pathname == '/index.php') {
index.serve(req, res);
} else if (url.parse(req.url).pathname == '/login.php') {
login.serve(req, res);
} else if (url.parse(req.url).pathname == '/admin/index.php') {
admin_index.serve(req, res);
} else if (url.parse(req.url).pathname == '/admin/login.php') {
admin_login.serve(req, res);
} else {
file.serve(req, res);
}
}).listen(1337, '127.0.0.1');
console.log('Server running at http://127.0.0.1:1337/');
There are essentially two modifications: (1) a set of require() function calls have been
added with one require() function call per .php file; and (2) a set of else-if statements
have been added with one else-if statement per .php file. In this case, this modified
version of httpsvr.njs is handling four .php files: /index.php, /login.php, /admin/
index.php, and /admin/login.php. For any web application that is converted from PHP
to Node.js, each .php file in the application will cause both a new require() function
call and an else-if statement to be added so that the httpsvr.njs file can properly route
an HTTP request for a .php file to a specific and separate .njs file that implements the
PHP code in Node.js instead.
As described earlier, a corresponding .njs file will be created for each .php file. For the
example with four .php files, there will be four .njs files: index.njs, login.njs, admin/
index.njs, and admin/login.njs. The files will have the same code except for a slight
modification to indicate the path name of the file:
exports.serve = function(req, res) {
res.writeHead(200, {'Content-Type': 'text/plain'});
res.end('admin/index.njs');
};
The exports.serve() function in each .njs file will ultimately contain the Node.js code
that exactly and completely emulates the operation of its corresponding .php file. The
stub implementation that just returns the name of the .njs file as an HTTP response will
be erased. The PHP code from the corresponding .php file will be copied and pasted
into the exports.serve() function, and through a series of transformations and con‐
version recipes, the PHP code will become Node.js code that does the exact same thing.
But we are not quite ready to do that yet.
28 | Chapter 2: A Simple Node.js Framework
Predefined PHP Variables
When a web server that supports PHP executes a PHP page, it does not supply a raw
HTTP request to the PHP page and then execute the page. If it did that, every PHP page
would have to add a lot of code to decode the raw HTTP request and put the values in
a more convenient format. Instead, the PHP engine decodes the raw HTTP request itself
and populates a bunch of well-known PHP global variables with the equivalent data.
PHP pages rely on these global variables to be correctly populated in order to work.
Since the basic approach is to copy the PHP page into the local module and transform
it to Node.js code, we will need to implement these global variables in Node.js in order
for the converted page to work. By analyzing the PHP page, we can determine which
global variables it relies upon. It is not necessary to implement every single global vari‐
able that the PHP engine makes available. Instead, we can just implement the ones that
are used.
Five PHP predefined global variables that are commonly used are $_GET, $_POST,
$_COOKIE, $_REQUEST, and $_SESSION.
An HTTP request is sent with an HTTP action, also called a method or a verb. An HTTP
GET action is very simple: the client is asking the server to retrieve a page. When a user
types a URL into the address bar of a browser, he is typing in an HTTP GET request.
The HTTP GET request may have some arguments in the form of name/value pairs.
These arguments are often called query arguments or the query string. A user can add
these arguments manually to a browser’s address bar by putting a question mark (?) at
the end of the URL and typing name/value pairs separated by ampersands (&). The
name/value pairs themselves separate the name from the value by using an equals sign
(=). Here’s an example:
http://localhost:1337/index.php?theme=green&tab=users&fastload=true
The name/value pairs in the example are: theme=green, tab=users, and fast
load=true. When a PHP page gets an HTTP GET request like this one, the PHP engine
extracts the name/value pairs from the raw HTTP GET request and puts them in the
predefined PHP $_GET array. The name becomes the key or index into the $_GET array,
and the value becomes, well, the value. For the previous example URL, the $_GET array
looks like this:
$_GET['theme'] = 'green';
$_GET['tab'] = 'users';
$_GET['fastload'] = 'true';
Predefined PHP Variables | 29
WhenaPHPpageisconvertedtoNode.jscode,theNode.jsstillexpectsthesepredefined
arrays to exist and to be correctly populated. The following code shows a Node.js init
GET() function, which can be used in any local module that contains a converted PHP
page to create and populate a Node.js _GET variable that works very similarly to the PHP
$_GET variable:
function initGET(req, pre, cb) {
pre._GET = {};
var urlparts = req.url.split('?');
if (urlparts.length >= 2) {
var query = urlparts[urlparts.length-1].split('&');
for (var p=0; p < query.length; ++p) {
var pair = query[p].split('=');
pre._GET[pair[0]] = pair[1];
}
}
cb();
}
The Node.js initGET() function takes three arguments: req, pre, and cb. The req
argument contains the raw HTTP request. The pre argument is a Node.js object that
contains all the predefined global variables, which are made available by the PHP engine
to PHP pages. Instead of juggling a bunch of different variables, all predefined variables
are stored in a pre variable that can be easily passed around. And finally, the cb contains
a callback function to be invoked when the initGET() function is finished. Since the
initGET() functiondoesonlysimplememorymanipulationandrequiresnooperations
that have callback functions, a callback function is not technically necessary. However,
since the initPOST() function that will be implemented later will need a cb() callback
function parameter, it is best that the initGET() and the initPOST() functions work as
similarly as possible.
ThefirstlineoftheNode.jsinitGET()functioncreatesa_GETarrayinthepreargument.
The pre._GET object will be the Node.js equivalent of the PHP $_GET array. Next, the
query arguments will be separated from the main URL, which is found in the req.url
property. Populating the Node.js pre._GET variable is as easy as using split() function
callstoseparateouttheURLqueryargumentsfromeachothertodeterminetheirname/
valuepairs.Finally,thecbargumentisinvokedtoletthecallbackknowthatthepre._GET
variable is ready for use.
To initialize the Node.js pre._GET variable, a modification will need to be made to the
Node.js exports.serve() function. Here’s the original exports.serve() function:
exports.serve = function(req, res) {
res.writeHead(200, {'Content-Type': 'text/plain'});
res.end('admin/index.njs');
};
30 | Chapter 2: A Simple Node.js Framework
Instead of implementing the actual page in the exports.serve() function, a new func‐
tion called page() will actually implement the page and the exports.serve() function
will be reserved for the initialization and other work that the PHP engine would provide
to a PHP page:
function page(req, res, pre, cb) {
res.writeHead(200, {'Content-Type': 'text/plain'});
res.end('admin/index.njs');
cb();
}
The page() function takes four arguments: req, res, pre, and cb. The req and res
arguments are the HTTP request and the HTTP response, respectively. The pre argu‐
ment is the predefined variables, including the _GET property that contains the query
arguments. The cb argument is a callback function that will let the exports.serve()
function know when the page has been completely handled.
For debugging purposes, it can be helpful to print out the pre object. By using the
require() function to load the built-in module called util and then adding a util
.inspect()functioncalltotheres.end()functioncall,thecontentsoftheprevariable,
including the contents of its _GET property, will be shown in the HTTP response:
var util = require('util');
function page(req, res, pre, cb) {
res.writeHead(200, {'Content-Type': 'text/plain'});
res.end('admin/index.njsn'+util.inspect(pre));
cb();
}
Now that handling the page has moved to the page() function, the exports.serve()
function will be changed to do an initialization, including calling the initGET()
function:
exports.serve = function(req, res) {
var pre = {};
initGET(req, pre, function() {
page(req, res, pre, function() {
});
});
};
The pre variable is created first. Then the initGET() function is called, and when it is
finished, the page() function is called. There is no finalization or cleanup after the
page() function, so its callback is empty.
Now that the _GET property is implemented, the page() function can be changed to use
query arguments. The page() function can be modified to expect an x query argument
and respond appropriately:
Predefined PHP Variables | 31
function page(req, res, pre, cb) {
res.writeHead(200, {'Content-Type': 'text/plain'});
if (pre._GET['x']) {
res.end('The value of x is '+pre._GET['x']+'.');
} else {
res.end('There is no value for x.');
}
cb();
}
If the Node.js server is run and a browser is directed to http://localhost:1337/index.php?
x=4, the browser page will show “The value of x is 4.”
The HTTP POST request is similar to the HTTP GET request, except that the name/
value pairs are delivered in the body of the request instead of as a query string at the
end of the URL. An HTTP request consists of HTTP headers and an HTTP body. The
URL, which includes the query string, is specified as one of the HTTP headers. HTTP
headers are meant to be short and limited in length. In particular, URLs, including the
query string, are limited in length; extremely long URLs are not recommended. Instead,
if a lot of data must be included in an HTTP request, it is recommended that the data
be sent in the HTTP body as part of an HTTP POST. Unlike HTTP headers, which are
limited in length, HTTP bodies can handle very large amounts of data. For an HTTP
POST, the HTTP body is often referred to as the POST data.
Since the body of an HTTP POST can be very large, the POST data is not delivered all
at once; it is delivered as it becomes available using events. Events are a way for Node.js
to indicate that something happened. For example, a data event indicates that the next
chunk of data has been read from the HTTP body. If there is a lot of data, Node.js may
trigger several data events as each chunk of data is read.
An event can be associated with a callback function, also called an event handler. The
event handler will execute some code each time that an event occurs.
The on() function associates an event with an event handler. The following example
shows the on function being used to associate the data event with an event handler that
writes the provided data to the console:
req.on('data', function(chunk) {
console.log(chunk);
});
For the initPOST() function, the _POST property of the pre variable is initialized. As
you will recall from the initGET() function, the pre argument is a Node.js object that
contains all the predefined global variables, which are made available by the PHP engine
to PHP pages. A body variable is created to hold the HTTP body as it is read. The on()
function associates an event handler with the data event, which will add the data to the
body variable as it becomes available:
32 | Chapter 2: A Simple Node.js Framework
pre._POST = {};
var body = '';
req.on('data', function(chunk) {
body += chunk;
if (body.length > 1e6) {
req.connection.destroy();
}
});
An if statement is added to the data event handler to detect if the HTTP body is too
long, and will terminate the connection if so. Poorly written or malicious clients might
send an endless amount of data, and this if statement protects the Node.js server by
arbitrarily giving up on an HTTP request if it has already sent a very large amount of
data.
Finally, the on() function associates an event handler with the end event, which occurs
when the entire HTTP body has been read. Using simple split() function calls, the
end event handler pulls apart the data and puts it into the pre._POST variable:
req.on('end', function() {
var pairs = body.split('&');
for (var p=0; p < pairs.length; ++p) {
var pair = pairs[p].split('=');
pre._POST[pair[0]] = pair[1];
}
cb();
});
The cb() callback is invoked at the very end to indicate that the pre._POST variable has
been fully populated and is ready for use.
Putting the code all together, the initPOST() function is shown here in its entirety:
function initPOST(req, pre, cb) {
pre._POST = {};
var body = '';
req.on('data', function(chunk) {
body += chunk;
if (body.length > 1e6) {
req.connection.destroy();
}
});
req.on('end', function() {
var pairs = body.split('&');
for (var p=0; p < pairs.length; ++p) {
var pair = pairs[p].split('=');
pre._POST[pair[0]] = pair[1];
}
cb();
});
}
Predefined PHP Variables | 33
Just like pages that expect HTTP GET requests, the exports.serve() function must be
updated. For pages that expect HTTP POST requests, the code in the exports
.serve()functionisalmostidentical,exceptthattheinitGET()functioncallisreplaced
withaninitPOST()functioncall.Thisisbydesign.EventhoughtheinitGET()function
does not need a callback, giving it a callback allows the initGET() function and the
initPOST() function to take the same parameters and has almost the same code:
exports.serve = function(req, res) {
var pre = {};
initPOST(req, pre, function() {
page(req, res, pre, function() {
});
});
};
By far, HTTP GET and HTTP POST are the most commonly used HTTP actions, and
in most cases, will be the only HTTP actions that a web application needs. There are
other HTTP actions, such as HTTP PUT, HTTP DELETE, and HTTP HEAD, but the
PHPenginedoesnotprovidethesamesupportforthem,soaNode.jsconversionusually
does not need to provide support for them either.
A cookie is a name/value pair that is sent to a client (usually a browser) by the server;
the client stores the cookie and adds it as an HTTP header to every subsequent HTTP
request that the client sends to the server. The client usually stores cookies in a perma‐
nent place, such as a hard disk, so that future HTTP requests can include the cookie,
even if the client is shutdown and restarted later. A cookie is a small piece of data that
the server gives the client so that it can identify the client later, and automatically log
the client into his account, for example.
An HTTP header for a cookie has the unsurprising name of “Cookie”:
Cookie: user=admin;sessid=21EC33203BEA1169A2EA08332B313090
In a Node.js HTTP request, the cookies are stored in the headers.cookie property of
the HTTP request, usually called req in the code examples in this book.
The initCOOKIE() function for extracting the cookies from the appropriate HTTP
header and putting it into the pre._COOKIE variable is very similar to the initGET()
function. Getting the cookies is even more convenient: the cookies are in their own
property, not tacked on to the end of a URL as a query string. The pre._COOKIE variable
will be a stand-in for the PHP $_COOKIE variable that is generated by the PHP engine
for PHP pages:
function initCOOKIE(req, pre, cb) {
pre._COOKIE = {};
if (req.headers.cookie) {
var cookies = req.headers.cookie.split(';');
for (var c=0; c < cookies.length; ++c) {
var pair = cookies[c].split('=');
34 | Chapter 2: A Simple Node.js Framework
Another Random Scribd Document
with Unrelated Content
was ruining the man. So when Rose looked up at him, with a very
honest desire to save him as well as herself from being swept into
the giddy vortex which keeps so many young people revolving
aimlessly, till they go down or are cast upon the shore wrecks of
what they might have been, he gave a shrug and answered briefly,—
"As you please. I'll bring you home as early as you like, and Effie
Waring can take your place in the German. What flowers shall I send
you?"
Now, that was an artful speech of Charlie's; for Miss Waring was a
fast and fashionable damsel, who openly admired Prince Charming,
and had given him the name. Rose disliked her, and was sure her
influence was bad; for youth made frivolity forgivable, wit hid want
of refinement, and beauty always covers a multitude of sins in a
man's eyes. At the sound of Effie's name, Rose wavered, and would
have yielded but for the memory of the "first mate's" last words. She
did desire to "keep a straight course;" so, though the current of
impulse set strongly in a southerly direction, principle, the only
compass worth having, pointed due north, and she tried to obey it
like a wise young navigator, saying steadily, while she directed to
Annabel the parcel containing a capacious pair of slippers intended
for Uncle Mac,—
"Don't trouble yourself about me. I can go with uncle, and slip away
without disturbing anybody."
"I don't believe you'll have the heart to do it," said Charlie,
incredulously, as he sealed the last note.
"Wait and see."
"I will, but shall hope to the last," and, kissing his hand to her, he
departed to post her letters, quite sure that Miss Waring would not
lead the German.
It certainly looked for a moment as if Miss Campbell would, because
she ran to the door with the words "I'll go" upon her lips. But she
did not open it till she had stood a minute staring hard at the old
glove on Psyche's head; then, like one who had suddenly got a
bright idea, she gave a decided nod and walked slowly out of the
room.
CHAPTER VI.
POLISHING MAC.
"Please could I say one word?" was the question three times
repeated before a rough head bobbed out from the grotto of books
in which Mac usually sat when he studied.
"Did any one speak?" he asked, blinking in the flood of sunshine that
entered with Rose.
"Only three times, thank you. Don't disturb yourself, I beg; for I
merely want to say a word," answered Rose, as she prevented him
from offering the easy-chair in which he sat.
"I was rather deep in a compound fracture, and didn't hear. What
can I do for you, cousin?" and Mac shoved a stack of pamphlets off
the chair near him, with a hospitable wave of the hand that sent his
papers flying in all directions.
Rose sat down, but did not seem to find her "word" an easy one to
utter; for she twisted her handkerchief about her fingers in
embarrassed silence, till Mac put on his glasses, and, after a keen
look, asked soberly,—
"Is it a splinter, a cut, or a whitlow, ma'am?"
"It is neither; do forget your tiresome surgery for a minute, and be
the kindest cousin that ever was," answered Rose, beginning rather
sharply and ending with her most engaging smile.
"Can't promise in the dark," said the wary youth.
"It is a favor, a great favor, and one I don't choose to ask any of the
other boys," answered the artful damsel.
Mac looked pleased, and leaned forward, saying more affably,—
"Name it, and be sure I'll grant it if I can."
"Go with me to Mrs. Hope's party to-morrow night."
"What!" and Mac recoiled as if she had put a pistol to his head.
"I've left you in peace a long time: but it is your turn now; so do
your duty like a man and a cousin."
"But I never go to parties!" cried the unhappy victim in great dismay.
"High time you began, sir."
"But I don't dance fit to be seen."
"I'll teach you."
"My dress-coat isn't decent, I know."
"Archie will lend you one: he isn't going."
"I'm afraid there's a lecture that I ought not to cut."
"No, there isn't: I asked uncle."
"I'm always so tired and dull in the evening."
"This sort of thing is just what you want to rest and freshen up your
spirits."
Mac gave a groan and fell back vanquished; for it was evident that
escape was impossible.
"What put such a perfectly wild idea into your head?" he demanded,
rather roughly; for hitherto he had been "left in peace," and this
sudden attack decidedly amazed him.
"Sheer necessity; but don't do it if it is so very dreadful to you. I
must go to several more parties, because they are made for me; but
after that I'll refuse, and then no one need be troubled with me."
Something in Rose's voice made Mac answer penitently, even while
he knit his brows in perplexity,—
"I didn't mean to be rude; and of course I'll go anywhere if I'm really
needed. But I don't understand where the sudden necessity is, with
three other fellows at command, all better dancers and beaux than I
am."
"I don't want them, and I do want you; for I haven't the heart to
drag uncle out any more, and you know I never go with any
gentleman but those of my own family."
"Now look here, Rose: if Steve has been doing any thing to tease
you just mention it, and I'll attend to him," cried Mac, plainly seeing
that something was amiss, and fancying that Dandy was at the
bottom of it, as he had done escort duty several times lately.
"No, Steve has been very good: but I know he had rather be with
Kitty Van; so of course I feel like a marplot, though he is too polite
to hint it."
"What a noodle that boy is! But there's Archie: he's as steady as a
church, and has no sweetheart to interfere," continued Mac, bound
to get at the truth, and half suspecting what it was.
"He is on his feet all day, and Aunt Jessie wants him in the evening.
He does not care for dancing as he used, and I suppose he really
does prefer to rest and read." Rose might have added, "and hear
Phebe sing;" for Phebe did not go out as much as Rose did, and
Aunt Jessie often came in to sit with the old lady when the young
folks were away; and, of course, dutiful Archie came with her; so
willingly of late!
"What's amiss with Charlie? I thought he was the prince of cavaliers.
Annabel says he dances 'like an angel,' and I know a dozen mothers
couldn't keep him at home of an evening. Have you had a tiff with
Adonis, and so fall back on poor me?" asked Mac, coming last to the
person of whom he thought first, but did not mention, feeling shy
about alluding to a subject often discussed behind her back.
"Yes, we have; and I don't intend to go with him any more for some
time. His ways do not suit me, and mine do not suit him; so I want
to be quite independent, and you can help me if you will," said Rose,
rather nervously spinning the big globe close by.
Mac gave a low whistle, looking wide awake all in a minute, as he
said with a gesture, as if he brushed a cobweb off his face,—
"Now, see here, cousin: I'm not good at mysteries, and shall only
blunder if you put me blindfold into any nice manœuvre. Just tell me
straight out what you want, and I'll do it if I can. Play I'm uncle, and
free your mind; come now."
He spoke so kindly, and the honest eyes were so full of merry good-
will, that Rose felt she might confide in him, and answered as frankly
as he could desire,—
"You are right, Mac; and I don't mind talking to you almost as freely
as to uncle, because you are such a reliable fellow, and won't think
me silly for trying to do what I believe to be right. Charlie does, and
so makes it hard for me to hold to my resolutions. I want to keep
early hours, dress simply, and behave properly; no matter what
fashionable people do. You will agree to that, I'm sure; and stand by
me through thick and thin for principle's sake."
"I will; and begin by showing you that I understand the case. I don't
wonder you are not pleased; for Charlie is too presuming, and you
do need some one to help you head him off a bit. Hey, cousin?"
"What a way to put it!" and Rose laughed in spite of herself, adding
with an air of relief, "That is it; and I do want some one to help me
make him understand that I don't choose to be taken possession of
in that lordly way, as if I belonged to him more than to the rest of
the family. I don't like it; for people begin to talk, and Charlie won't
see how disagreeable it is to me."
"Tell him so," was Mac's blunt advice.
"I have; but he only laughs and promises to behave, and then he
does it again, when I am so placed that I can't say any thing. You
will never understand, and I cannot explain; for it is only a look, or a
word, or some little thing: but I won't have it, and the best way to
cure him is to put it out of his power to annoy me so."
"He is a great flirt, and wants to teach you how, I suppose. I'll speak
to him if you like, and tell him you don't want to learn. Shall I?"
asked Mac, finding the case rather an interesting one.
"No, thank you: that would only make trouble. If you will kindly play
escort a few times, it will show Charlie that I am in earnest without
more words, and put a stop to the gossip," said Rose, coloring like a
poppy at the recollection of what she heard one young man whisper
to another, as Charlie led her through a crowded supper-room with
his most devoted air, "Lucky dog! he is sure to get the heiress, and
we are nowhere."
"There's no danger of people's gossiping about us, is there?" and
Mac looked up, with the oddest of all his odd expressions.
"Of course not: you're only a boy."
"I'm twenty-one, thank you; and Prince is but a couple of years
older," said Mac, promptly resenting the slight put upon his
manhood.
"Yes; but he is like other young men, while you are a dear old
bookworm. No one would ever mind what you did; so you may go to
parties with me every night, and not a word would be said; or, if
there was, I shouldn't mind since it is 'only Mac,'" answered Rose,
smiling as she quoted a household word often used to excuse his
vagaries.
"Then I am nobody?" lifting his brows, as if the discovery surprised
and rather nettled him.
"Nobody in society as yet; but my very best cousin in private, and
I've just proved my regard by making you my confidant, and
choosing you for my knight," said Rose, hastening to soothe the
feelings her careless words seemed to have ruffled slightly.
"Much good that is likely to do me," grumbled Mac.
"You ungrateful boy, not to appreciate the honor I've conferred upon
you! I know a dozen who would be proud of the place: but you only
care for compound fractures; so I won't detain you any longer,
except to ask if I may consider myself provided with an escort for to-
morrow night?" said Rose, a trifle hurt at his indifference; for she
was not used to refusals.
"If I may hope for the honor," and, rising, he made her a bow which
was such a capital imitation of Charlie's grand manner that she
forgave him at once, exclaiming with amused surprise,—
"Why, Mac! I didn't know you could be so elegant!"
"A fellow can be almost any thing he likes, if he tries hard enough,"
he answered, standing very straight, and looking so tall and dignified
that Rose was quite impressed, and with a stately courtesy she
retired, saying graciously,—
"I accept with thanks. Good-morning, Doctor Alexander Mackenzie
Campbell."
When Friday evening came, and word was sent up that her escort
had arrived, Rose ran down, devoutly hoping that he had not come
in a velveteen jacket, top-boots, black gloves, or made any trifling
mistake of that sort. A young gentleman was standing before the
long mirror, apparently intent on the arrangement of his hair; and
Rose paused suddenly as her eye went from the glossy broadcloth to
the white-gloved hands, busy with an unruly lock that would not
stay in place.
"Why, Charlie, I thought—" she began with an accent of surprise in
her voice, but got no further; for the gentleman turned and she
beheld Mac in immaculate evening costume, with his hair parted
sweetly on his brow, a superior posy at his button-hole, and the
expression of a martyr upon his face.
"Ah, don't you wish it was? No one but yourself to thank that it isn't
he. Am I right? Dandy got me up, and he ought to know what is
what," demanded Mac, folding his hands and standing as stiff as a
ramrod.
"You are so regularly splendid that I don't know you."
"Neither do I."
"I really had no idea you could look so like a gentleman," added
Rose, surveying him with great approval.
"Nor I that I could feel so like a fool."
"Poor boy! he does look rather miserable. What can I do to cheer
him up, in return for the sacrifice he is making?"
"Stop calling me a boy. It will soothe my agony immensely, and give
me courage to appear in a low-necked coat and a curl on my
forehead; for I'm not used to such elegancies, and find them no end
of a trial."
Mac spoke in such a pathetic tone, and gave such a gloomy glare at
the aforesaid curl, that Rose laughed in his face, and added to his
woe by handing him her cloak. He surveyed it gravely for a minute,
then carefully put it on wrong side out, and gave the swan's-down
hood a good pull over her head, to the utter destruction of all
smoothness to the curls inside.
Rose uttered a cry and cast off the cloak, bidding him learn to do it
properly, which he meekly did, and then led her down the hall
without walking on her skirts more than three times by the way. But
at the door she discovered that she had forgotten her furred
overshoes, and bade Mac get them.
"Never mind: it's not wet," he said, pulling his cap over his eyes and
plunging into his coat, regardless of the "elegancies" that afflicted
him.
"But I can't walk on cold stones with thin slippers, can I?" began
Rose, showing a little white foot.
"You needn't, for—there you are, my lady;" and, unceremoniously
picking her up, Mac landed her in the carriage before she could say
a word.
"What an escort!" she exclaimed in comic dismay, as she rescued
her delicate dress from the rug in which he was about to tuck her up
like a mummy.
"It's 'only Mac,' so don't mind," and he cast himself into an opposite
corner, with the air of a man who had nerved himself to the
accomplishment of many painful duties, and was bound to do them
or die.
"But gentlemen don't catch up ladies like bags of meal, and poke
them into carriages in this way. It is evident that you need looking
after, and it is high time I undertook your society manners. Now, do
mind what you are about, and don't get yourself or me into a scrape
if you can help it," besought Rose, feeling that on many accounts
she had gone farther and fared worse.
"I'll behave like a Turveydrop: see if I don't."
Mac's idea of the immortal Turveydrop's behavior seemed to be a
peculiar one; for, after dancing once with his cousin, he left her to
her own devices, and soon forgot all about her in a long
conversation with Professor Stumph, the learned geologist. Rose did
not care; for one dance proved to her that that branch of Mac's
education had been sadly neglected, and she was glad to glide
smoothly about with Steve, though he was only an inch or two taller
than herself. She had plenty of partners, however, and plenty of
chaperons; for all the young men were her most devoted, and all the
matrons beamed upon her with maternal benignity.
Charlie was not there; for when he found that Rose stood firm, and
had moreover engaged Mac as a permanency, he would not go at
all, and retired in high dudgeon to console himself with more
dangerous pastimes. Rose feared it would be so; and, even in the
midst of the gayety about her, an anxious mood came over her now
and then, and made her thoughtful for a moment. She felt her
power, and wanted to use it wisely; but did not know how to be kind
to Charlie without being untrue to herself and giving him false
hopes.
"I wish we were all children again, with no hearts to perplex us and
no great temptations to try us," she said to herself, as she rested a
moment in a quiet nook while her partner went to get a glass of
water. Right in the midst of this half-sad, half-sentimental reverie,
she heard a familiar voice behind her say earnestly,—
"And allophite is the new hydrous silicate of alumina and magnesia,
much resembling pseudophite, which Websky found in Silesia."
"What is Mac talking about!" she thought: and, peeping behind a
great azalea in full bloom, she saw her cousin in deep converse with
the professor, evidently having a capital time; for his face had lost its
melancholy expression and was all alive with interest, while the elder
man was listening as if his remarks were both intelligent and
agreeable.
"What is it?" asked Steve, coming up with the water, and seeing a
smile on Rose's face.
She pointed out the scientific tête-à-tête going on behind the azalea,
and Steve grinned as he peeped, then grew sober and said in a tone
of despair,—
"If you had seen the pains I took with that fellow, the patience with
which I brushed his wig, the time I spent trying to convince him that
he must wear thin boots, and the fight I had to get him into that
coat; you'd understand my feelings when I see him now."
"Why, what is the matter with him?" asked Rose.
"Will you take a look, and see what a spectacle he has made of
himself. He'd better be sent home at once, or he will disgrace the
family by looking as if he'd been in a row."
Steve spoke in such a tragic tone that Rose took another peep and
did sympathize with Dandy; for Mac's elegance was quite gone. His
tie was under one ear, his posy hung upside down, his gloves were
rolled into a ball, which he absently squeezed and pounded as he
talked, and his hair looked as if a whirlwind had passed over it; for
his ten fingers set it on end now and then, as they had a habit of
doing when he studied or talked earnestly. But he looked so happy
and wide awake, in spite of his dishevelment, that Rose gave an
approving nod, and said behind her fan,—
"It is a trying spectacle, Steve: yet, on the whole, I think his own
odd ways suit him best; and I fancy we shall yet be proud of him,
for he knows more than all the rest of us put together. Hear that
now," and Rose paused, that they might listen to the following burst
of eloquence from Mac's lips:—
"You know Frenzel has shown that the globular forms of silicate of
bismuth at Schneeburg and Johanngeorgenstadt are not isometric,
but monoclinic in crystalline form; and consequently he separates
them from the old eulytite, and gives them the new name
Agricolite."
"Isn't it awful? Let us get out of this before there's another
avalanche, or we shall be globular silicates and isometric crystals in
spite of ourselves," whispered Steve with a panic-stricken air; and
they fled from the hail-storm of hard words that rattled about their
ears, leaving Mac to enjoy himself in his own way.
But when Rose was ready to go home, and looked about for her
escort, he was nowhere to be seen; for the professor had departed,
and Mac with him, so absorbed in some new topic that he entirely
forgot his cousin, and went placidly home, still pondering on the
charms of geology. When this pleasing fact dawned upon Rose, her
feelings may be imagined. She was both angry and amused: it was
so like Mac to go mooning off and leave her to her fate. Not a hard
one, however; for, though Steve was gone with Kitty before her
flight was discovered, Mrs. Bliss was only too glad to take the
deserted damsel under her wing, and bear her safely home.
Rose was warming her feet, and sipping the chocolate which Phebe
always had ready for her, as she never ate suppers; when a hurried
tap came at the long window whence the light streamed, and Mac's
voice was heard softly asking to be let in "just for one minute."
Curious to know what had befallen him, Rose bade Phebe obey his
call; and the delinquent cavalier appeared, breathless, anxious, and
more dilapidated than ever: for he had forgotten his overcoat; his tie
was at the back of his neck now; and his hair as rampantly erect as
if all the winds of heaven had been blowing freely through it, as they
had; for he had been tearing to and fro the last half-hour trying to
undo the dreadful deed he had so innocently committed.
"Don't take any notice of me; for I don't deserve it: I only came to
see that you were safe, cousin, and then go hang myself, as Steve
advised," he began, in a remorseful tone, that would have been very
effective, if he had not been obliged to catch his breath with a
comical gasp now and then.
"I never thought you would be the one to desert me," said Rose,
with a reproachful look; thinking it best not to relent too soon,
though she was quite ready to do it when she saw how sincerely
distressed he was.
"It was that confounded man! He was a regular walking
encyclopædia; and, finding I could get a good deal out of him, I
went in for general information, as the time was short. You know I
always forget every thing else when I get hold of such a fellow."
"That is evident. I wonder how you came to remember me at all,"
answered Rose, on the brink of a laugh: it was so absurd.
"I didn't till Steve said something that reminded me: then it burst
upon me, in one awful shock, that I'd gone and left you; and you
might have knocked me down with a feather," said honest Mac,
hiding none of his iniquity.
"What did you do then?"
"Do! I went off like a shot, and never stopped till I reached the
Hopes"—
"You didn't walk all that way?" cried Rose.
"Bless you, no: I ran. But you were gone with Mrs. Bliss: so I pelted
back again to see with my own eyes that you were safe at home,"
answered Mac, wiping his hot forehead, with a sigh of relief.
"But it is three miles at least each way; and twelve o'clock, and dark
and cold. O Mac! how could you!" exclaimed Rose, suddenly realizing
what he had done, as she heard his labored breathing, saw the state
of the thin boots, and detected the absence of an overcoat.
"Couldn't do less, could I?" asked Mac, leaning up against the door
and trying not to pant.
"There was no need of half-killing yourself for such a trifle. You
might have known I could take care of myself for once, at least, with
so many friends about. Sit down this minute. Bring another cup,
please, Phebe: this boy isn't going home till he is rested and
refreshed after such a run as that," commanded Rose.
"Don't be good to me: I'd rather take a scolding than a chair, and
drink hemlock instead of chocolate if you happen to have any ready,"
answered Mac, with a pathetic puff, as he subsided on to the sofa,
and meekly took the draught Phebe brought him.
"If you had any thing the matter with your heart, sir, a race of this
sort might be the death of you: so never do it again," said Rose,
offering her fan to cool his heated countenance.
"Haven't got any heart."
"Yes, you have, for I hear it beating like a trip-hammer, and it is my
fault: I ought to have stopped as we went by, and told you I was all
right."
"It's the mortification, not the miles, that upsets me. I often take
that run for exercise, and think nothing of it; but to-night I was so
mad I made extra good time, I fancy. Now don't you worry, but
compose your mind, and 'sip your dish of tea,' as Evelina says,"
answered Mac, artfully turning the conversation from himself.
"What do you know about Evelina?" asked Rose, in great surprise.
"All about her. Do you suppose I never read a novel?"
"I thought you read nothing but Greek and Latin, with an occasional
glance at Websky's pseudophites and the monoclinics of
Johanngeorgenstadt."
Mac opened his eyes wide at this reply, then seemed to see the joke,
and joined in the laugh with such heartiness that Aunt Plenty's voice
was heard demanding from above, with sleepy anxiety,—
"Is the house afire?"
"No, ma'am, every thing is safe, and I'm only saying good-night,"
answered Mac, diving for his cap.
"Then go at once, and let that child have her sleep," added the old
lady, retiring to her bed.
Rose ran into the hall, and, catching up her uncle's fur coat, met
Mac as he came out of the study, absently looking about for his own.
"You haven't got any, you benighted boy! so take this, and have your
wits about you next time, or I won't let you off so easily," she said,
holding up the heavy garment, and peeping over it, with no sign of
displeasure in her laughing eyes.
"Next time! Then you do forgive me? You will try me again, and give
me a chance to prove that I'm not a fool?" cried Mac, embracing the
big coat with emotion.
"Of course I will; and, so far from thinking you a fool, I was much
impressed with your learning to-night, and told Steve that we ought
to be proud of our philosopher."
"Learning be hanged! I'll show you that I'm not a book-worm, but as
much a man as any of them; and then you may be proud or not, as
you like!" cried Mac, with a defiant nod, that caused the glasses to
leap wildly off his nose, as he caught up his hat and departed as he
came.
A day or two later, Rose went to call upon Aunt Jane, as she dutifully
did once or twice a week. On her way upstairs, she heard a singular
sound in the drawing-room, and involuntarily stopped to listen.
"One, two, three, slide! One, two, three, turn! Now then, come on!"
said one voice, impatiently.
"It's very easy to say 'come on;' but what the dickens do I do with
my left leg while I'm turning and sliding with my right?" demanded
another voice, in a breathless and mournful tone.
Then the whistling and thumping went on more vigorously than
before; and Rose, recognizing the voices, peeped through the half-
open door to behold a sight which made her shake with suppressed
laughter. Steve, with a red table-cloth tied round his waist,
languished upon Mac's shoulder, dancing in perfect time to the air he
whistled; for Dandy was a proficient in the graceful art, and plumed
himself upon his skill. Mac, with a flushed face and dizzy eye,
clutched his brother by the small of his back, vainly endeavoring to
steer him down the long room without entangling his own legs in the
table-cloth, treading on his partner's toes, or colliding with the
furniture. It was very droll; and Rose enjoyed the spectacle, till Mac,
in a frantic attempt to swing round, dashed himself against the wall,
and landed Steve upon the floor. Then it was impossible to restrain
her laughter any longer; and she walked in upon them, saying
merrily,—
"It was splendid! Do it again, and I'll play for you."
Steve sprung up, and tore off the table-cloth in great confusion;
while Mac, still rubbing his head, dropped into a chair, trying to look
quite calm and cheerful as he gasped out,—
"How are you, cousin? When did you come? John should have told
us."
"I'm glad he didn't, for then I should have missed this touching
tableau of cousinly devotion and brotherly love. Getting ready for our
next party, I see."
"Trying to; but there are so many things to remember all at once,—
keep time, steer straight, dodge the petticoats, and manage my
confounded legs,—that it isn't easy to get on at first," answered
Mac, wiping his hot forehead, with a sigh of exhaustion.
"Hardest job I ever undertook; and, as I'm not a battering-ram, I
decline to be knocked round any longer," growled Steve, dusting his
knees, and ruefully surveying the feet that had been trampled on till
they tingled; for his boots and broadcloth were dear to the heart of
the dapper youth.
"Very good of you, and I'm much obliged. I've got the pace, I think,
and can practise with a chair to keep my hand in," said Mac, with
such a comic mixture of gratitude and resignation that Rose went off
again so irresistibly that her cousins joined her with a hearty roar.
"As you are making a martyr of yourself in my service, the least I
can do is to lend a hand. Play for us, Steve, and I'll give Mac a
lesson, unless he prefers the chair." And, throwing off hat and cloak,
Rose beckoned so invitingly that the gravest philosopher would have
yielded.
"A thousand thanks, but I'm afraid I shall hurt you," began Mac,
much gratified, but mindful of past mishaps.
"I'm not. Steve didn't manage his train well, for good dancers always
loop theirs up. I have none at all: so that trouble is gone; and the
music will make it much easier to keep step. Just do as I tell you,
and you'll go beautifully after a few turns."
"I will, I will! Pipe up, Steve! Now, Rose!" And, brushing his hair out
of his eyes with an air of stern determination, Mac grasped Rose,
and returned to the charge, bent on distinguishing himself if he died
in the attempt.
The second lesson prospered: for Steve marked the time by a series
of emphatic bangs; Mac obeyed orders as promptly as if his life
depended on it; and, after several narrow escapes at exciting
moments, Rose had the satisfaction of being steered safely down the
room, and landed with a grand pirouette at the bottom. Steve
applauded, and Mac, much elated, exclaimed with artless candor,—
"There really is a sort of inspiration about you, Rose. I always
detested dancing before; but now, do you know, I rather like it."
"I knew you would; only you mustn't stand with your arm round
your partner in this way when you are done. You must seat and fan
her, if she likes it," said Rose, anxious to perfect a pupil who seemed
so lamentably in need of a teacher.
"Yes, of course, I know how they do it;" and, releasing his cousin,
Mac raised a small whirlwind round her with a folded newspaper, so
full of grateful zeal that she had not the heart to chide him again.
"Well done, old fellow. I begin to have hopes of you, and will order
you a new dress-coat at once, since you are really going in for the
proprieties of life," said Steve from the music-stool, with the
approving nod of one who was a judge of said proprieties. "Now,
Rose, if you will just coach him a little in his small-talk, he won't
make a laughing-stock of himself as he did the other night," added
Steve. "I don't mean his geological gabble: that was bad enough,
but his chat with Emma Curtis was much worse. Tell her, Mac, and
see if she doesn't think poor Emma had a right to think you a first-
class bore."
"I don't see why, when I merely tried to have a little sensible
conversation," began Mac, with reluctance; for he had been
unmercifully chaffed by his cousins, to whom his brother had
betrayed him.
"What did you say? I won't laugh if I can help it," said Rose, curious
to hear; for Steve's eyes were twinkling with fun.
"Well, I knew she was fond of theatres; so I tried that first, and got
on pretty well till I began to tell her how they managed those things
in Greece. Most interesting subject, you know?"
"Very. Did you give her one of the choruses or a bit of Agamemnon,
as you did when you described it to me?" asked Rose, keeping sober
with difficulty as she recalled that serio-comic scene.
"Of course not; but I was advising her to read Prometheus, when
she gaped behind her fan, and began to talk about Phebe. What a
'nice creature' she was, 'kept her place,' 'dressed according to her
station,' and that sort of twaddle. I suppose it was rather rude, but
being pulled up so short confused me a bit, and I said the first thing
that came into my head, which was that I thought Phebe the best-
dressed woman in the room, because she wasn't all fuss and
feathers like most of the girls."
"O Mac! that to Emma, who makes it the labor of her life to be
always in the height of the fashion, and was particularly splendid
that night. What did she say?" cried Rose, full of sympathy for both
parties.
"She bridled and looked daggers at me."
"And what did you do?"
"I bit my tongue, and tumbled out of one scrape into another.
Following her example, I changed the subject by talking about the
Charity Concert for the orphans; and, when she gushed about the
'little darlings,' I advised her to adopt one, and wondered why young
ladies didn't do that sort of thing, instead of cuddling cats and
lapdogs."
"Unhappy boy! her pug is the idol of her life, and she hates babies,"
said Rose.
"More fool she! Well, she got my opinion on the subject, anyway,
and she's very welcome; for I went on to say that I thought it would
not only be a lovely charity, but excellent training for the time when
they had little darlings of their own. No end of poor things die
through the ignorance of mothers, you know," added Mac, so
seriously that Rose dared not smile at what went before.
"Imagine Emma trotting round with a pauper baby under her arm
instead of her cherished Toto," said Steve, with an ecstatic twirl on
the stool.
"Did she seem to like your advice, Monsieur Malapropos?" asked
Rose, wishing she had been there.
"No, she gave a little shriek, and said, 'Good gracious, Mr. Campbell,
how droll you are! Take me to mamma, please,' which I did with a
thankful heart. Catch me setting her pug's leg again," ended Mac,
with a grim shake of the head.
"Never mind. You were unfortunate in your listener that time. Don't
think all girls are so foolish. I can show you a dozen sensible ones,
who would discuss dress reform and charity with you, and enjoy
Greek tragedy if you did the chorus for them as you did for me," said
Rose, consolingly; for Steve would only jeer.
"Give me a list of them, please; and I'll cultivate their acquaintance.
A fellow must have some reward for making a teetotum of himself."
"I will with pleasure; and if you dance well they will make it very
pleasant for you, and you'll enjoy parties in spite of yourself."
"I cannot be a 'glass of fashion and a mould of form' like Dandy
here, but I'll do my best: only, if I had my choice, I'd much rather go
round the streets with an organ and a monkey," answered Mac,
despondently.
"Thank you kindly for the compliment," and Rose made him a low
courtesy, while Steve cried,—
"Now you have done it!" in a tone of reproach which reminded the
culprit, all too late, that he was Rose's chosen escort.
"By the gods, so I have!" and, casting away the newspaper with a
gesture of comic despair, Mac strode from the room, chanting
tragically the words of Cassandra,—
"'Woe! woe! O Earth! O Apollo! I will dare to die; I will accost the
gates of Hades, and make my prayer that I may receive a mortal
blow!'"
CHAPTER VII.
PHEBE.
While Rose was making discoveries and having experiences, Phebe
was doing the same in a quieter way: but, though they usually
compared notes during the bedtime tête-à-tête which always ended
their day, certain topics were never mentioned; so each had a little
world of her own into which even the eye of friendship did not peep.
Rose's life just now was the gayest, but Phebe's the happiest. Both
went out a good deal; for the beautiful voice was welcomed
everywhere, and many were ready to patronize the singer who
would have been slow to recognize the woman. Phebe knew this,
and made no attempt to assert herself; content to know that those
whose regard she valued felt her worth, and hopeful of a time when
she could gracefully take the place she was meant to fill.
Proud as a princess was Phebe about some things, though in most
as humble as a child; therefore, when each year lessened the service
she loved to give, and increased the obligations she would have
refused from any other source, dependence became a burden which
even the most fervent gratitude could not lighten. Hitherto the
children had gone on together, finding no obstacles to their
companionship in the secluded world in which they lived: now that
they were women their paths inevitably diverged, and both
reluctantly felt that they must part before long.
It had been settled, when they went abroad, that on their return
Phebe should take her one gift in her hand, and try her fortunes. On
no other terms would she accept the teaching which was to fit her
for the independence she desired. Faithfully had she used the
facilities so generously afforded both at home and abroad, and now
was ready to prove that they had not been in vain. Much encouraged
by the small successes she won in drawing-rooms, and the praise
bestowed by interested friends, she began to feel that she might
venture on a larger field, and begin her career as a concert singer;
for she aimed no higher.
Just at this time, much interest was felt in a new asylum for orphan
girls, which could not be completed for want of funds. The
Campbells "well had borne their part," and still labored to accomplish
the much-needed charity. Several fairs had been given for this
purpose, followed by a series of concerts. Rose had thrown herself
into the work with all her heart, and now proposed that Phebe
should make her début at the last concert which was to be a
peculiarly interesting one, as all the orphans were to be present, and
were expected to plead their own cause by the sight of their
innocent helplessness, as well as touch hearts by the simple airs
they were to sing.
Some of the family thought Phebe would object to so humble a
beginning: but Rose knew her better, and was not disappointed; for,
when she made her proposal, Phebe answered readily,—
"Where could I find a fitter time and place to come before the public
than here among my little sisters in misfortune? I'll sing for them
with all my heart: only I must be one of them, and have no flourish
made about me."
"You shall arrange it as you like; and, as there is to be little vocal
music but yours and the children's, I'll see that you have every thing
as you please," promised Rose.
It was well she did; for the family got much excited over the
prospect of "our Phebe's début," and would have made a flourish if
the girls had not resisted. Aunt Clara was in despair about the dress;
because Phebe decided to wear a plain claret-colored merino with
frills at neck and wrists, so that she might look as much as possible,
like the other orphans in their stuff gowns and white aprons. Aunt
Plenty wanted to have a little supper afterward in honor of the
occasion; but Phebe begged her to change it to a Christmas dinner
for the poor children. The boys planned to throw bushels of flowers,
and Charlie claimed the honor of leading the singer in. But Phebe,
with tears in her eyes, declined their kindly offers, saying earnestly,
—
"I had better begin as I am to go on, and depend upon myself
entirely. Indeed, Mr. Charlie, I'd rather walk in alone; for you'd be
out of place among us, and spoil the pathetic effect we wish to
produce," and a smile sparkled through the tears, as Phebe looked
at the piece of elegance before her, and thought of the brown gowns
and pinafores.
So, after much discussion, it was decided that she should have her
way in all things, and the family content themselves with applauding
from the front.
"We'll blister our hands every man of us, and carry you home in a
chariot and four: see if we don't, you perverse prima donna!"
threatened Steve, not at all satisfied with the simplicity of the affair.
"A chariot and two will be very acceptable as soon as I'm done. I
shall be quite steady till my part is all over, and then I may feel a
little upset; so I'd like to get away before the confusion begins.
Indeed I don't mean to be perverse: but you are all so kind to me,
my heart is full whenever I think of it; and that wouldn't do if I'm to
sing," said Phebe, dropping one of the tears on the little frill she was
making.
No diamond could have adorned it better Archie thought, as he
watched it shine there for a moment; and felt like shaking Steve for
daring to pat the dark head with an encouraging,—
"All right. I'll be on hand, and whisk you away while the rest are
splitting their gloves. No fear of your breaking down. If you feel the
least bit like it, though, just look at me; and I'll glare at you and
shake my fist, since kindness upsets you."
"I wish you would, because one of my ballads is rather touching,
and I always want to cry when I sing it. The sight of you trying to
glare will make me want to laugh, and that will steady me nicely: so
sit in front, please, ready to slip out when I come off the last time."
"Depend upon me!" And the little man departed, taking great credit
to himself for his influence over tall, handsome Phebe.
If he had known what was going on in the mind of the silent young
gentleman behind the newspaper, Steve would have been much
astonished; for Archie, though apparently engrossed by business,
was fathoms deep in love by this time. No one suspected this but
Rose; for he did his wooing with his eyes, and only Phebe knew how
eloquent they could be. He had discovered what the matter was long
ago,—had made many attempts to reason himself out of it; but,
finding it a hopeless task, had given up trying, and let himself drift
deliciously. The knowledge that the family would not approve only
seemed to add ardor to his love and strength to his purpose: for the
same energy and persistence which he brought to business went
into every thing he did; and, having once made up his mind to marry
Phebe, nothing could change his plan except a word from her.
He watched and waited for three months, so that he might not be
accused of precipitation, though it did not take him one to decide
that this was the woman to make him happy. Her steadfast nature;
quiet, busy ways; and the reserved power and passion betrayed
sometimes by a flash of the black eyes, a quiver of the firm lips,—
suited Archie, who possessed many of the same attributes himself:
while the obscurity of her birth and isolation of her lot, which would
have deterred some lovers, not only appealed to his kindly heart, but
touched the hidden romance which ran like a vein of gold through
his strong common-sense, and made practical, steady-going Archie a
poet when he fell in love. If Uncle Mac had guessed what dreams
and fancies went on in the head bent over his ledgers, and what
emotions were fermenting in the bosom of his staid "right-hand
man," he would have tapped his forehead, and suggested a lunatic
asylum. The boys thought Archie had sobered down too soon. His
mother began to fear that the air of the counting-room did not suit
him: and Dr. Alec was deluded into the belief that the fellow really
began to "think of Rose;" he came so often in the evening, seeming
quite contented to sit beside her work-table, and snip tape, or draw
patterns, while they chatted.
No one observed that, though he talked to Rose on these occasions,
he looked at Phebe, in her low chair close by, busy but silent; for she
always tried to efface herself when Rose was near, and often
mourned that she was too big to keep out of sight. No matter what
he talked about, Archie always saw the glossy black braids on the
other side of the table, the damask cheek curving down into the firm
white throat, and the dark lashes, lifted now and then, showing eyes
so deep and soft he dared not look into them long. Even the swift
needle charmed him, the little brooch which rose and fell with her
quiet breath, the plain work she did, and the tidy way she gathered
her bits of thread into a tiny bag. He seldom spoke to her; never
touched her basket, though he ravaged Rose's if he wanted string or
scissors; very rarely ventured to bring her some curious or pretty
thing when ships came in from China: only sat and thought of her;
imagined that this was his parlor, this her work-table, and they two
sitting there alone a happy man and wife.
At this stage of the little evening drama, he would be conscious of
such a strong desire to do something rash that he took refuge in a
new form of intoxication, and proposed music, sometimes so
abruptly that Rose would pause in the middle of a sentence and look
at him, surprised to meet a curiously excited look in the usually cool,
gray eyes.
Then Phebe, folding up her work, would go to the piano, as if glad
to find a vent for the inner life which she seemed to have no power
of expressing except in song. Rose would follow to accompany her;
and Archie, moving to a certain shady corner whence he could see
Phebe's face as she sang, would give himself up to unmitigated
rapture for half an hour. Phebe never sang so well as at such times:
for the kindly atmosphere was like sunshine to a bird, criticisms were
few and gentle, praises hearty and abundant; and she poured out
her soul as freely as a spring gushes up when its hidden source is
full.
Always comely, with a large and wholesome growth, in moments
such as these Phebe was beautiful with the beauty that makes a
man's eye brighten with honest admiration, and thrills his heart with
a sense of womanly nobility and sweetness. Little wonder, then, that
the chief spectator of this agreeable tableau grew nightly more
enamoured; and, while the elders were deep in whist, the young
people were playing that still more absorbing game in which hearts
are always trumps.
Rose, having Dummy for a partner, soon discovered the fact, and
lately had begun to feel as she fancied Wall must have done when
Pyramus wooed Thisbe through its chinks. She was a little startled at
first, then amused, then anxious, then heartily interested, as every
woman is in such affairs, and willingly continued to be a medium,
though sometimes she quite tingled with the electricity which
seemed to pervade the air. She said nothing, waiting for Phebe to
speak; but Phebe was silent, seeming to doubt the truth, till doubt
became impossible, then to shrink as if suddenly conscious of
wrong-doing, and seize every possible pretext for absenting herself
from the "girls' corner," as the pretty recess was called.
The concert plan afforded excellent opportunities for doing this; and
evening after evening she slipped away to practise her songs
upstairs, while Archie sat staring disconsolately at the neglected
work-basket and mute piano. Rose pitied him, and longed to say a
word of comfort, but felt shy,—he was such a reserved fellow,—so
left him to conduct his quiet wooing in his own way, feeling that the
crisis would soon arrive.
She was sure of this, as she sat beside him on the evening of the
concert; for while the rest of the family nodded and smiled, chatted
and laughed in great spirits, Archie was as mute as a fish, and sat
with his arms tightly folded, as if to keep in any unruly emotions
which might attempt to escape. He never looked at the programme;
but Rose knew when Phebe's turn came by the quick breath he
drew, and the intent look that came into his eyes so absent before.
But her own excitement prevented much notice of his; for Rose was
in a flutter of hope and fear, sympathy and delight, about Phebe and
her success. The house was crowded; the audience sufficiently
mixed to make the general opinion impartial; and the stage full of
little orphans with shining faces, a most effective reminder of the
object in view.
"Little dears, how nice they look!" "Poor things, so young to be
fatherless and motherless." "It will be a disgrace to the city, if those
girls are not taken proper care of." "Subscriptions are always in
order, you know; and pretty Miss Campbell will give you her
sweetest smile if you hand her a handsome check." "I've heard this
Phebe Moore, and she really has a delicious voice: such a pity she
won't fit herself for opera!" "Only sings three times to-night; that's
modest I'm sure, when she is the chief attraction; so we must give
her an encore after the Italian piece." "The orphans lead off, I see:
stop your ears if you like; but don't fail to applaud, or the ladies will
never forgive you."
Chat of this sort went on briskly, while fans waved, programmes
rustled, and ushers flew about distractedly; till an important
gentleman appeared, made his bow, skipped upon the leader's
stand, and with a wave of his bâton caused a general uprising of
white pinafores, as the orphans led off with that much-enduring
melody, "America," in shrill small voices, but with creditable attention
to time and tune. Pity and patriotism produced a generous round of
applause; and the little girls sat down, beaming with innocent
satisfaction.
An instrumental piece followed, and then a youthful gentleman, with
his hair in picturesque confusion, and what his friends called a
"musical brow," bounded up the steps, and, clutching a roll of music
with a pair of tightly gloved hands, proceeded to inform the
audience, in a husky tenor voice, that
"It was a lovely violet."
What else the song contained in the way of sense or sentiment it
was impossible to discover; as the three pages of music appeared to
consist of variations upon that one line, ending with a prolonged
quaver, which flushed the musical brow, and left the youth quite
breathless when he made his bow.
"Now she's coming! O uncle, my heart beats as if it was myself!"
whispered Rose, clutching Dr. Alec's arm with a little gasp, as the
piano was rolled forward, the leader's stand pushed back, and all
eyes turned toward the anteroom door.
She forgot to glance at Archie, and it was as well perhaps; for his
heart was thumping almost audibly, as he waited for his Phebe. Not
from the anteroom, but out from among the children, where she had
sat unseen in the shadow of the organ, came stately Phebe in her
wine-colored dress, with no ornament but her fine hair and a white
flower at her throat. Very pale, but quite composed, apparently; for
she stepped slowly through the narrow lane of upturned faces,
holding back her skirts, lest they should rudely brush against some
little head. Straight to the front she went, bowed hastily, and, with a
gesture to the accompanist, stood waiting to begin, her eyes fixed
on the great gilt clock at the opposite end of the hall.
They never wandered from that point while she sung; but, as she
ended, they dropped for an instant on an eager, girlish countenance,
bending from a front seat; then, with her hasty little bow, she went
quickly back among the children, who clapped and nodded as she
passed, well pleased with the ballad she had sung.
Every one courteously followed their example; but there was no
enthusiasm, and it was evident that Phebe had not produced a
particularly favorable impression.
"Never sang so badly in her life," muttered Charlie, irefully.
"She was frightened, poor thing. Give her time, give her time," said
Uncle Mac, kindly.
"I saw she was, and I glared like a gorgon, but she never looked at
me," added Steve, smoothing his gloves and his brows at the same
time.
"That first song was the hardest, and she got through much better
than I expected," put in Dr. Alec, bound not to show the
disappointment he felt.
"Don't be troubled. Phebe has courage enough for any thing, and
she'll astonish you before the evening's over," prophesied Mac, with
unabated confidence; for he knew something that the rest did not.
Rose said nothing, but, under cover of her burnous, gave Archie's
hand a sympathetic squeeze; for his arms were unfolded now, as if
the strain was over, and one lay on his knee, while with the other he
wiped his hot forehead with an air of relief.
Friends about them murmured complimentary fibs, and affected
great delight and surprise at Miss Moore's "charming style,"
"exquisite simplicity," and "undoubted talent." But strangers freely
criticised, and Rose was so indignant at some of their remarks she
could not listen to any thing upon the stage, though a fine overture
was played, a man with a remarkable bass voice growled and roared
melodiously, and the orphans sang a lively air with a chorus of "Tra,
la, la," which was a great relief to little tongues unused to long
silence.
"I've often heard that women's tongues were hung in the middle and
went at both ends: now I'm sure of it," whispered Charlie, trying to
cheer her up by pointing out the comical effect of some seventy-five
open mouths, in each of which the unruly member was wagging
briskly.
Rose laughed and let him fan her, leaning from his seat behind with
the devoted air he always assumed in public; but her wounded
feelings were not soothed, and she continued to frown at the stout
man on the left, who had dared to say with a shrug and a glance at
Phebe's next piece, "That young woman can no more sing this
Italian thing than she can fly, and they ought not to let her attempt
it."
Phebe did, however; and suddenly changed the stout man's opinion
by singing it grandly; for the consciousness of her first failure pricked
her pride and spurred her to do her best with the calm sort of
determination which conquers fear, fires ambition, and changes
defeat to success. She looked steadily at Rose now, or the flushed,
intent face beside her; and throwing all her soul into the task let her
voice ring out like a silver clarion, filling the great hall and setting
the hearers' blood a-tingle with the exulting strain.
That settled Phebe's fate as cantatrice; for the applause was genuine
and spontaneous this time, and broke out again and again with the
generous desire to atone for former coldness. But she would not
return, and the shadow of the great organ seemed to have
swallowed her up; for no eye could find her, no pleasant clamor win
her back.
"Now I can die content," said Rose, beaming with heart-felt
satisfaction; while Archie looked steadfastly at his programme, trying
to keep his face in order, and the rest of the family assumed a
triumphant air, as if they had never doubted from the first.
"Very well, indeed," said the stout man, with an approving nod.
"Quite promising for a beginner. Shouldn't wonder if in time they
made a second Cary or Kellogg of her."
"Now you'll forgive him, won't you?" murmured Charlie, in his
cousin's ear.
"Yes; and I'd like to pat him on the head. But take warning and
never judge by first appearances again," whispered Rose, at peace
now with all mankind.
Phebe's last song was another ballad; for she meant to devote her
talent to that much neglected but always attractive branch of her
art. It was a great surprise, therefore, to all but one person in the
hall, when, instead of singing "Auld Robin Grey," she placed herself
at the piano, and, with a smiling glance over her shoulder at the
children, broke out in the old bird-song which first won Rose. But the
chirping, twittering, and cooing were now the burden to three verses
of a charming little song, full of spring-time and the awakening life
that makes it lovely. A rippling accompaniment flowed through it all,
and a burst of delighted laughter from the children filled up the first
pause with a fitting answer to the voices that seemed calling to
them from the vernal woods.
It was very beautiful, and novelty lent its charm to the surprise; for
art and nature worked a pretty miracle, and the clever imitation, first
heard from a kitchen hearth, now became the favorite in a crowded
concert room. Phebe was quite herself again; color in the cheeks
now; eyes that wandered smiling to and fro; and lips that sang as
gaily and far more sweetly than when she kept time to her blithe
music with a scrubbing brush.
This song was evidently intended for the children, and they
appreciated the kindly thought; for, as Phebe went back among
them, they clapped ecstatically, flapped their pinafores, and some
caught her by the skirts with audible requests to "do it again,
please; do it again."
But Phebe shook her head and vanished; for it was getting late for
such small people, several of whom "lay sweetly slumbering there,"
till roused by the clamor round them. The elders, however, were not
to be denied, and applauded persistently, especially Aunt Plenty, who
seized Uncle Mac's cane and pounded with it as vigorously as "Mrs.
Nubbles" at the play.
"Never mind your gloves, Steve; keep it up till she comes," cried
Charlie, enjoying the fun like a boy; while Jamie lost his head with
excitement, and standing up called "Phebe! Phebe!" in spite of his
mother's attempts to silence him.
Even the stout man clapped, and Rose could only laugh delightedly
as she turned to look at Archie, who seemed to have let himself
loose at last, and was stamping with a dogged energy funny to see.
So Phebe had to come, and stood there meekly bowing, with a
moved look on her face, that showed how glad and grateful she
was, till a sudden hush came; then, as if inspired by the memory of
the cause that brought her there, she looked down into the sea of
friendly faces before her, with no trace of fear in her own, and sung
the song that never will grow old.
That went straight to the hearts of those who heard her: for there
was something inexpressibly touching in the sight of this sweet-
voiced woman singing of home for the little creatures who were
homeless; and Phebe made her tuneful plea irresistible by an almost
involuntary gesture of the hands which had hung loosely clasped
before her; till, with the last echo of the beloved word, they fell
apart and were half-out-stretched as if pleading to be filled.
It was the touch of nature that works wonders; for it made full
purses suddenly weigh heavily in pockets slow to open, brought
tears to eyes unused to weep, and caused that group of red-gowned
girls to grow very pathetic in the sight of fathers and mothers who
had left little daughters safe asleep at home. This was evident from
the stillness that remained unbroken for an instant after Phebe
ended; and before people could get rid of their handkerchiefs she
would have been gone, if the sudden appearance of a mite in a
pinafore, climbing up the stairs from the anteroom, with a great
bouquet grasped in both hands, had not arrested her.
Up came the little creature, intent on performing the mission for
which rich bribes of sugar-plums had been promised, and trotting
Welcome to our website – the perfect destination for book lovers and
knowledge seekers. We believe that every book holds a new world,
offering opportunities for learning, discovery, and personal growth.
That’s why we are dedicated to bringing you a diverse collection of
books, ranging from classic literature and specialized publications to
self-development guides and children's books.
More than just a book-buying platform, we strive to be a bridge
connecting you with timeless cultural and intellectual values. With an
elegant, user-friendly interface and a smart search system, you can
quickly find the books that best suit your interests. Additionally,
our special promotions and home delivery services help you save time
and fully enjoy the joy of reading.
Join us on a journey of knowledge exploration, passion nurturing, and
personal growth every day!
ebookbell.com
Ad

Recommended

Ruby Best Practices Increase Your Productivity Write Better Code 1st Edition ...
Ruby Best Practices Increase Your Productivity Write Better Code 1st Edition ...
forbahchonev
 
Node Up And Running Scalable Serverside Code With Javascript 1st Edition Tom ...
Node Up And Running Scalable Serverside Code With Javascript 1st Edition Tom ...
nunleyagika
 
Programming Interactivity 2nd Edition 2nd Edition Joshua Noble
Programming Interactivity 2nd Edition 2nd Edition Joshua Noble
maanggeido20
 
Programming Embedded Systems With C And Gnu Development Tools 2nd Edition 2nd...
Programming Embedded Systems With C And Gnu Development Tools 2nd Edition 2nd...
cunneyaidane
 
High Performance JavaScript Build Faster Web Application Interfaces 1st Editi...
High Performance JavaScript Build Faster Web Application Interfaces 1st Editi...
yarecofuxxa58
 
A Functional Approach to Java: Augmenting Object-Oriented Java Code with Func...
A Functional Approach to Java: Augmenting Object-Oriented Java Code with Func...
romergalbowx
 
Php How To
Php How To
Adil Jafri
 
Angularjs
Angularjs
Mustafa Juma
 
Angularjs
Angularjs
Heinrrich Facho
 
Angular js book
Angular js book
Kamlesh Singh
 
Linux System Programming 1st Edition Robert Love
Linux System Programming 1st Edition Robert Love
bouliflantei
 
Php5
Php5
daveparky
 
RESTful Java With JAX RS 1st Edition Bill Burke
RESTful Java With JAX RS 1st Edition Bill Burke
rohismhmob88
 
Client Server Web Apps with JavaScript and Java 1st Edition Casimir Saternos
Client Server Web Apps with JavaScript and Java 1st Edition Casimir Saternos
tomeooakesrq
 
Learning Web App Development 1st Edition Semmy Purewal
Learning Web App Development 1st Edition Semmy Purewal
molaxmeizu
 
Introducing Erlang Getting Started In Functional Programming 2nd Edition St L...
Introducing Erlang Getting Started In Functional Programming 2nd Edition St L...
izalhideto
 
Designing Evolvable Web Apis With Aspnet 1st Edition Glenn Block
Designing Evolvable Web Apis With Aspnet 1st Edition Glenn Block
zisanashham6
 
Dat 210 academic adviser ....tutorialrank.com
Dat 210 academic adviser ....tutorialrank.com
ladworkspaces
 
DAT 210 Education Specialist |tutorialrank.com
DAT 210 Education Specialist |tutorialrank.com
ladworkspaces
 
[Ebooks PDF] download AngularJS 1st Edition Brad Green full chapters
[Ebooks PDF] download AngularJS 1st Edition Brad Green full chapters
kiciunonge
 
Instant Download Hadoop Operations 1st Edition Eric Sammer PDF All Chapters
Instant Download Hadoop Operations 1st Edition Eric Sammer PDF All Chapters
istvanysmoni
 
Consumer centric api design v0.4.0
Consumer centric api design v0.4.0
mustafa sarac
 
Designing Web Apis Building Apis That Developers Love Jin Brendasahni
Designing Web Apis Building Apis That Developers Love Jin Brendasahni
reknesluima
 
Instant download Developing Backbone js Applications Addy Osmani pdf all chapter
Instant download Developing Backbone js Applications Addy Osmani pdf all chapter
dinetvenitja
 
Sap to php
Sap to php
kerbarous
 
Web_Development_with_Node_Express.pdf
Web_Development_with_Node_Express.pdf
Marco Antonio Martinez Andrade
 
Programming Ios 4 Fundamentals Of Iphone Ipad And Ipod Touch Development 1st ...
Programming Ios 4 Fundamentals Of Iphone Ipad And Ipod Touch Development 1st ...
jungieaugi
 
RESTful Java With JAX RS 1st Edition Bill Burke
RESTful Java With JAX RS 1st Edition Bill Burke
jolokmertah
 
SPENT QUIZ NQL JR FEST 5.0 BY SOURAV.pptx
SPENT QUIZ NQL JR FEST 5.0 BY SOURAV.pptx
Sourav Kr Podder
 
LDMMIA GRAD Student Check-in Orientation Sampler
LDMMIA GRAD Student Check-in Orientation Sampler
LDM & Mia eStudios
 

More Related Content

Similar to Nodejs For Php Developers Porting Php To Nodejs 1st Edition Daniel Howard (20)

Angularjs
Angularjs
Heinrrich Facho
 
Angular js book
Angular js book
Kamlesh Singh
 
Linux System Programming 1st Edition Robert Love
Linux System Programming 1st Edition Robert Love
bouliflantei
 
Php5
Php5
daveparky
 
RESTful Java With JAX RS 1st Edition Bill Burke
RESTful Java With JAX RS 1st Edition Bill Burke
rohismhmob88
 
Client Server Web Apps with JavaScript and Java 1st Edition Casimir Saternos
Client Server Web Apps with JavaScript and Java 1st Edition Casimir Saternos
tomeooakesrq
 
Learning Web App Development 1st Edition Semmy Purewal
Learning Web App Development 1st Edition Semmy Purewal
molaxmeizu
 
Introducing Erlang Getting Started In Functional Programming 2nd Edition St L...
Introducing Erlang Getting Started In Functional Programming 2nd Edition St L...
izalhideto
 
Designing Evolvable Web Apis With Aspnet 1st Edition Glenn Block
Designing Evolvable Web Apis With Aspnet 1st Edition Glenn Block
zisanashham6
 
Dat 210 academic adviser ....tutorialrank.com
Dat 210 academic adviser ....tutorialrank.com
ladworkspaces
 
DAT 210 Education Specialist |tutorialrank.com
DAT 210 Education Specialist |tutorialrank.com
ladworkspaces
 
[Ebooks PDF] download AngularJS 1st Edition Brad Green full chapters
[Ebooks PDF] download AngularJS 1st Edition Brad Green full chapters
kiciunonge
 
Instant Download Hadoop Operations 1st Edition Eric Sammer PDF All Chapters
Instant Download Hadoop Operations 1st Edition Eric Sammer PDF All Chapters
istvanysmoni
 
Consumer centric api design v0.4.0
Consumer centric api design v0.4.0
mustafa sarac
 
Designing Web Apis Building Apis That Developers Love Jin Brendasahni
Designing Web Apis Building Apis That Developers Love Jin Brendasahni
reknesluima
 
Instant download Developing Backbone js Applications Addy Osmani pdf all chapter
Instant download Developing Backbone js Applications Addy Osmani pdf all chapter
dinetvenitja
 
Sap to php
Sap to php
kerbarous
 
Web_Development_with_Node_Express.pdf
Web_Development_with_Node_Express.pdf
Marco Antonio Martinez Andrade
 
Programming Ios 4 Fundamentals Of Iphone Ipad And Ipod Touch Development 1st ...
Programming Ios 4 Fundamentals Of Iphone Ipad And Ipod Touch Development 1st ...
jungieaugi
 
RESTful Java With JAX RS 1st Edition Bill Burke
RESTful Java With JAX RS 1st Edition Bill Burke
jolokmertah
 
Linux System Programming 1st Edition Robert Love
Linux System Programming 1st Edition Robert Love
bouliflantei
 
RESTful Java With JAX RS 1st Edition Bill Burke
RESTful Java With JAX RS 1st Edition Bill Burke
rohismhmob88
 
Client Server Web Apps with JavaScript and Java 1st Edition Casimir Saternos
Client Server Web Apps with JavaScript and Java 1st Edition Casimir Saternos
tomeooakesrq
 
Learning Web App Development 1st Edition Semmy Purewal
Learning Web App Development 1st Edition Semmy Purewal
molaxmeizu
 
Introducing Erlang Getting Started In Functional Programming 2nd Edition St L...
Introducing Erlang Getting Started In Functional Programming 2nd Edition St L...
izalhideto
 
Designing Evolvable Web Apis With Aspnet 1st Edition Glenn Block
Designing Evolvable Web Apis With Aspnet 1st Edition Glenn Block
zisanashham6
 
Dat 210 academic adviser ....tutorialrank.com
Dat 210 academic adviser ....tutorialrank.com
ladworkspaces
 
DAT 210 Education Specialist |tutorialrank.com
DAT 210 Education Specialist |tutorialrank.com
ladworkspaces
 
[Ebooks PDF] download AngularJS 1st Edition Brad Green full chapters
[Ebooks PDF] download AngularJS 1st Edition Brad Green full chapters
kiciunonge
 
Instant Download Hadoop Operations 1st Edition Eric Sammer PDF All Chapters
Instant Download Hadoop Operations 1st Edition Eric Sammer PDF All Chapters
istvanysmoni
 
Consumer centric api design v0.4.0
Consumer centric api design v0.4.0
mustafa sarac
 
Designing Web Apis Building Apis That Developers Love Jin Brendasahni
Designing Web Apis Building Apis That Developers Love Jin Brendasahni
reknesluima
 
Instant download Developing Backbone js Applications Addy Osmani pdf all chapter
Instant download Developing Backbone js Applications Addy Osmani pdf all chapter
dinetvenitja
 
Programming Ios 4 Fundamentals Of Iphone Ipad And Ipod Touch Development 1st ...
Programming Ios 4 Fundamentals Of Iphone Ipad And Ipod Touch Development 1st ...
jungieaugi
 
RESTful Java With JAX RS 1st Edition Bill Burke
RESTful Java With JAX RS 1st Edition Bill Burke
jolokmertah
 

Recently uploaded (20)

SPENT QUIZ NQL JR FEST 5.0 BY SOURAV.pptx
SPENT QUIZ NQL JR FEST 5.0 BY SOURAV.pptx
Sourav Kr Podder
 
LDMMIA GRAD Student Check-in Orientation Sampler
LDMMIA GRAD Student Check-in Orientation Sampler
LDM & Mia eStudios
 
Paper 108 | Thoreau’s Influence on Gandhi: The Evolution of Civil Disobedience
Paper 108 | Thoreau’s Influence on Gandhi: The Evolution of Civil Disobedience
Rajdeep Bavaliya
 
Ray Dalio How Countries go Broke the Big Cycle
Ray Dalio How Countries go Broke the Big Cycle
Dadang Solihin
 
Capitol Doctoral Presentation -June 2025.pptx
Capitol Doctoral Presentation -June 2025.pptx
CapitolTechU
 
THERAPEUTIC COMMUNICATION included definition, characteristics, nurse patient...
THERAPEUTIC COMMUNICATION included definition, characteristics, nurse patient...
parmarjuli1412
 
Introduction to Generative AI and Copilot.pdf
Introduction to Generative AI and Copilot.pdf
TechSoup
 
PEST OF WHEAT SORGHUM BAJRA and MINOR MILLETS.pptx
PEST OF WHEAT SORGHUM BAJRA and MINOR MILLETS.pptx
Arshad Shaikh
 
ICT-8-Module-REVISED-K-10-CURRICULUM.pdf
ICT-8-Module-REVISED-K-10-CURRICULUM.pdf
penafloridaarlyn
 
How to Manage Multi Language for Invoice in Odoo 18
How to Manage Multi Language for Invoice in Odoo 18
Celine George
 
Non-Communicable Diseases and National Health Programs – Unit 10 | B.Sc Nursi...
Non-Communicable Diseases and National Health Programs – Unit 10 | B.Sc Nursi...
RAKESH SAJJAN
 
JHS SHS Back to School 2024-2025 .pptx
JHS SHS Back to School 2024-2025 .pptx
melvinapay78
 
2025 June Year 9 Presentation: Subject selection.pptx
2025 June Year 9 Presentation: Subject selection.pptx
mansk2
 
FEBA Sofia Univercity final diplian v3 GSDG 5.2025.pdf
FEBA Sofia Univercity final diplian v3 GSDG 5.2025.pdf
ChristinaFortunova
 
Chalukyas of Gujrat, Solanki Dynasty NEP.pptx
Chalukyas of Gujrat, Solanki Dynasty NEP.pptx
Dr. Ravi Shankar Arya Mahila P. G. College, Banaras Hindu University, Varanasi, India.
 
Nice Dream.pdf /
Nice Dream.pdf /
ErinUsher3
 
How to Implement Least Package Removal Strategy in Odoo 18 Inventory
How to Implement Least Package Removal Strategy in Odoo 18 Inventory
Celine George
 
Overview of Employee in Odoo 18 - Odoo Slides
Overview of Employee in Odoo 18 - Odoo Slides
Celine George
 
Sustainable Innovation with Immersive Learning
Sustainable Innovation with Immersive Learning
Leonel Morgado
 
june 10 2025 ppt for madden on art science is over.pptx
june 10 2025 ppt for madden on art science is over.pptx
roger malina
 
SPENT QUIZ NQL JR FEST 5.0 BY SOURAV.pptx
SPENT QUIZ NQL JR FEST 5.0 BY SOURAV.pptx
Sourav Kr Podder
 
LDMMIA GRAD Student Check-in Orientation Sampler
LDMMIA GRAD Student Check-in Orientation Sampler
LDM & Mia eStudios
 
Paper 108 | Thoreau’s Influence on Gandhi: The Evolution of Civil Disobedience
Paper 108 | Thoreau’s Influence on Gandhi: The Evolution of Civil Disobedience
Rajdeep Bavaliya
 
Ray Dalio How Countries go Broke the Big Cycle
Ray Dalio How Countries go Broke the Big Cycle
Dadang Solihin
 
Capitol Doctoral Presentation -June 2025.pptx
Capitol Doctoral Presentation -June 2025.pptx
CapitolTechU
 
THERAPEUTIC COMMUNICATION included definition, characteristics, nurse patient...
THERAPEUTIC COMMUNICATION included definition, characteristics, nurse patient...
parmarjuli1412
 
Introduction to Generative AI and Copilot.pdf
Introduction to Generative AI and Copilot.pdf
TechSoup
 
PEST OF WHEAT SORGHUM BAJRA and MINOR MILLETS.pptx
PEST OF WHEAT SORGHUM BAJRA and MINOR MILLETS.pptx
Arshad Shaikh
 
ICT-8-Module-REVISED-K-10-CURRICULUM.pdf
ICT-8-Module-REVISED-K-10-CURRICULUM.pdf
penafloridaarlyn
 
How to Manage Multi Language for Invoice in Odoo 18
How to Manage Multi Language for Invoice in Odoo 18
Celine George
 
Non-Communicable Diseases and National Health Programs – Unit 10 | B.Sc Nursi...
Non-Communicable Diseases and National Health Programs – Unit 10 | B.Sc Nursi...
RAKESH SAJJAN
 
JHS SHS Back to School 2024-2025 .pptx
JHS SHS Back to School 2024-2025 .pptx
melvinapay78
 
2025 June Year 9 Presentation: Subject selection.pptx
2025 June Year 9 Presentation: Subject selection.pptx
mansk2
 
FEBA Sofia Univercity final diplian v3 GSDG 5.2025.pdf
FEBA Sofia Univercity final diplian v3 GSDG 5.2025.pdf
ChristinaFortunova
 
Nice Dream.pdf /
Nice Dream.pdf /
ErinUsher3
 
How to Implement Least Package Removal Strategy in Odoo 18 Inventory
How to Implement Least Package Removal Strategy in Odoo 18 Inventory
Celine George
 
Overview of Employee in Odoo 18 - Odoo Slides
Overview of Employee in Odoo 18 - Odoo Slides
Celine George
 
Sustainable Innovation with Immersive Learning
Sustainable Innovation with Immersive Learning
Leonel Morgado
 
june 10 2025 ppt for madden on art science is over.pptx
june 10 2025 ppt for madden on art science is over.pptx
roger malina
 
Ad

Nodejs For Php Developers Porting Php To Nodejs 1st Edition Daniel Howard

  • 1. Nodejs For Php Developers Porting Php To Nodejs 1st Edition Daniel Howard download https://ebookbell.com/product/nodejs-for-php-developers-porting- php-to-nodejs-1st-edition-daniel-howard-4078222 Explore and download more ebooks at ebookbell.com
  • 2. Here are some recommended products that we believe you will be interested in. You can click the link to download. Nodejs For Php Developers Daniel Howard https://ebookbell.com/product/nodejs-for-php-developers-daniel- howard-61579382 Nodejs For Beginners A Comprehensive Guide To Building Efficient Fullfeatured Web Applications With Nodejs 1st Edition Ulises Gascn https://ebookbell.com/product/nodejs-for-beginners-a-comprehensive- guide-to-building-efficient-fullfeatured-web-applications-with- nodejs-1st-edition-ulises-gascn-57082746 Nodejs For Net Developers 1st Edition David Gaynes https://ebookbell.com/product/nodejs-for-net-developers-1st-edition- david-gaynes-4991070 Nodejs For Embedded Systems Using Web Technologies To Build Connected Devices Patrick Mulder https://ebookbell.com/product/nodejs-for-embedded-systems-using-web- technologies-to-build-connected-devices-patrick-mulder-5903438
  • 3. Nodejs For Net Developers Developer Reference David Gaynes https://ebookbell.com/product/nodejs-for-net-developers-developer- reference-david-gaynes-167564452 Nodejs For Beginners A Comprehensive Guide To Building Efficient Fullfeatured Web Applications With Nodejs Ulises Gascn https://ebookbell.com/product/nodejs-for-beginners-a-comprehensive- guide-to-building-efficient-fullfeatured-web-applications-with-nodejs- ulises-gascn-231447746 Nodejs For Beginners A Comprehensive Guide To Building Efficient Fullfeatured Web Applications With Nodejs Ulises Gascn https://ebookbell.com/product/nodejs-for-beginners-a-comprehensive- guide-to-building-efficient-fullfeatured-web-applications-with-nodejs- ulises-gascn-57113056 Nodejs For Embedded Systems Patrick Mulder https://ebookbell.com/product/nodejs-for-embedded-systems-patrick- mulder-170703072 Ultimate Nodejs For Crossplatform App Development Learn To Build Robust Scalable And Performant Serverside Javascript Applications With Nodejs English Edition 1st Edition Kumar https://ebookbell.com/product/ultimate-nodejs-for-crossplatform-app- development-learn-to-build-robust-scalable-and-performant-serverside- javascript-applications-with-nodejs-english-edition-1st-edition- kumar-55500574
  • 7. Daniel Howard Node.js for PHP Developers
  • 8. ISBN: 978-1-449-33360-7 [LSI] Node.js for PHP Developers by Daniel Howard Copyright © 2013 Daniel Howard. All rights reserved. Printed in the United States of America. Published by O’Reilly Media, Inc., 1005 Gravenstein Highway North, Sebastopol, CA 95472. O’Reilly books may be purchased for educational, business, or sales promotional use. Online editions are also available for most titles (http://my.safaribooksonline.com). For more information, contact our corporate/ institutional sales department: 800-998-9938 or [email protected]. Editors: Simon St. Laurent and Meghan Blanchette Production Editor: Kara Ebrahim Copyeditor: Jasmine Kwityn Proofreader: Kara Ebrahim Indexer: Potomac Indexing, LLC Cover Designer: Karen Montgomery Interior Designer: David Futato Illustrator: Rebecca Demarest December 2012: First Edition Revision History for the First Edition: 2012-11-28 First release See http://oreilly.com/catalog/errata.csp?isbn=9781449333607 for release details. Nutshell Handbook, the Nutshell Handbook logo, and the O’Reilly logo are registered trademarks of O’Reilly Media, Inc. Node.js for PHP Developers, the image of the Wallachian sheep, and related trade dress are trademarks of O’Reilly Media, Inc. Many of the designations used by manufacturers and sellers to distinguish their products are claimed as trademarks. Where those designations appear in this book, and O’Reilly Media, Inc., was aware of a trade‐ mark claim, the designations have been printed in caps or initial caps. While every precaution has been taken in the preparation of this book, the publisher and author assume no responsibility for errors or omissions, or for damages resulting from the use of the information contained herein.
  • 9. Table of Contents Preface. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . v 1. Node.js Basics. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1 The node and npm Executables 1 Stack Traces 7 Eclipse PDT 9 2. A Simple Node.js Framework. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21 An HTTP Server 21 Predefined PHP Variables 29 A PHP Example Page 42 3. Simple Callbacks. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47 Linearity 49 Making Code Linear 57 4. Advanced Callbacks. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65 Anonymous Functions, Lambdas, and Closures 66 PHP 5.3 69 PHP 4 73 5. HTTP Responses. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89 Headers 90 Body 92 A PHP Example Page 97 6. Syntax. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 107 String Literals 109 Syntax Differences 112 iii
  • 10. PHP Alternative Syntax 117 7. Variables. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 125 Simple Variables 126 Array Variables 128 Other Variable Types 143 Undefined Variables 144 Scope 148 8. Classes. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 157 Encapsulation 157 Inheritance 166 PHP parent and static Keywords 173 9. File Access. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 177 Reading and Writing Files 177 PHP file() API Function 183 Low-Level File Handling 186 Filenames 191 10. MySQL Access. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 199 Database Approaches 200 node-mysql 203 11. Plain Text, JSON, and XML. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 219 Plain Text 221 JSON 223 XML 226 12. Miscellaneous Functions. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 241 Array 242 Time and Date 246 File 247 JSON 247 Math 248 String 249 Type 253 Text 254 MySQL 257 Variable 257 php.js License 258 Index. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 261 iv | Table of Contents
  • 11. Preface Why bother with this book? PHP is an old language, as Internet languages go, invented in 1995. Node.js is new, very new, invented in 2009. Looking at PHP side by side with Node.js gives you a bird’s eye view of where web servers started, how far they have come, and what’s changed. But, more importantly, it shows what hasn’t changed—what the industry as a whole has agreed are good practices—and a little bit of what the future holds. The biggest difference between PHP and Node.js is that PHP is a blocking language, relying on APIs that don’t return until they are done, and Node.js is a nonblocking language, relying on APIs that use events and callbacks when they are done. But, except for that, they are surprisingly similar. Both use the curly bracket notation ( { and } ) for blocks of code, just like the C programming language. Both have the function keyword, which serves the exact same purpose and has the exact same syntax in both languages. If Node.js shows that blocking APIs are the past, it also shows that a pretty specific variation of the C programming language is the past, present, and future. Callbacks may be an evolution, but syntax is almost frozen. But beyond just, “oh, isn’t that interesting,” why bother with this book? PHP is supported by a zillion cPanel website hosting services. If you develop a web application and want to give it to other people to run, they can install it almost anywhere if it is written in PHP. They can buy web hosting for $10 per month, install your PHP web application, and be on their way. Node.jsisnotsupportedbyazillioncPanelwebsitehostingservices.Infact,Idon’tknow even one web hosting service that supports it. But I know that a lot of developers are v
  • 12. interested in it and are writing Node.js code. By writing Node.js code, you make your web application code interesting and useful to a lot of developers. If you develop a web application and want to give it to other developers to improve and reuse, they can get your Node.js web application from GitHub or wherever else the source code is hosted. In a perfect world, you could appeal to both sets of people. Ours isn’t a perfect world, but you can still achieve this goal by porting your PHP code to Node.js code and simultaneously having and developing two working codebases in two different languages. The Mission The mission of this book—and when I write “mission,” I mean it in the “I really, really, really, really want you to do it” kind of mission—is to convince you to convert some of your PHP code to Node.js code. I don’t want you to just read this book. I want you to actually sit down at a computer and take some of your most tired, annoying PHP 4 code and convert it to Node.js using this book as a guide. I want you to see for yourself that PHP and Node.js are not that different. I want you to see for yourself that your PHP code does not need to be thrown away and rewritten in Node.js from scratch. I want you to see for yourself that you don’t have to surrender to just living with your PHP code, being a prisoner of the past. As you will see, converting your PHP code to Node.js code isn’t just about Node.js. It is also about improving your PHP code. An important step throughout this book is re‐ factoring and improving your PHP code such that it is easier to convert it to Node.js code. This book isn’t just about making a new Node.js codebase. It is about improving your PHP codebase and creating a new Node.js codebase. It is about both codebases: your PHP codebase and your Node.js codebase. Converting your PHP codebase to Node.js can make you a better PHP developer. If you are a PHP developer, this book is perfect for you because you can learn how to develop Node.js code by using your existing PHP knowledge. You can see how certain code works in PHP, such as reading a text file, and in the next few paragraphs, you can see how exactly the same thing is accomplished in Node.js. Unlike other Node.js books, this book does not describe file handling in general. It specifically compares it to PHP so you can see the nuts and bolts of what it looks like in the language that you know as well as in the language you are learning. You might even find a few corners of PHP you weren’t previously aware of, because a few of those PHP corners are central concepts in Node.js. If you are a Node.js developer already, you have a decent chance of learning PHP from this book. After all, if PHP developers can figure out Node.js by looking at PHP code vi | Preface
  • 13. sidebysidewithNode.js,thereisgoodreasontothinkthatNode.jsdeveloperscanfigure out PHP by looking at the same code. Even better, by comparing Node.js to a specific different language, such as PHP, it will give you a good idea as to how much of Node.js is the same as PHP. Comparing two languages or, even better, showing how to convert or port from one language to another, is a powerful way to become an expert in both languages. Other books, which deal with only one language, mostly read like step-by-step tutorials or encyclopedias. “This is this,” they read, “that is that.” They can describe concepts only as abstractions. Other books can’t use the powerful explanation of an ongoing compar‐ ison of two languages that this book does. Besides being more effective, a book such as this one can also be more interesting and focus on only the interesting topics. In a run-of-the-mill Node.js programming book, time is spent explaining what a statement is and why every Node.js statement ends in a semicolon (;). That’s dull. But when a book is explaining how to program in Node.js in a vacuum without any point of reference (such as the PHP language), there is no alter‐ native. With this book, I can assume that you already know what a PHP statement is and that a PHP statement ends in a semicolon (;). All that needs to be said is that Node.js is exactly the same way. With this book, I can assume that the reader has a specific background—PHP development—instead of needing to write more broadly for people who come with a Python or Microsoft Office macro background. By proselytizing the conversion of PHP code to Node.js code, I am not saying that PHP code is bad. In fact, I think PHP is a very capable and pretty good language. I am not saying that you should convert your PHP code to Node.js code and then throw away theoriginalPHPcode.IamencouragingyoutokeeptheoriginalPHPcodeandimprove it while, at the same time, becoming a skilled Node.js developer. PHP and Node.js are both important. When first setting out to write this book, I made a very important decision early on: I was going to focus on real-life, practical, existing PHP code. PHP 5 is the current PHP version, but there is still a lot of PHP 4 code out there. This book has explicitly avoided the easy prescription: convert your PHP 4 code to PHP 5 code, then use this book to convert your PHP 5 code to Node.js. No, despite the fact that PHP 4 support is rapidly fading in favor of PHP 5 support, this book takes the much harder road of showing how PHP 4 code can be improved upon and converted to Node.js code without requiring PHP 5 features. Although this book does show how to convert PHP 5 code to Node.js, let me assure you that PHP 4 code is readily convertible to Node.js using this book. VerysoonaftermakingthedecisiontoembraceandaddressPHP4code,Imadeanother decision related to this book: I was going to describe a system of conversion such that the PHP code and the Node.js code would be kept synchronized and working through‐ out the conversion process. At the end of the conversion process, both the PHP and Node.js codebases would be fully functional and, going forward, new features and bug Preface | vii
  • 14. fixescouldbedevelopedonbothcodebasessimultaneously.Thisdecisionavoidsamuch easier approach, which would have been a “convert-and-discard” conversion process where the PHP codebase would be unsynchronized and possibly not working at the end of the conversion process and the developer’s only option would be to proceed ahead with the Node.js codebase by itself. This would have made a much shorter book, but would have been a cheap trick—a way to make life easier for me, as the writer, and make the book less useful to you, as the reader. These two decisions, one to support PHP 4 and the other to support two synchronized PHPandNode.jscodebasesasanendproduct,havemadethisbooklongerthanitwould otherwise be, but have also made it eminently practical. This is not a book that you will read once and put on the shelf as an “isn’t that nice to know” book. This is a book that you can use for reference to quickly refresh yourself about important aspects of either PHP or Node.js. By now, you might understand what the mission is and why it might be worthwhile. But maybe you are still doubtful. Consider the following PHP code, which was taken from a real-world PHP web appli‐ cation that implemented instant message−style chatting: function roomlist() { $rooms = array(); $room_list = mysql_query( 'SELECT room FROM '.SQL_PREFIX.'chats GROUP BY room ORDER BY room ASC' ); while ($row = mysql_fetch_assoc($room_list)) { $room = $row['room']; $rooms[] = $room; } print json_encode($r); } Now consider the equivalent code in Node.js: function roomlist() { var rooms = [ ]; link.query( 'SELECT room FROM '+SQL_PREFIX+'chats GROUP BY room ORDER BY room ASC', function(err, rows, fields) { for (var r=0; r < rows.length; ++r) { var row = rows[r]; var room = row['room']; rooms.push(room); } res.writeHead(200, {'Content-Type': 'text/plain'}); res.end(JSON.stringify(r)); } }); }; viii | Preface
  • 15. Sure, the syntax is a bit different. To concatenate strings, PHP uses the dot (.) operator whereas JavaScript uses the plus (+) operator. PHP uses array() to initialize an array, but JavaScript uses square brackets ( [ and ] ). It’s not identical. Butforheaven’ssake,it’sstillprettydarnclose.Thisisn’t“fake”code,either:itusesarrays, accesses a MySQL database, uses JSON, and writes output. The similarities and the possibility of converting PHP source code to Node.js, and con‐ sequently the writing of this book for O’Reilly Media, are a direct result of my experience with creating a Node.js implementation of my open source project. Who I Am I’m Daniel Howard, the founder and sole maintainer of ajaximrpg, a preeminent browser-based instant messaging (IM) and chat system. ajaximrpg is specifically geared toward playing tabletop role-playing games, such as Dungeons & Dragons, over the Internet, although the role-playing specific features can be stripped away to reveal a general-purpose client. ajaximrpg is completely open source and available via Source‐ Forge with a full range of supporting services such as a Twitter feed, a Google Group, and a live demo. ajaximrpg was originally written in PHP 4 with no inkling that it might someday be ported to Node.js JavaScript. But it works on PHP 5 and, now, on Node.js. Starting in January 2012, it took me a single week to come up to speed on Node.js and do a proof of concept to have my client-side JavaScript code detect the installation status of the server side running on Node.js. In a month, I had enough of a few thousand lines converted to enable users to log in and IM each other. It dawned on me that there were general principles at work here, and that these general principles could be laid out in a book to explain how to convert any PHP source code to Node.js and, using these prin‐ ciples, the reader of the book could apply them to his PHP source code much quicker and more accurately than just muddling along as I had. I put aside my mostly working but not yet completed Node.js implementation and im‐ mediately set out to write this book that you now hold in your hands (or view on your screen). This Book This book consists of 12 chapters, starting out with the basics and moving on to more advanced topics. Preface | ix
  • 16. Chapter 1, Node.js Basics This chapter describes how to install Node.js and use the Node.js executables, node and npm. It also describes how to install the Eclipse PDT and configure it for use for a PHP to Node.js conversion. Chapter 2, A Simple Node.js Framework This chapter presents a simple Node.js framework such that individual PHP pages canbeconvertedtoNode.jsfilesandtheresultingNode.jsfileswillbeinvokedwhen actions, such as visiting a URL, are taken against the Node.js web server. Chapter 3, Simple Callbacks This chapter explains how to refactor blocking PHP source code such that it can be easily converted to nonblocking Node.js source code that uses callbacks. It presents the concept of linearity as a simple way to analyze and improve PHP source code such that it can be placed in Node.js callbacks when converted to Node.js. Chapter 4, Advanced Callbacks This chapter presents a more sophisticated and generic way to refactor blocking PHP 4 source code to simulate anonymous functions, function variables, and clo‐ sure. For PHP 5 source code, it explains how to use PHP 5 features to actually implement anonymous functions, function variables, and closure. Chapter 5, HTTP Responses This chapter explains how to convert PHP output, such as the print and echo keywords, into HTTP responses in Node.js. Chapter 6, Syntax ThischapterexplainshowtoconvertPHPsyntax,suchasconcatenatingtwostrings, into Node.js syntax. Chapter 7, Variables This chapter explains how to convert PHP single and array variables into Node.js, as well as common operations, such as adding and deleting elements from array variables. It also describes how to convert PHP types to Node.js types. Chapter 8, Classes This chapter presents a way to implement PHP classes and class inheritance in Node.js with a step-by-step technique to perform the conversion. Chapter 9, File Access This chapter explains all the file reading and file writing APIs in both PHP and Node.js. It explains how to convert the PHP file handling APIs into their Node.js equivalents. x | Preface
  • 17. Chapter 10, MySQL Access This chapter describes all the ways that a database, specifically a MySQL database, canbeusedinawebapplication.Itprovidesastep-by-stepprocedureforconverting database access code from the PHP MySQL APIs to use the node-mysql Node.js npm package. Chapter 11, Plain Text, JSON, and XML This chapter explains three data formats: plain text, JSON, and XML. It explains how to convert PHP source code that uses PHP JSON or XML APIs into Node.js source code that uses similar Node.js npm packages. Chapter 12, Miscellaneous Functions This chapter provides Node.js implementations for a large number of PHP API functions. These Node.js implementations can be used to speed along conversion and provide an interesting way to contrast PHP and Node.js. Now let’s get started with Node.js. About This Book This book is about how to take existing PHP source code and develop new Node.js source code from it. PHP and Node.js have many similarities, but of course, there are some significant differences. By leveraging the similarities and noting the differences, you can use your PHP experience to learn Node.js and, ultimately, create a Node.js web application that is a drop-in replacement for any existing PHP web application that you have. This book assumes that you are a developer who understands the basics of development, suchascreatingandthenimplementingadesigninworkinglinesofprogrammingcode. It assumes that you are already familiar with classes, functions, and looping constructs. It also assumes that you are familiar with web development, including the basics of how web browsers and web servers interact to create a web application. Furthermore, this book assumes that you have significant expertise in the PHP pro‐ gramming language. If you do not have a background in the PHP programming lan‐ guage,itispossiblethatyoucanuseyourbackgroundinanotherprogramminglanguage (e.g., Python, Ruby, or C) and, by reading this book and examining the intersection between PHP, Node.js, and the programming language that is familiar to you, acquire a good understanding of both PHP and Node.js. Not necessarily easy, but possible. This book can be read straight through as a Node.js tutorial, consulted as a reference to see how a specific PHP feature can be implemented in Node.js, or executed as a step- by-step recipe to convert an arbitrary PHP web application into a Node.js web applica‐ tion. The book was written to serve all these purposes. Preface | xi
  • 18. No matter how you approach this book, as its author, I sincerely hope that it answers the questions you have about PHP and Node.js. Conventions Used in This Book The following typographical conventions are used in this book: Italic Indicates new terms, URLs, email addresses, filenames, and file extensions. Constant width Used for program listings, as well as within paragraphs to refer to program elements such as variable or function names, databases, data types, environment variables, statements, and keywords. Constant width bold Shows commands or other text that should be typed literally by the user. Constant width italic Shows text that should be replaced with user-supplied values or by values deter‐ mined by context. This icon signifies a tip, suggestion, or general note. This icon indicates a warning or caution. Using Code Examples This book is here to help you get your job done. In general, you may use the code in this book in your programs and documentation. You do not need to contact us for permis‐ sion unless you’re reproducing a significant portion of the code. For example, writing a program that uses several chunks of code from this book does not require permission. Selling or distributing a CD-ROM of examples from O’Reilly books does require per‐ mission. Answering a question by citing this book and quoting example code does not require permission. Incorporating a significant amount of example code from this book into your product’s documentation does require permission. We appreciate, but do not require, attribution. An attribution usually includes the title, author, publisher, and ISBN. For example: “Node.js for PHP Developers by Daniel Ho‐ ward (O’Reilly). Copyright 2013 Daniel Howard, 978-0-596-33360-7.” xii | Preface
  • 19. If you feel your use of code examples falls outside fair use or the permission given above, feel free to contact us at [email protected]. Safari® Books Online Safari Books Online (www.safaribooksonline.com) is an on-demand digital library that delivers expert content in both book and video form from the world’s leading authors in technology and business. Technologyprofessionals,softwaredevelopers,webdesigners,andbusinessandcreative professionals use Safari Books Online as their primary resource for research, problem solving, learning, and certification training. Safari Books Online offers a range of product mixes and pricing programs for organi‐ zations, government agencies, and individuals. Subscribers have access to thousands of books, training videos, and prepublication manuscripts in one fully searchable database from publishers like O’Reilly Media, Prentice Hall Professional, Addison-Wesley Pro‐ fessional, Microsoft Press, Sams, Que, Peachpit Press, Focal Press, Cisco Press, John Wiley & Sons, Syngress, Morgan Kaufmann, IBM Redbooks, Packt, Adobe Press, FT Press, Apress, Manning, New Riders, McGraw-Hill, Jones & Bartlett, Course Technol‐ ogy, and dozens more. For more information about Safari Books Online, visit us online. How to Contact Us Please address comments and questions concerning this book to the publisher: O’Reilly Media, Inc. 1005 Gravenstein Highway North Sebastopol, CA 95472 800-998-9938 (in the United States or Canada) 707-829-0515 (international or local) 707-829-0104 (fax) We have a web page for this book, where we list errata, examples, and any additional information. You can access this page at http://oreil.ly/nodejs-php. To comment or ask technical questions about this book, send email to [email protected]. For more information about our books, courses, conferences, and news, see our website at http://www.oreilly.com. Find us on Facebook: http://facebook.com/oreilly Follow us on Twitter: http://twitter.com/oreillymedia Watch us on YouTube: http://www.youtube.com/oreillymedia Preface | xiii
  • 20. Acknowledgments This book is the product of many months of effort by me, of course, but also by several others. I want to thank the editors at O’Reilly Media, Inc., specifically Simon St. Laurent and Meghan Blanchette, for their encouragement and feedback. I want to thank Neha Utkur, the book’s technical editor, for her enthusiasm and will‐ ingness to provide feedback on a whole range of areas that sorely needed her input. Her contribution has made this a much better book. Finally, I want to thank Shelley Powers for lending a second pair of eyes to review the book for technical accuracy. xiv | Preface
  • 21. CHAPTER 1 Node.js Basics Let’s assume you have a significant PHP codebase that you have decided to convert to Node.js. You will provide both the PHP and Node.js codebases to your users for the foreseeable future, meaning that you will update and improve both codebases simulta‐ neously. But you only know a little about Node.js; in fact, you have not really done any serious development with Node.js yet. Where do you start? The first thing to do is to download Node.js for your platform, probably Linux or Win‐ dows (yes, they have a Windows version now!). Since installation methods and installers vary from version to version and change over time, this book will not spend time on how to install the current version. Instead, if you need assistance with installation, you should use the online documentation and, if that fails you, use Google or another search engine to find web pages and forum postings where others have come across the same installation issues you are having and have found solutions that you can use. The node and npm Executables Once installed, you will see that a Node.js installation is fairly simple and has two main parts: the main node executable and the npm executable. The node executable is simple to use. Although it has other arguments, usually you will pass only one argument, the name of your main Node.js source file. For example: node hello.js The node executable will interpret the Node.js code within the source file (hello.js in this case), execute the code, and when it finishes, exit back to the shell or command line. Notice that hello.js uses the .js extension. The .js extension stands for JavaScript. Un‐ fortunately, files with the .js extension can contain either client-side JavaScript or server- side Node.js code. Even though they both use the JavaScript language, they have nothing 1
  • 22. else in common. Client-side JavaScript code needs to be served out to browsers, while server-side Node.js code needs to have the node executable run on it or otherwise needs to be accessible to the main Node.js code that is being run under the node executable. This is a serious and unnecessary cause of confusion. In some Node.js projects, the client-side JavaScript files are put in one folder, such as a client folder, while the Node.js files are put in another folder named something like server.Separatingclient-sideJavaScriptfilesfromNode.jsfilesviaafolderschemehelps, but is still problematic because many source code editors show only the filename but not the full path name in a title bar or tab. Instead, I have adopted the .njs extension for Node.js files and reserved the .js extension for client-side JavaScript files in my own projects. Let me be clear, though: the .njs extension is not a standard! At least, not yet (and maybe not ever). I have diligently searched using Google, and it is common to use the .js extension for Node.js code. To avoid constant confusion between client-side and server-side JavaScript, I use the .njs extension for Node.js code, and in your own PHP to Node.js conversion, I suggest that you do the same. So, instead of using the hello.js file given earlier, I would use hello.njs: node hello.njs The remainder of this book will use the .njs extension for Node.js files. A simple hello.njs looks like: console.log('Hello world!'); If you run node hello.njs on this source file, it prints “Hello world!” to the console and then exits. To actually get a web server running, use the following hellosvr.njs source file: var http = require('http'); http.createServer(function (req, res) { res.writeHead(200, {'Content-Type': 'text/plain'}); res.end('Hello World!n'); }).listen(1337, '127.0.0.1'); console.log('Server running at http://127.0.0.1:1337/'); If you run node hellosvr.njs, the command line will intentionally hang. The server must continue to run so it can wait for web page requests and respond to them. If you start a browser such as Firefox or Chrome and type http://127.0.0.1:1337/ into the address bar, you will see a simple web page that says, “Hello world!” In fact, if you go to http://127.0.0.1:1337/index.html or http://127.0.0.1:1337/abc or even http:// 127.0.0.1:1337/abc/def/ghi, you will always see the same simple web page that says “Hello world!” because the server responds to all web page requests in the same way. 2 | Chapter 1: Node.js Basics
  • 23. For now, the important line in this source file is the first line that uses the Node.js require() global function. The require() function makes a Node.js module available for use. Node.js modules are what you might expect: a collection of data and functions that are bundled together, usually providing functionality in some particular area. In this case, the http Node.js module provides simple HTTP server functionality. The node executable has a number of built-in modules: http, https, fs, path, crypto, url, net, dgram, dns, tls, and child_process. Expect these built-in modules and their functionality to vary from version to version. By design, a module resides in a namespace. A namespace is an extra specification that is added to the front of a data or function reference; for example, http is the namespace that the createServer() function resides in. In Node.js, a namespace is just imple‐ mented as an object. When the http module is loaded, the require() function returns an object and that object is assigned to the http variable. The variable does not have to be called “http”; it could be called “xyzzybub” and, in that case, the server would be created by calling the xyzzybub.createServer() function. Why have a namespace? Why not just put all the data and functions as global variables? Node.js anticipated that new modules with new functionality, such as a MySQL access, would be developed by other people and need to be integrated into the node executable after Node.js was already installed on a user’s computer. Since the names of data and functions in those modules would be unpredictable, a developer might accidentally choose the exact same name for a function in a module as a different developer might choose for another module. But since a module is contained in a namespace, the name‐ space would distinguish between the two functions. In fact, an important improvement over previous languages, such as C++ and Java, is that Node.js allows the user of the module to specify the name of the namespace because the user himself assigns the module to his variable, such as http or xyzzybub. These new modules with new functionality are packages. A package is a module that can be added to the node executable later and is not built into the node executable by default. The difference between a module and a package is not very important; it is really just a change of terminology. The npm (node package manager) executable adds new packages to the node executable. To install a package, first use Google or another search engine to find the npm package that you want to install. Often, the package will be found on GitHub. An alternative to using a search engine is to use the npm executable itself to find the package using the search command. Instead of the web server that always returns a “Hello world!” page, suppose we want to create a web server that actually serves up static web pages from files on the hard disk. To find a Node.js static file server module, a good search phrase to type into a search The node and npm Executables | 3
  • 24. engine is “nodejs static file web server”. Alternatively, “npm search static”, “npm search file”, or “npm search server” will list the npm packages that have the words “static”, “file”, or “server” in their names or descriptions. Using either of these two methods or both in combination (and with a little extra reading and browsing), you will find that Alexis Sellier, a.k.a. cloudhead, created a popular static file server module and hosted it here. This package can be installed by running the following command line (additional op‐ tions, such as the -g or --global command line switch, are available to configure the package installation): npm install node-static The npm executable will retrieve the package and, hopefully, install it successfully. Here’s the output from a successful installation: npm http GET https://registry.npmjs.org/node-static npm http 200 https://registry.npmjs.org/node-static npm http GET https://registry.npmjs.org/node-static/-/node-static-0.5.9.tgz npm http 200 https://registry.npmjs.org/node-static/-/node-static-0.5.9.tgz [email protected] ./node_modules/node-static The GET indicates that an HTTP GET was used to attempt to retrieve the package. The 200 indicates that the HTTP GET request returned “HTTP status 200 OK”, meaning that the file was retrieved successfully. There are hundreds of npm packages, but a few very popular ones are express, node- static, connect, sockets.io, underscore, async, and optimist. To implement a web server that serves up static web pages, use the following httpsvr.njs source file: var http = require('http'); var static = require('node-static'); var file = new static.Server(); http.createServer(function (req, res) { file.serve(req, res); }).listen(1337, '127.0.0.1'); console.log('Server running at http://127.0.0.1:1337/'); At a basic level, this is how Node.js development happens. An editor is used to create and modify one or more .njs source files that contain Node.js code. When new func‐ tionality is needed that is not built into the node executable, the npm executable is used to download and install the needed functionality in the form of an npm package. The node executable is run on the .njs files to execute the Node.js code so the web application can be tested and used. 4 | Chapter 1: Node.js Basics
  • 25. At this point, three Node.js servers have been presented: hello.njs, hellosvr.njs, and httpsvr.njs. These source files have been so simple that it did not matter how they were created. You could have used any text editor to create them and they would work fine. If you made a mistake, it was easily remedied by editing the source file. It is safe to assume, though, that you already have a complicated PHP web application with dozens of files and tens of thousands of lines of PHP that you want to convert to Node.js. The conversion strategy will follow a straightforward but tedious step-by-step routine. The first step will be to create a boilerplate Node.js source file, as described in detail in Chapter 2, that will support the new Node.js code. This boilerplate Node.js code will be enhanced to respond to the specific URLs that are available to be invoked by the client. A web application is, at its heart, a series of URL requests. The objective of conversion is to make a Node.js server that responds to the client in the exact same way as the PHP server. To make this happen, the boilerplate Node.js code is modified to handle each HTTPcallandrouteittospecificNode.jscodethatwilllaterimplementthefunctionality of the specific PHP page in Node.js. The second step will be to refactor the PHP code, as described in detail in Chapter 3 and Chapter 4, to make it easier to convert to Node.js code—that is, make the PHP code more Node.js friendly. It may come as a shock, but the conversion process is not just a matter of freezing the PHP code in whatever form it currently is, copying the PHP code into the Node.js source file, and then, line by line, converting the PHP code to Node.js code. Since both the PHP and Node.js code will be improved and have new features added going forward, it makes sense that both the PHP and Node.js code will need to “give” a little in their purity to smooth over the differences between how the two lan‐ guages function. The PHP code will need to be refactored and make some sacrifices that will allow functional Node.js code to be created later on. At the end of the conversion process, both codebases will look very similar and will be written in a sort of hybrid metalanguage, a collection of idioms and algorithms that are easily ported from PHP to Node.js. The metalanguage will make both codebases look a little odd, but will be fully functional and, with time, will become very familiar and understandable to the devel‐ opers who maintain and improve both codebases. Even if you plan to throw away the PHP code in the end and want to have pristine Node.js code, it is best to refactor the PHP code anyway, convert both the PHP and Node.js code into the odd hybrid meta‐ language, throw away the PHP code, and then refactor the hybridized Node.js code into pure Node.js code. Refactoring PHP code is an essential step for any PHP to Node.js conversion, no matter what your eventual goal is. The third step is to copy and paste one of the PHP pages from the PHP source file into the Node.js source file. Almost certainly, the Node.js server will then be broken; when the node executable is run on it, it will immediately exit with a stack trace. The node and npm Executables | 5
  • 26. ThefourthstepistoconvertandfixthenewlyaddedcodeintheNode.jsfile,asdescribed in detail in the remaining chapters, such that it becomes working Node.js code. Initially, the Node.js server will not run and will immediately exit with a stack trace. The stack trace will indicate the location of the error, which will be caused by some PHP code that was not completely converted or was not converted correctly to Node.js code. After the problem is analyzed, a conversion technique from one of the remaining chapters will be applied to the entire Node.js file; for example, Chapter 7 shows the technique to convert PHParrayinitializationusingthearray()functiontoNode.jsobjectinitializationusing curly brackets ( { and } ). When the Node.js server is run again, it will get a little further along, but will most likely continue to exit with a stack trace. Eventually, the Node.js code will be good enough such that it will not immediately exit with a stack trace. It is surprising how much unconverted PHP code can exist in a Node.js source file and not cause the Node.js server to immediately exit with a stack trace. As you become familiar with the conversion process, you will learn just how similar PHP and Node.js are, even such that unconverted PHP code will be parseable by the node executable and will allow the node executable to run and accept HTTP requests and fail only when it needs to actually execute some unconverted PHP code. Once the Node.js code is good enough that it does not immediately exit with a stack trace, you can begin to test the client against it. The client will usually be a browser, like Firefox or Google Chrome. Usually, when you start trying to use the client, the Node.js code will exit with a stack trace at some point, and then you will need to analyze the stack trace and apply a conversion technique to fix the problem. Over time, you will develop an ad hoc series of test cases that you can execute with the client to reveal unaddressedconversionissuesorhopefullytoconfirmthattheNode.jsserverisrunning correctly. At times, it will also help to use a visual diff tool to compare the PHP code and Node.js code; by viewing it side by side with the original PHP code, you can more easily locate issues in the new Node.js code. This will help remind you of conversion techniques that you have not used yet but need to use. It will also help you keep the conversion process on track and under control. The rest of the PHP to Node.js conversion process is simply a matter of applying a combination of previous steps many, many times until all the PHP code has been con‐ verted to Node.js code and the Node.js code works reliably and interchangeably with the PHP version. Depending on the size of the PHP codebase, the conversion process may take months, but—if you are determined—the conversion will be accomplished. 6 | Chapter 1: Node.js Basics
  • 27. Stack Traces During the conversion process, you will see a lot of stack traces. A lot. Here’s an example stack trace that is generated because the node-static npm package was not installed using the npm executable before the httpsvr.njs was run: module.js:337 throw new Error("Cannot find module '" + request + "'"); ^ Error: Cannot find module 'node-static' at Function._resolveFilename (module.js:337:11) at Function._load (module.js:279:25) at Module.require (module.js:359:17) at require (module.js:375:17) at Object.<anonymous> (httpsvr.njs:2:14) at Module._compile (module.js:446:26) at Object..js (module.js:464:10) at Module.load (module.js:353:31) at Function._load (module.js:311:12) at Array.0 (module.js:484:10) The top of the stack trace shows the code that threw the error. This is not the code that caused the error; this is the code that created and threw the error object. Below that, the error message inside the Error object is shown. This error message indicates that the node-static module could not be found. The remainder is the “call stack,” a series of function calls indicated by the word “at” that show the chain of function calls that arrived at the code that threw the error. The call stack is listed from innermost call to outermost call. In this case, the Function._resol veFilename() function is the call at the top of the call stack, which indicates that it is the innermost call and thus the one that actually contains the code that threw the error. The Function._resolveFilename() function was called by the Function._load() function, which was called by the Module.require() function, which was called by the require() function, which was called by the Object.<anonymous>() function, and so on. After each function call in the call stack, you will see the filename of the source file that contains that function, the last line that was executed (which is either the line that called the function above it or the line that actually threw the error object), and the position in the line that was last executed. In the example, you can see that two source files are involved: module.js and httpsvr.njs. The module.js file resides inside the node executable; we can guess that because we do not recognize it as one of our files. The httpsvr.njs file is part of our own source code. Even though httpsvr.njs is referenced only once and is in the middle of the call stack, it is safe to assume that the error was caused by our source code. In general, we can assume that Node.js itself, its built-in modules, and even any installed npm modules are in Stack Traces | 7
  • 28. perfect working order. Even if they are not, we must assume that they are working until we prove otherwise by eliminating all errors from our calling code. Even if we discover that the error originates elsewhere, we have control only over our own code, not over any other code. The solution would likely be to create a workaround in our own code rather than take on the long and slow process of lobbying other developers to fix their code. So, in the end, regardless of where the ultimate fault may be, the first place to focus our attention is on the httpsvr.njs file. The part of the call stack to focus our attention on is: Object.<anonymous> (httpsvr.njs:2:14) This function call is on line 2 at position 14 in the httpsvr.njs file. Here’s the httpsvr.njs file: var http = require('http'); var static = require('node-static'); var file = new static.Server(); http.createServer(function (req, res) { file.serve(req, res); }).listen(1337, '127.0.0.1'); console.log('Server running at http://127.0.0.1:1337/'); By cross-referencing the call stack with the source code, the require() function that attempts to load the node-static module is the function call in which the error occur‐ red. This is consistent with the error message: “Cannot find module ‘node-static’”. If we look up the call stack, we see the Function._load() function and the Function ._resolveFilename() function at the top. Looking at the name of these two functions, we guess that the Node.js environment is having difficulty loading the module because it cannot find the file that is associated with the module. We can guess that the module file (probably the npm package) is missing because it has not been installed yet. Again, this is consistent with the error message: “Cannot find module ‘node-static’”. The Object.<anonymous> so-called function probably indicates that the require() function call was made in the global space, instead of within a user-defined function in httpsvr.njs.Butthatisnotalwaysthecase.Ananonymousobjectmaybegeneratedinside a user-defined function. But farther down the call stack, below the Object.<anony mous> function call, we see that the caller was the Module._compile function in the module.js file. The require() function call was made in the global space. Usingallthisinformation,onesolutionistotrytoinstallthenode-static npmpackage: npm install node-static 8 | Chapter 1: Node.js Basics
  • 29. Admittedly, you won’t need to do all this analysis every time you see a Node.js call stack. But since you will be seeing many, many call stacks, you should understand how to thoroughlyanalyzeone—especiallybecausecatchingandfixingerrorsiswhattakes95% of the time in a PHP to Node.js conversion. In summary, here’s the process to analyze a call stack: read the error, look at the error message (if any), take a guess and focus on a particular function call in your own code, look at the code and find the line and perhaps even the position of the error, look up the stack to see if it indicates more detail about what the error might be, and look down the stack to see how the execution of the server got to that particular function call. Eclipse PDT Learning how to fully analyze a stack trace is one helpful skill for doing a successful PHP to Node.js conversion. A stack trace is a diagnostic tool for figuring out what is wrong with the code, like an x-ray is used by a doctor to figure out what is wrong with his patient. From a certain point of view, converting PHP to Node.js can be seen as similar to a complex surgery on a patient. You will be performing surgery on PHP and Node.js code. Like performing surgery, it takes a lot of skill and tenacity, but having a good environment can really help, too. Just like the x-ray is a tool used in the operating room, the stack trace will be a tool in the development environment for the conversion. Next, we will discuss integrated development environments, which will provide a sort of “op‐ erating room theater” for the conversion process. Since you will probably be dealing with dozens of PHP files and tens of thousands of lines of PHP and, very soon, dozens of Node.js files and tens of thousands of lines of Node.js, a simple plain text editor will probably not be good enough to keep track of everything and keep the conversion process efficient. A plain text editor will be fine when you are typing in some simple examples to learn how to program using Node.js, but when you are dealing with a large amount of PHP and Node.js code, you will need something more effective. If you were developing PHP or Node.js code by itself, you could choose a single language integrated development environment (IDE) and use it nearly straight out of the box. Eclipse PDT (PHP Development Tools) is a popular PHP IDE written in Java that is produced by the Eclipse Foundation. Some others are Zend Studio, PHPEdit, and Dreamweaver.OntheNode.jsside,therearefewerchoices,andtheyareofmoredubious popularity and effectiveness. At the time of this writing, I found Komodo Edit, nide, and Cloud9. Eclipse PDT | 9
  • 30. However, your objective is to convert PHP code to Node.js code while simultaneously improving and adding features to both codebases. To do this effectively, I recommend using the Eclipse PDT, but with some modifications to help it support Node.js code. Additional knowledge on how to easily compare PHP and Node.js code will be needed to support the conversion process. Now, before I describe how to set up Eclipse PDT for PHP to Node.js conversion, I should briefly address developers who reject such tools and insist on using simple plain text editors. They say, “I only use vi!” If you are somebody who feels this way, you are free to skip the rest of this chapter and set up your conversion environment in any way that works for you. I am describing the installation and modification of Eclipse PDT hereonlybecauseitwasanessentialtoolformetodomyownPHPtoNode.jsconversion project and it will be an essential tool for a lot of other developers as well. To install Elipse PDT, first download Java. All the Eclipse IDEs are developed in Java and need Java to run, including the Eclipse PDT. I prefer to install the Java JDK instead of the JRE. At the time of this writing, I am using jdk-6u29-windows-i586.exe. Next, browse to here. Consider using the Zend Server Community Edition (CE) instal‐ lation, which includes Eclipse PDT, the Zend Server HTTP server with built-in PHP debugging support, and even the MySQL database. I assume that your PHP web appli‐ cation uses the MySQL database or at least has the MySQL database as an option. As of this writing, there is a PDT and Zend Server Community Edition link on the Eclipse PDT downloads page. If the link does not exist or you have a different web server already running, download the latest stable Eclipse PDT version that is appropriate for your operating system. Then, skip the next few paragraphs until the text describes in‐ stalling and configuring the Eclipse PDT. Otherwise, follow the link and download the Eclipse PDT for Zend Server CE. For now, I am using zend-eclipse-php-helios-win32- x86.zip. Unzip but do not run the Eclipse PDT yet. From the same web page, download Zend Server CE itself. At this time, I am using ZendServer-CE-php-5.3.8-5.5.0-Windows_x86.exe. Install Zend Server CE. In brief, choose sensible, mostly default, selections until the Setup Type page. Select the Custom radio button on the Setup Type page, instead of the Typical radio button, and press the Next button. Check the “MySQL Server (separate download)” checkbox from the Custom Setup page. Then finish the installer. Currently, Zend Server CE shows a browser to configure the way that it operates. In our case, no special configuration is needed for the server itself. The MySQL database server is installed and configured as part of the Zend Server CE installer. By default, the root password for the MySQL database server is the empty string (a.k.a. “”). 10 | Chapter 1: Node.js Basics
  • 31. Run the Eclipse PDT. Zend Server CE is built on Apache 2 and has an htdocs folder. When the Eclipse PDT runs, find and select the htdocs folder as the Eclipse PDT Work‐ space folder. If you are using a different web server than Zend Server CE or Apache, select the document root as the Eclipse PDT Workspace folder so the PHP files that are deployed to the web server can be edited in place. It is beyond the scope of this book, but if you wish, try to experiment with using the PHP debugger on your existing PHP codebase. The Eclipse PDT and your web server will be the foundation of your “conversion de‐ velopment environment.” Now, let’s make some modifications and learn how to use the Eclipse PDT to effectively manage and implement the conversion process. The Eclipse PDT, by itself, already supports JavaScript files, and since Node.js is Java‐ Script, it supports Node.js. But because the .njs file extension is nonstandard, Eclipse PDT does not recognize a .njs file as a Node.js file. So if a .njs file (e.g., httpsvr.njs) is opened in Eclipse PDT, it is shown as plain text with no syntax coloring or popup code completion like in a regular JavaScript (.js) file. To modify Eclipse PDT to recognize .njs files as Node.js files, open the Window menu from the Eclipse PDT main menu and select the Preferences menu item. When you do this, you will see the Preferences dialog box with two inset panes (Figure 1-1). In the left pane, you will see a tree control with a hierarchically organized group of categories and subcategories of preferences. In the right pane, you will see a dialog that allows you to view and edit the preference items for the currently selected category in the left pane. In the left pane, open the General tree folder item, then select the Content Types tree item. In the right pane, you will see a list of content types. Open the Text tree folder item in the “Content types” tree control in the right pane. Beneath the Text tree folder item, select the JavaScript Source File tree item. When you select the JavaScript Source File tree item, you should see a list box with a single item, “*.js (locked)”, in the “File asso‐ ciations” list box along with an Add… button on the middle-right of the pane. Press the Add… button. Once the Add… button is pressed, the Add Content Type Association dialog box should pop up (Figure 1-2). You will type *.njs into the “Content type” edit box in that new dialog box. Then, press the OK button on all the open dialog boxes to store the modifications. When that modification is saved, JavaScript syntax coloring and code completion will work for Node.js source files that are stored as .njs files. Eclipse PDT | 11
  • 32. Figure 1-1. Eclipse PDT Preferences dialog box With syntax coloring working for .njs files, you can spot simple Node.js syntax errors by noticing that some words have the wrong color. Visual inspection is an important part of any programming project, particularly in a PHP to Node.js conversion project. Another useful visual inspection technique is comparing the PHP and Node.js code‐ bases using an advanced and very visual diff viewer to find out all kinds of things about the quality and progress of the conversion. A diff program shows the difference between two files. Simple text-based diff programs usually print out the differences as monochrome lines of text, each line from a single file. That kind of diff program is useless for analyzing a PHP to Node.js conversion. A sophisticated visual diff program is needed. Instead of showing files as alternating lines of text, the files will be shown side by side. Instead of monochrome, color will be used. Insteadofshowingonlywhichlinesaredifferent,thedifferenceswithinthelines—down to the character level—will be reconciled and shown. 12 | Chapter 1: Node.js Basics
  • 33. Figure 1-2. Eclipse PDT Add Content Type Association dialog box Eclipse PDT has an advanced visual diff viewer built in. We can use this viewer to compare a .php file to its corresponding .njs file. To use the viewer of a .php file and a .njs file, select both files. Then, right-click one of them and select the Compare With submenu and then the Each Other menu item within that submenu. Figure 1-3 shows a screenshot of the Eclipse PDT viewer comparing a simple .php file with its corre‐ sponding .njs file. Eclipse PDT | 13
  • 34. Figure 1-3. Eclipse PDT Compare view You do not need to look at the figure in detail to either understand how it works or see just how similar PHP and Node.js are. On the left is the rand.njs file. On the right is the rand.php file. The differences are in gray; the identical sequences of characters that have been matched are in white. Notice how many of the lines are almost completely in white, except for a stray dollar sign ($) in gray. Both PHP and Node.js use the keyword function in the same place and put the name of the function in the same place. Over the years, it has become common for new languages to eschew variation in syntax structure and adopt a similar syntax for things like defining functions. Also, notice that the while statement is very similar. It benefits the developer to make it easy for the visual diff to compare the .php file to its corresponding .njs file. The visual diff feature in Eclipse PDT is very good but it is not infallible. Sometimes, moving around code in either file may allow the comparison algorithm of the visual diff feature to find more matches; that is, the visual diff feature will show more white in both files. Copying a function so that it is earlier or later in a 14 | Chapter 1: Node.js Basics
  • 35. file might be irrelevant to the performance and functionality of the code, but might make the visual diff feature match up the code much more accurately. It is worth spend‐ ing some time periodically throughout the conversion process to experiment with mov‐ ing code around in each file and seeing the effect on the comparison. In Eclipse PDT, the code can be edited in a separate window, in the comparison window itself, or in both. If it is edited in a separate window and saved, any comparison windows that show the same file will be reloaded and recompared. Making some tweaks in a separate window and saving the file so that the effect on the comparison can be deter‐ mined is a common technique. Naturally, it really helps to keep the code in the same format in both files to use the same names for everything (such as functions and variables), and even to refactor the code in one or both files such that the visual diff feature will find as many matches as possible. To keep the PHP and Node.js code synchronized and simultaneously improve and add features to both codebases, you will often rely on the visual diff to make sure that the PHP and Node.js code are correct. In time, a developer will develop a finely tuned sense of what is not enough white and what is too much white. When there isn’t enough white, the visual diff feature usually is getting off track and trying to match PHP code in the .php file to Node.js code in the .njs file, which is not meant to be matched. There will be a lot of gray in the comparison, indicating differ‐ ences, and not each matches. Experimentation will often correct this issue. When there is too much white, it often means that there is some PHP code in the .njs file that has not been converted completely to Node.js code. Even though the .njs file can be parsed and run, too much white indicates that more conversion is needed. Often, eyeballing the Node.js code will indicate specific conversions that have not been done yet. One simple conversion that may be missed is that dollar signs ($) need to be added to PHP variables; dollar signs are not used on Node.js variables. Adding dollar signs to the PHP code will reduce the amount of white, bringing the comparison closer to having the right amount of white. Visualinspection,especiallyusingthevisualdifffeature,ismuchfasterthaninteractively testing the PHP and the Node.js code. Visual inspection can act as a “smoke test” to determine if the conversion is approximately correct. Automated test cases, which are beyond the scope of this book, may also be used to quickly test the effectiveness of the conversion so far. Throughout the book, there will be opportunities to convert a particular code element of a large amount of PHP code into the corresponding code element for Node.js code. For example, a PHP associative array is created by calling the PHP array() function, whereas in Node.js, it is often created by using the object literal notation, which uses curly brackets ( { and } ). When the contents of an entire .php file are copied wholesale into a .njs file at the start of the conversion of the code, the .njs file will then obviously Eclipse PDT | 15
  • 36. contain many PHP array() function calls that will need to be replaced by Node.js object literals. A simple way to address this particular conversion issue might be to simply use Eclipse PDT’s Find/Replace feature to do a global search for array( and universally replace it with a left curly bracket ( { ); see Figure 1-4. Figure 1-4. Eclipse PDT Find/Replace dialog box The operation of this dialog box is straightforward. Rather than including a screenshot of the Find/Replace dialog box every time that it is needed, this book uses a text shorthand. For the Find/Replace dialog box options in the figure, the text will have the following blurb inserted: Operation: "Find/Replace" in Eclipse PDT Find: array( Replace: { Options: Case sensitive Action: Replace All The Find/Replace dialog box can be used in two different ways. One way is to do what I call a “blind” global find-and-replace action, like the example find-and-replace blurb in Figure 1-4. I call it “blind” because it finds and replaces every occurrence in the file all at once, with no warning and no manual inspection. If all the Find/Replace dialog box values are tested and determined to be foolproof, a “blind” global find-and-replace action is fast and accurate. Unfortunately, if the result causes an error, there are only two options: undo the action or perform a new action that corrects the previous action. The second option for find-and-replace action repair work is worth pointing out. Some‐ times,itisbettertodoasimple-to-understandfind-and-replaceactionthatwillcorrectly 16 | Chapter 1: Node.js Basics
  • 37. convert 298 code elements and incorrectly convert two code elements than it is to do a complicatedfind-and-replaceactionthatcorrectlyconvertsthesame300codeelements. Manually finding and fixing a few edge cases is a worthwhile technique; not everything needs to be fully automatic. Even though PHP to Node.js conversion is a lengthy task, it is not a task that you will be running over and over. This book is not describing “continuous conversion”; it is describing conversion as a one-time event. So manually finding and fixing a few edge cases is a perfectly acceptable technique to get the job done. A second way to use the Find/Replace dialog box is to do a step-by-step global find- and-replace action. First, the Find/Replace dialog box is used to find the first instance. The developer then examines the instance and decides whether to modify the code manually (which he can do by clicking on the code and without dismissing the dialog box), or to execute the replace (by pressing the Replace/Find button), or to skip to the next instance without changing the current instance (by pressing the Find button again). Here’s the blurb for a step-by-step global find-and-replace action: Operation: "Find/Replace" in Eclipse PDT Find: array( Replace: { Options: Case sensitive Action: Find, then Replace/Find The Find/Replace dialog box in the Eclipse PDT can also use regular expressions. Reg‐ ular expressions are a pattern matching technique: instead of finding an exact phrase, aregularexpressiondescribesapatterntosearchfor.Eachtimethatthepatternisfound, the exact phrase that matches the pattern can be applied to the Replace field. For ex‐ ample, if the array((.*)) regular expression matches array(id=>'name'), the (.*) in the regular expression will save the id=>'name' text. This saved text is called a capture field, or less commonly, a capture group. In the Eclipse PDT Find/Replace dialog box, a capture field is captured by surrounding it with undelimited parentheses. To apply a capture field to the Replace field, the capture fields are enumerated according to the order that they were captured in the Find field. A dollar sign ($) indicates that a capture field is being specified, followed by the capture field number. For example, $1 in the Replace field indicates the first capture field, which, in the example earlier in this para‐ graph, would contain the id=>'name' text. Very often, there is only one capture field, so it is very common to only see $1 and rarely to see $2, $3, or beyond. Here’s a blurb for a blind global find-and-replace action using regular expressions: Operation: "Find/Replace" in Eclipse PDT Find: array((.*)) Replace: {$1} Options: Case sensitive, Regular expressions Action: Replace All Eclipse PDT | 17
  • 38. In converting PHP to Node.js, regular expressions are only tangential to the process, so this book will not be giving a primer on how to understand and write your own regular expressions. The regular expressions will be provided as part of blurbs for find-and- replace actions that can be copied to the appropriate fields of the Find/Replace dialog boxintheEclipsePDT,usuallyverbatim,withoutrequiringyoutounderstandormodify them. If you need additional help with regular expressions or need to understand the rules and how they work, you are encouraged to consult the Eclipse PDT and to use Google or a similar search engine to find websites, blogs, and forums that will answer your questions. Find-and-replace actions with regular expressions are often more comprehensive and effective than literal find-and-replace actions (i.e., actions where only one specific string is matched). A regular expression allows more variation in what it can match, and with capture fields, it can transport that variation to the Replace field. Often, a literal find- and-replace will be able to match only the beginning of a code element or the end of a codeelementatonetimebecausethecodeelementcanvaryinthemiddle.Witharegular expression, the middle can be matched to a pattern that allows the entire code element to be matched in a single find-and-replace action. When the conversion of a code ele‐ ment can be done in a single find-and-replace action, instead of multiple ones, the chances for errors are reduced. Until now, this chapter has described a range of activities and knowledge about how to set up a development environment for doing a PHP to Node.js conversion. The first thing to do was to download and install Node.js itself and become familiar with the two executables that it comes with. After that, we dug into Node.js stack traces to learn how to read them and how to use them to find what the real, underlying problem is such that the coding issue can be addressed and repaired. Then, we set up the Eclipse PDT as a foundation for a development environment, including a modification for it to under‐ stand .njs files, geared toward PHP to Node.js conversion. And finally, we learned how to use the visual diff feature and find-and-replace actions that will be very important when doing the conversion. A capable development environment is essential to efficiency and is the way that big efforts get done. Too often, amateur developers will leap into coding with an inefficient orevenanannoyingdevelopmentenvironment.Atfirst,thedevelopmentwillgoquickly in any environment because a small amount of code is simple to improve upon. But as the codebase grows larger, the complexity of the code will also grow and the pace of development will slow down. An inefficient or annoying development environment will do nothing to help the developer with the complexity, but a capable development envi‐ ronment will simplify the knowledge needed and help the developer such that the pace can be sustained and, ultimately, the project finished. WithaPHPtoNode.jsconversion,itisassumedthatalargePHPcodebasealreadyexists. At the end of the conversion, it is expected that the codebase will more than double in 18 | Chapter 1: Node.js Basics
  • 39. size: the PHP code will be refactored for conversion, not brevity, so it will increase, and of course, an entire Node.js codebase will be added. The initial PHP codebase might have been created by many developers, but in conversions, there is often so much cou‐ pling between the activities that only a single developer will do the majority of the conversion. Even though a primitive development environment might have been ac‐ ceptable for the creation of the original PHP codebase, a more sophisticated develop‐ ment environment will be needed to convert it to Node.js. If a project already has an existing development environment, it may not be necessary to adopt the Eclipse PDT. The Eclipse PDT is presented as a workable, prototypical environment suitable only for conversion activities. Alternative development environ‐ ments can work if they can support and be coupled with additional tools that support the features in this chapter. In summary, they need to be made to support the following syntax coloring for both .php and .njs files, visual side-by-side comparison between two files down to a word-by-word comparison and not just line-by-line comparison, and find-and-replace actions that support regular expressions. Now that all the infrastructure for the conversion is ready, we can move on to creating the initial .njs file that will host the new Node.js code. In the next chapter, a template for an initial .njs file will be presented such that, in subsequent chapters, PHP code can be refactored for conversion and actual PHP code can be copied into Node.js files and transformed into working Node.js code. Eclipse PDT | 19
  • 41. CHAPTER 2 A Simple Node.js Framework In the previous chapter, I presented a development environment along with a general education about how to use it to execute a conversion. In this chapter, we will start using that development environment and begin the actual conversion. An HTTP Server In PHP, a PHP file represents an HTML page. A web server, such as Apache, accepts requests and if a PHP page is requested, the web server runs the PHP. But in Node.js, the main Node.js file represents the entire web server. It does not run inside a web server like Apache; it replaces Apache. So, some bootstrap Node.js code is needed to make the web server work. The httpsvr.njs file was presented as an example in the previous chapter. Here’s the Node.js code for the httpsvr.njs file: var http = require('http'); var static = require('node-static'); var file = new static.Server(); http.createServer(function (req, res) { file.serve(req, res); }).listen(1337, '127.0.0.1'); console.log('Server running at http://127.0.0.1:1337/'); How does this work? As described in the previous chapter, the Node.js require() API function makes a module available for use. The first two lines show a built-in module and an external module: var http = require('http'); // built-in module var static = require('node-static'); // external module 21
  • 42. If you installed Node.js and followed the examples in the previous chapter, the node- static npm package, which contains the node-static external module, will already be installed. If not, install it now using the npm executable: npm install node-static The third line is a little tricky: var file = new static.Server(); The node-static module wants to provide the Node.js server with as many file serving objects as needed rather than limit the client to a single file serving object. So, instead of using the module itself as the file serving object, the file serving object is created by calling a constructor function. A constructor function is a function that is designed to be used with the new keyword. Using the new keyword and invoking the constructor function creates a new object. In this case, the module object is named static. Inside the module object, there is a key named Server, which has a value that is a constructor function. The dot (.) operator indicates this relationship such that the new operator is properly applied. A newly con‐ structed file serving object is created and stored in the file variable. The file serving object constructor can take zero or one arguments. If no arguments are provided, the file serving object uses the current directory (folder) as the HTTP server’s top-leveldocumentsdirectory.Forexample,ifthehttpsvr.njsisrunintheferrisdirectory and a web browser such as Google Chrome goes to http://127.0.0.1:1337/hello.html, the file serving object will look for the hello.html file in the ferris directory. However, if a web browser goes to http://127.0.0.1:1337/exit/goodbye.html, the file serving object will look for the goodbye.html file in the exit directory inside the ferris directory. However, when one argument is passed to the file serving object constructor, the file serving object will look for files in the directory specified in the argument. For example, if the .. string is passed as the argument, the newly created file serving object will look for files in the parent directory of the current directory. Therequire()functiontakesonlyoneargument,thenameofthemoduletoload.There is no flexibility to pass additional arguments to the module while loading. Since it is desirable to specify a directory to load files from as an argument, it is best that loading the module be completely separate from specifying where to load files from. After creating a file serving object, the HTTP server object is created to accept HTTP requests and return the file to the client, probably a web browser: http.createServer(function (req, res) { file.serve(req, res); }).listen(1337, '127.0.0.1'); 22 | Chapter 2: A Simple Node.js Framework
  • 43. This code can be rewritten as three statements to make it easier to understand: var handleReq = function(req, res) { file.serve(req, res); }; var svr = http.createServer(handleReq); svr.listen(1337, '127.0.0.1'); The first statement takes up three lines. It defines a variable named handleReq that, instead of containing a “normal” value like a string or a number, contains a function. In Node.js, functions can be assigned to variables, just like strings and numbers. When a function is assigned to a variable, the function itself is called a callback, and for our convenience, the variable that it is assigned to is called a callback variable. A callback variable is defined nearly the same as a regular function is defined, except that a callback can be unnamed and is preceded by a variable assignment. In this case, the callback expects two parameters. The first parameter, req, contains all the data related to the HTTP request. The second parameter, res, contains all the data related to the HTTP response. In this implementation, the file serving object, file, decodes the HTTP request, finds the appropriate file on the hard disk, and writes the appropriate data to the HTTP response such that the file will be returned to the browser. The node-static module was specifically written with this in mind, so that the file serving object could return hard disk files with only one line of code. The fourth line creates an HTTP server and an HTTP request handling loop that will continuously wait for new HTTP requests and use the handleReq callback variable to fulfill them: var svr = http.createServer(handleReq); Inside the createServer() function, the handleReq callback variable will be invoked: function createServer(handleReq) { while (true) { // wait for HTTP request var req = decodeHttpRequest(); // somehow decode the request var res = createHttpRequest(); // somehow create a response object handleReq(req, res); // invoke handleReq() // send HTTP response back to client based on "res" object } } Like functions (but unlike other types of variables), a callback variable can invoke the function that it contains. As you can see, invoking the handleReq callback argument is identicaltocallingastandardfunction;itjustsohappensthathandleReq isnotthename of a function but is the name of a callback variable or argument. Callback variables can be passed to functions as arguments just like other kinds of variables. Why not just hardcode the file.serve() call into the createServer() function? Isn’t serving files what a web server does? An HTTP Server | 23
  • 44. function createServer(handleReq) { while (true) { // wait for HTTP request var req = decodeHttpRequest(); // somehow decode the request var res = createHttpRequest(); // somehow create a response object file.serve(req, res); // hardcode a reference to the "node static" module // send HTTP response back to the client based on "res" object } } Yes, but passing a callback to the createServer() function is more flexible. Remember: the http module is built into Node.js and the node static module is an npm package that was installed separately. If the file.serve() call was baked into the create Server() function, using a different module instead of the node static module or adding additional custom HTTP request handling code would require copying and pastingtheentirecreateServer()functionjustsoalineinthemiddlecouldbetweaked. Instead, a callback is used. So, if you think about it, a callback is a way for some calling code to insert some of its own code into a function that it is calling. It is a way to modify the function that it is calling without having to modify the code of the function itself. The function being called, createServer() in this case, has to expect and support the callback, but if it is written with the callback in mind, a caller can create a callback that matches what is expected and the called function can use it without knowing anything aboutthecallingcode.Callbacksenabletwopiecesofcodetosuccessfullyworktogether, even though they are written by different people at different times. In this case, a callback function is passed to allow the caller to handle an HTTP request in whatever way that it sees fit. But, in many other cases, a callback function is passed as an argument so that the callback function can be invoked when an asynchronous operation has been completed. In the next chapter, this use of callback functions will be covered in detail. The fifth line uses the svr object to listen on port 1337 on the '127.0.0.1' computer, a.k.a. the “localhost” computer (meaning the computer that the Node.js server is run‐ ning on): svr.listen(1337, '127.0.0.1'); It should be pointed out that it is much more likely that the HTTP request handling loop is in the listen() function instead of the createServer() function. But for the purposes of explaining callbacks, it really does not matter. SincethesvrvariableandthehandleReqvariableareusedonlyonceandcanbereplaced with more succinct code, the three statements are combined into one: http.createServer(function(req, res) { file.serve(req, res); }).listen(1337, '127.0.0.1'); 24 | Chapter 2: A Simple Node.js Framework
  • 45. Thelaststatementofthehttpsvr.njsfilewritesamessagetotheconsolesothattheperson who started the HTTP server will know how to access it: console.log('Server running at http://127.0.0.1:1337/'); The httpsvr.njs file makes a basic Node.js HTTP server. Now we move all the files that constitute the web application from the current web server that supports PHP to the same directory that the httpsvr.njs file resides in. When the httpsvr.njs file is started, these files—which include all the HTML, CSS, client-side JavaScript, image files (e.g., PNG files), and other assorted files—will be delivered to the client, probably a web browser, and work just as they always have. The client needs to be directed only to the correctport(e.g.,1337)toloadthesefilesfromNode.jsinsteadoftheoriginalwebserver. The only reason that the web application will break is that it is still written in PHP, but since HTML, CSS, client-side JavaScript, and image files are all handled and executed exclusively on the client, they will work as much as they can until a PHP file is needed. The .php files can be moved to the Node.js server, too, but they will not work because Node.js cannot interpret .php files. The key difference between the .php files and all the other files is that the .php files are interpreted and the result of the interpretation is passed back to the client as an HTTP response. For all other files, the contents of the file are read and then written directly into the HTTP response with no interpretation. If the .php file is not interpreted, the client receives the PHP source code, which it does not know how to use. The client needs the output of the source code, not the source code itself, in order to work. A PHP to Node.js conversion boils down to writing Node.js code that will produce the exact same HTTP response in all cases that the PHP source code would have produced. It seems simple, but it is still a ton of work. To start with, a Node.js local module will be created for each .php file. For the purposes of this book, a local module is a local .njs file that can be loaded by the main .njs file (i.e., httpsvr.njs) using the Node.js require() API function. To create a Node.js local module for a .php file, we will create an empty .njs file in the same directory as the .php file using the same filename but a different extension. For example, for admin/ index.php, create an empty admin/index.njs file. For your own conversion effort, you will have to use your own judgment. In some cases, there may be so many .php files that creating the corresponding .njs files will be a lot of busywork, so it may be better just to do a few at first. In other cases, there may be only a few .php files, so it may make sense to create all the empty files at once. Once there is a corresponding .njs file for some .php files, choose one of the .php files and edit its corresponding .njs file: exports.serve = function(req, res) { res.writeHead(200, {'Content-Type': 'text/plain'}); res.end('admin/index.njs'); }; An HTTP Server | 25
  • 46. Type this Node.js code into the .njs file and change the res.end() function call imple‐ mentation to indicate the correct path (in this case, admin/index.njs) to the particu‐ lar .njs file that is being edited. With this simple code, the .njs file is now a Node.js module with an exports.serve() stub function implementation. A stub implementation is a placeholder with some very simple code that will be replaced with a serious implementation later. The exports .serve() stub function takes two parameters that correspond to the parameters to the callback function that is passed to the http.createServer() function in httpsvr.njs. The req parameter is the HTTP request object and the res parameter is the HTTP response object. When the exports.serve() function is invoked, the stub implementation will return an HTTP response with a Content-Type HTTP header set to “text/plain” with the con‐ tent containing the path name of the file that is being invoked. By customizing the stub implementation, it will be a little easier to debug later, in case a coding error leads to an HTTP request for one file accidentally ending up in another file. Once you have a .njs file with a exports.serve() stub function, you should modify the httpsvr.njsfiletoinvoketheexports.serve() functionattheappropriatetime.Butfirst, the module with the exports.serve() function must be made accessible to the httpsvr.njs file. The Node.js require() API function can load local modules as well as built-in module and npm packages: var admin_index = require('./admin/index.njs'); To load a local module, the argument must contain a path name with the ./ string at the beginning. The ./ indicates that this is a path name to a local module and not a name of a built-in module or npm package. In this example, the local module is assigned to the admin_index variable. Depending on the number of .php files that are in a web application (and, thus, the number of corresponding .njs files), it may be convenient and less confusing to use a variation of thepathnameasthenameofthevariablethatholdsthemodulethatcontainstheNode.js code that implements the functionality of a particular .php file. To route an HTTP request for a particular PHP page to the correct Node.js local module, the callback passed to the http.createServer() function needs to be modified to identify and pass the HTTP request to the appropriate Node.js local module. Using a simple comparison in an if statement, the HTTP request is examined to see if it is requesting a particular .php file and if it is, it is routed to the appropriate Node.js local module instead of passing it to the node static npm package (i.e., the file variable): http.createServer(function (req, res) { if (url.parse(req.url).pathname == '/admin/index.php') { admin_index.serve(req, res); 26 | Chapter 2: A Simple Node.js Framework
  • 47. } else { file.serve(req, res); } }).listen(1337, '127.0.0.1'); This source code uses the http and url Node.js built-in modules to implement a simple but functional Node.js server. If desired, more sophisticated optional packages, such as the express Node.js npm package, could be installed and used instead. For this implementation, the url built-in module is needed to parse the URL in the HTTP request. The following Node.js code uses the require() API function to access the url built-in module: var url = require('url'); Here we continue with our previous example. If the HTTP request is requesting the /admin/index.php URL resource, the admin_index local module interprets the re‐ quest and provides the HTTP response instead of allowing the file variable to read the file and return it as a static page. You may notice that the URL in the HTTP request is tested against /admin/index.php insteadof/admin/index.njs.Sincethebrowserhandlesrequestsfor.phpfileswithcustom code, it can handle .php file requests in any way that it likes, including ignoring the .php file on the hard disk and running Node.js code instead. An HTTP request is a request, not a demand, and the Node.js server can force .php file requests to be handled without using PHP at all. In Node.js, the .php file reference becomes a unique identifier to perform a particular action; the .php extension no longer means PHP. The if-else statements in the callback now just match a unique path with a specific piece of Node.js code. The semantic meaning of the .php extension is ignored. Most likely, the client is requesting .php files either because the user clicked on a link in an HTML page that had the .php file as a hyperlink reference or because it is part of a JavaScript AJAX call. To change this, the HTML and client-side JavaScript files would need to be modified to refer to .njs files instead of .php files. Whether or not you make these changes depends on your situation. On the plus side, removing the .php file ref‐ erences eliminates confusion by other developers accessing your code, who may wonder why there are PHP references in a Node.js codebase. On the minus side, removing the .php file references creates technically unnecessary differences between the PHP and Node.js codebases. Having walked through a step-by-step explanation with a single .php file, let’s put it all together and do the same thing with multiple .php files: var http = require('http'); var static = require('node-static'); var file = new static.Server(); var url = require('url'); var index = require('./index.njs'); var login = require('./login.njs'); An HTTP Server | 27
  • 48. var admin_index = require('./admin/index.njs'); var admin_login = require('./admin/login.njs'); http.createServer(function (req, res) { if (url.parse(req.url).pathname == '/index.php') { index.serve(req, res); } else if (url.parse(req.url).pathname == '/login.php') { login.serve(req, res); } else if (url.parse(req.url).pathname == '/admin/index.php') { admin_index.serve(req, res); } else if (url.parse(req.url).pathname == '/admin/login.php') { admin_login.serve(req, res); } else { file.serve(req, res); } }).listen(1337, '127.0.0.1'); console.log('Server running at http://127.0.0.1:1337/'); There are essentially two modifications: (1) a set of require() function calls have been added with one require() function call per .php file; and (2) a set of else-if statements have been added with one else-if statement per .php file. In this case, this modified version of httpsvr.njs is handling four .php files: /index.php, /login.php, /admin/ index.php, and /admin/login.php. For any web application that is converted from PHP to Node.js, each .php file in the application will cause both a new require() function call and an else-if statement to be added so that the httpsvr.njs file can properly route an HTTP request for a .php file to a specific and separate .njs file that implements the PHP code in Node.js instead. As described earlier, a corresponding .njs file will be created for each .php file. For the example with four .php files, there will be four .njs files: index.njs, login.njs, admin/ index.njs, and admin/login.njs. The files will have the same code except for a slight modification to indicate the path name of the file: exports.serve = function(req, res) { res.writeHead(200, {'Content-Type': 'text/plain'}); res.end('admin/index.njs'); }; The exports.serve() function in each .njs file will ultimately contain the Node.js code that exactly and completely emulates the operation of its corresponding .php file. The stub implementation that just returns the name of the .njs file as an HTTP response will be erased. The PHP code from the corresponding .php file will be copied and pasted into the exports.serve() function, and through a series of transformations and con‐ version recipes, the PHP code will become Node.js code that does the exact same thing. But we are not quite ready to do that yet. 28 | Chapter 2: A Simple Node.js Framework
  • 49. Predefined PHP Variables When a web server that supports PHP executes a PHP page, it does not supply a raw HTTP request to the PHP page and then execute the page. If it did that, every PHP page would have to add a lot of code to decode the raw HTTP request and put the values in a more convenient format. Instead, the PHP engine decodes the raw HTTP request itself and populates a bunch of well-known PHP global variables with the equivalent data. PHP pages rely on these global variables to be correctly populated in order to work. Since the basic approach is to copy the PHP page into the local module and transform it to Node.js code, we will need to implement these global variables in Node.js in order for the converted page to work. By analyzing the PHP page, we can determine which global variables it relies upon. It is not necessary to implement every single global vari‐ able that the PHP engine makes available. Instead, we can just implement the ones that are used. Five PHP predefined global variables that are commonly used are $_GET, $_POST, $_COOKIE, $_REQUEST, and $_SESSION. An HTTP request is sent with an HTTP action, also called a method or a verb. An HTTP GET action is very simple: the client is asking the server to retrieve a page. When a user types a URL into the address bar of a browser, he is typing in an HTTP GET request. The HTTP GET request may have some arguments in the form of name/value pairs. These arguments are often called query arguments or the query string. A user can add these arguments manually to a browser’s address bar by putting a question mark (?) at the end of the URL and typing name/value pairs separated by ampersands (&). The name/value pairs themselves separate the name from the value by using an equals sign (=). Here’s an example: http://localhost:1337/index.php?theme=green&tab=users&fastload=true The name/value pairs in the example are: theme=green, tab=users, and fast load=true. When a PHP page gets an HTTP GET request like this one, the PHP engine extracts the name/value pairs from the raw HTTP GET request and puts them in the predefined PHP $_GET array. The name becomes the key or index into the $_GET array, and the value becomes, well, the value. For the previous example URL, the $_GET array looks like this: $_GET['theme'] = 'green'; $_GET['tab'] = 'users'; $_GET['fastload'] = 'true'; Predefined PHP Variables | 29
  • 50. WhenaPHPpageisconvertedtoNode.jscode,theNode.jsstillexpectsthesepredefined arrays to exist and to be correctly populated. The following code shows a Node.js init GET() function, which can be used in any local module that contains a converted PHP page to create and populate a Node.js _GET variable that works very similarly to the PHP $_GET variable: function initGET(req, pre, cb) { pre._GET = {}; var urlparts = req.url.split('?'); if (urlparts.length >= 2) { var query = urlparts[urlparts.length-1].split('&'); for (var p=0; p < query.length; ++p) { var pair = query[p].split('='); pre._GET[pair[0]] = pair[1]; } } cb(); } The Node.js initGET() function takes three arguments: req, pre, and cb. The req argument contains the raw HTTP request. The pre argument is a Node.js object that contains all the predefined global variables, which are made available by the PHP engine to PHP pages. Instead of juggling a bunch of different variables, all predefined variables are stored in a pre variable that can be easily passed around. And finally, the cb contains a callback function to be invoked when the initGET() function is finished. Since the initGET() functiondoesonlysimplememorymanipulationandrequiresnooperations that have callback functions, a callback function is not technically necessary. However, since the initPOST() function that will be implemented later will need a cb() callback function parameter, it is best that the initGET() and the initPOST() functions work as similarly as possible. ThefirstlineoftheNode.jsinitGET()functioncreatesa_GETarrayinthepreargument. The pre._GET object will be the Node.js equivalent of the PHP $_GET array. Next, the query arguments will be separated from the main URL, which is found in the req.url property. Populating the Node.js pre._GET variable is as easy as using split() function callstoseparateouttheURLqueryargumentsfromeachothertodeterminetheirname/ valuepairs.Finally,thecbargumentisinvokedtoletthecallbackknowthatthepre._GET variable is ready for use. To initialize the Node.js pre._GET variable, a modification will need to be made to the Node.js exports.serve() function. Here’s the original exports.serve() function: exports.serve = function(req, res) { res.writeHead(200, {'Content-Type': 'text/plain'}); res.end('admin/index.njs'); }; 30 | Chapter 2: A Simple Node.js Framework
  • 51. Instead of implementing the actual page in the exports.serve() function, a new func‐ tion called page() will actually implement the page and the exports.serve() function will be reserved for the initialization and other work that the PHP engine would provide to a PHP page: function page(req, res, pre, cb) { res.writeHead(200, {'Content-Type': 'text/plain'}); res.end('admin/index.njs'); cb(); } The page() function takes four arguments: req, res, pre, and cb. The req and res arguments are the HTTP request and the HTTP response, respectively. The pre argu‐ ment is the predefined variables, including the _GET property that contains the query arguments. The cb argument is a callback function that will let the exports.serve() function know when the page has been completely handled. For debugging purposes, it can be helpful to print out the pre object. By using the require() function to load the built-in module called util and then adding a util .inspect()functioncalltotheres.end()functioncall,thecontentsoftheprevariable, including the contents of its _GET property, will be shown in the HTTP response: var util = require('util'); function page(req, res, pre, cb) { res.writeHead(200, {'Content-Type': 'text/plain'}); res.end('admin/index.njsn'+util.inspect(pre)); cb(); } Now that handling the page has moved to the page() function, the exports.serve() function will be changed to do an initialization, including calling the initGET() function: exports.serve = function(req, res) { var pre = {}; initGET(req, pre, function() { page(req, res, pre, function() { }); }); }; The pre variable is created first. Then the initGET() function is called, and when it is finished, the page() function is called. There is no finalization or cleanup after the page() function, so its callback is empty. Now that the _GET property is implemented, the page() function can be changed to use query arguments. The page() function can be modified to expect an x query argument and respond appropriately: Predefined PHP Variables | 31
  • 52. function page(req, res, pre, cb) { res.writeHead(200, {'Content-Type': 'text/plain'}); if (pre._GET['x']) { res.end('The value of x is '+pre._GET['x']+'.'); } else { res.end('There is no value for x.'); } cb(); } If the Node.js server is run and a browser is directed to http://localhost:1337/index.php? x=4, the browser page will show “The value of x is 4.” The HTTP POST request is similar to the HTTP GET request, except that the name/ value pairs are delivered in the body of the request instead of as a query string at the end of the URL. An HTTP request consists of HTTP headers and an HTTP body. The URL, which includes the query string, is specified as one of the HTTP headers. HTTP headers are meant to be short and limited in length. In particular, URLs, including the query string, are limited in length; extremely long URLs are not recommended. Instead, if a lot of data must be included in an HTTP request, it is recommended that the data be sent in the HTTP body as part of an HTTP POST. Unlike HTTP headers, which are limited in length, HTTP bodies can handle very large amounts of data. For an HTTP POST, the HTTP body is often referred to as the POST data. Since the body of an HTTP POST can be very large, the POST data is not delivered all at once; it is delivered as it becomes available using events. Events are a way for Node.js to indicate that something happened. For example, a data event indicates that the next chunk of data has been read from the HTTP body. If there is a lot of data, Node.js may trigger several data events as each chunk of data is read. An event can be associated with a callback function, also called an event handler. The event handler will execute some code each time that an event occurs. The on() function associates an event with an event handler. The following example shows the on function being used to associate the data event with an event handler that writes the provided data to the console: req.on('data', function(chunk) { console.log(chunk); }); For the initPOST() function, the _POST property of the pre variable is initialized. As you will recall from the initGET() function, the pre argument is a Node.js object that contains all the predefined global variables, which are made available by the PHP engine to PHP pages. A body variable is created to hold the HTTP body as it is read. The on() function associates an event handler with the data event, which will add the data to the body variable as it becomes available: 32 | Chapter 2: A Simple Node.js Framework
  • 53. pre._POST = {}; var body = ''; req.on('data', function(chunk) { body += chunk; if (body.length > 1e6) { req.connection.destroy(); } }); An if statement is added to the data event handler to detect if the HTTP body is too long, and will terminate the connection if so. Poorly written or malicious clients might send an endless amount of data, and this if statement protects the Node.js server by arbitrarily giving up on an HTTP request if it has already sent a very large amount of data. Finally, the on() function associates an event handler with the end event, which occurs when the entire HTTP body has been read. Using simple split() function calls, the end event handler pulls apart the data and puts it into the pre._POST variable: req.on('end', function() { var pairs = body.split('&'); for (var p=0; p < pairs.length; ++p) { var pair = pairs[p].split('='); pre._POST[pair[0]] = pair[1]; } cb(); }); The cb() callback is invoked at the very end to indicate that the pre._POST variable has been fully populated and is ready for use. Putting the code all together, the initPOST() function is shown here in its entirety: function initPOST(req, pre, cb) { pre._POST = {}; var body = ''; req.on('data', function(chunk) { body += chunk; if (body.length > 1e6) { req.connection.destroy(); } }); req.on('end', function() { var pairs = body.split('&'); for (var p=0; p < pairs.length; ++p) { var pair = pairs[p].split('='); pre._POST[pair[0]] = pair[1]; } cb(); }); } Predefined PHP Variables | 33
  • 54. Just like pages that expect HTTP GET requests, the exports.serve() function must be updated. For pages that expect HTTP POST requests, the code in the exports .serve()functionisalmostidentical,exceptthattheinitGET()functioncallisreplaced withaninitPOST()functioncall.Thisisbydesign.EventhoughtheinitGET()function does not need a callback, giving it a callback allows the initGET() function and the initPOST() function to take the same parameters and has almost the same code: exports.serve = function(req, res) { var pre = {}; initPOST(req, pre, function() { page(req, res, pre, function() { }); }); }; By far, HTTP GET and HTTP POST are the most commonly used HTTP actions, and in most cases, will be the only HTTP actions that a web application needs. There are other HTTP actions, such as HTTP PUT, HTTP DELETE, and HTTP HEAD, but the PHPenginedoesnotprovidethesamesupportforthem,soaNode.jsconversionusually does not need to provide support for them either. A cookie is a name/value pair that is sent to a client (usually a browser) by the server; the client stores the cookie and adds it as an HTTP header to every subsequent HTTP request that the client sends to the server. The client usually stores cookies in a perma‐ nent place, such as a hard disk, so that future HTTP requests can include the cookie, even if the client is shutdown and restarted later. A cookie is a small piece of data that the server gives the client so that it can identify the client later, and automatically log the client into his account, for example. An HTTP header for a cookie has the unsurprising name of “Cookie”: Cookie: user=admin;sessid=21EC33203BEA1169A2EA08332B313090 In a Node.js HTTP request, the cookies are stored in the headers.cookie property of the HTTP request, usually called req in the code examples in this book. The initCOOKIE() function for extracting the cookies from the appropriate HTTP header and putting it into the pre._COOKIE variable is very similar to the initGET() function. Getting the cookies is even more convenient: the cookies are in their own property, not tacked on to the end of a URL as a query string. The pre._COOKIE variable will be a stand-in for the PHP $_COOKIE variable that is generated by the PHP engine for PHP pages: function initCOOKIE(req, pre, cb) { pre._COOKIE = {}; if (req.headers.cookie) { var cookies = req.headers.cookie.split(';'); for (var c=0; c < cookies.length; ++c) { var pair = cookies[c].split('='); 34 | Chapter 2: A Simple Node.js Framework
  • 55. Another Random Scribd Document with Unrelated Content
  • 56. was ruining the man. So when Rose looked up at him, with a very honest desire to save him as well as herself from being swept into the giddy vortex which keeps so many young people revolving aimlessly, till they go down or are cast upon the shore wrecks of what they might have been, he gave a shrug and answered briefly,— "As you please. I'll bring you home as early as you like, and Effie Waring can take your place in the German. What flowers shall I send you?" Now, that was an artful speech of Charlie's; for Miss Waring was a fast and fashionable damsel, who openly admired Prince Charming, and had given him the name. Rose disliked her, and was sure her influence was bad; for youth made frivolity forgivable, wit hid want of refinement, and beauty always covers a multitude of sins in a man's eyes. At the sound of Effie's name, Rose wavered, and would have yielded but for the memory of the "first mate's" last words. She did desire to "keep a straight course;" so, though the current of impulse set strongly in a southerly direction, principle, the only compass worth having, pointed due north, and she tried to obey it like a wise young navigator, saying steadily, while she directed to Annabel the parcel containing a capacious pair of slippers intended for Uncle Mac,— "Don't trouble yourself about me. I can go with uncle, and slip away without disturbing anybody." "I don't believe you'll have the heart to do it," said Charlie, incredulously, as he sealed the last note. "Wait and see." "I will, but shall hope to the last," and, kissing his hand to her, he departed to post her letters, quite sure that Miss Waring would not lead the German. It certainly looked for a moment as if Miss Campbell would, because she ran to the door with the words "I'll go" upon her lips. But she
  • 57. did not open it till she had stood a minute staring hard at the old glove on Psyche's head; then, like one who had suddenly got a bright idea, she gave a decided nod and walked slowly out of the room.
  • 58. CHAPTER VI. POLISHING MAC. "Please could I say one word?" was the question three times repeated before a rough head bobbed out from the grotto of books in which Mac usually sat when he studied. "Did any one speak?" he asked, blinking in the flood of sunshine that entered with Rose. "Only three times, thank you. Don't disturb yourself, I beg; for I merely want to say a word," answered Rose, as she prevented him from offering the easy-chair in which he sat. "I was rather deep in a compound fracture, and didn't hear. What can I do for you, cousin?" and Mac shoved a stack of pamphlets off the chair near him, with a hospitable wave of the hand that sent his papers flying in all directions. Rose sat down, but did not seem to find her "word" an easy one to utter; for she twisted her handkerchief about her fingers in embarrassed silence, till Mac put on his glasses, and, after a keen look, asked soberly,— "Is it a splinter, a cut, or a whitlow, ma'am?" "It is neither; do forget your tiresome surgery for a minute, and be the kindest cousin that ever was," answered Rose, beginning rather sharply and ending with her most engaging smile. "Can't promise in the dark," said the wary youth. "It is a favor, a great favor, and one I don't choose to ask any of the other boys," answered the artful damsel.
  • 59. Mac looked pleased, and leaned forward, saying more affably,— "Name it, and be sure I'll grant it if I can." "Go with me to Mrs. Hope's party to-morrow night." "What!" and Mac recoiled as if she had put a pistol to his head. "I've left you in peace a long time: but it is your turn now; so do your duty like a man and a cousin." "But I never go to parties!" cried the unhappy victim in great dismay. "High time you began, sir." "But I don't dance fit to be seen." "I'll teach you." "My dress-coat isn't decent, I know." "Archie will lend you one: he isn't going." "I'm afraid there's a lecture that I ought not to cut." "No, there isn't: I asked uncle." "I'm always so tired and dull in the evening." "This sort of thing is just what you want to rest and freshen up your spirits." Mac gave a groan and fell back vanquished; for it was evident that escape was impossible. "What put such a perfectly wild idea into your head?" he demanded, rather roughly; for hitherto he had been "left in peace," and this sudden attack decidedly amazed him. "Sheer necessity; but don't do it if it is so very dreadful to you. I must go to several more parties, because they are made for me; but after that I'll refuse, and then no one need be troubled with me."
  • 60. Something in Rose's voice made Mac answer penitently, even while he knit his brows in perplexity,— "I didn't mean to be rude; and of course I'll go anywhere if I'm really needed. But I don't understand where the sudden necessity is, with three other fellows at command, all better dancers and beaux than I am." "I don't want them, and I do want you; for I haven't the heart to drag uncle out any more, and you know I never go with any gentleman but those of my own family." "Now look here, Rose: if Steve has been doing any thing to tease you just mention it, and I'll attend to him," cried Mac, plainly seeing that something was amiss, and fancying that Dandy was at the bottom of it, as he had done escort duty several times lately. "No, Steve has been very good: but I know he had rather be with Kitty Van; so of course I feel like a marplot, though he is too polite to hint it." "What a noodle that boy is! But there's Archie: he's as steady as a church, and has no sweetheart to interfere," continued Mac, bound to get at the truth, and half suspecting what it was. "He is on his feet all day, and Aunt Jessie wants him in the evening. He does not care for dancing as he used, and I suppose he really does prefer to rest and read." Rose might have added, "and hear Phebe sing;" for Phebe did not go out as much as Rose did, and Aunt Jessie often came in to sit with the old lady when the young folks were away; and, of course, dutiful Archie came with her; so willingly of late! "What's amiss with Charlie? I thought he was the prince of cavaliers. Annabel says he dances 'like an angel,' and I know a dozen mothers couldn't keep him at home of an evening. Have you had a tiff with Adonis, and so fall back on poor me?" asked Mac, coming last to the
  • 61. person of whom he thought first, but did not mention, feeling shy about alluding to a subject often discussed behind her back. "Yes, we have; and I don't intend to go with him any more for some time. His ways do not suit me, and mine do not suit him; so I want to be quite independent, and you can help me if you will," said Rose, rather nervously spinning the big globe close by. Mac gave a low whistle, looking wide awake all in a minute, as he said with a gesture, as if he brushed a cobweb off his face,— "Now, see here, cousin: I'm not good at mysteries, and shall only blunder if you put me blindfold into any nice manœuvre. Just tell me straight out what you want, and I'll do it if I can. Play I'm uncle, and free your mind; come now." He spoke so kindly, and the honest eyes were so full of merry good- will, that Rose felt she might confide in him, and answered as frankly as he could desire,— "You are right, Mac; and I don't mind talking to you almost as freely as to uncle, because you are such a reliable fellow, and won't think me silly for trying to do what I believe to be right. Charlie does, and so makes it hard for me to hold to my resolutions. I want to keep early hours, dress simply, and behave properly; no matter what fashionable people do. You will agree to that, I'm sure; and stand by me through thick and thin for principle's sake." "I will; and begin by showing you that I understand the case. I don't wonder you are not pleased; for Charlie is too presuming, and you do need some one to help you head him off a bit. Hey, cousin?" "What a way to put it!" and Rose laughed in spite of herself, adding with an air of relief, "That is it; and I do want some one to help me make him understand that I don't choose to be taken possession of in that lordly way, as if I belonged to him more than to the rest of the family. I don't like it; for people begin to talk, and Charlie won't see how disagreeable it is to me."
  • 62. "Tell him so," was Mac's blunt advice. "I have; but he only laughs and promises to behave, and then he does it again, when I am so placed that I can't say any thing. You will never understand, and I cannot explain; for it is only a look, or a word, or some little thing: but I won't have it, and the best way to cure him is to put it out of his power to annoy me so." "He is a great flirt, and wants to teach you how, I suppose. I'll speak to him if you like, and tell him you don't want to learn. Shall I?" asked Mac, finding the case rather an interesting one. "No, thank you: that would only make trouble. If you will kindly play escort a few times, it will show Charlie that I am in earnest without more words, and put a stop to the gossip," said Rose, coloring like a poppy at the recollection of what she heard one young man whisper to another, as Charlie led her through a crowded supper-room with his most devoted air, "Lucky dog! he is sure to get the heiress, and we are nowhere." "There's no danger of people's gossiping about us, is there?" and Mac looked up, with the oddest of all his odd expressions. "Of course not: you're only a boy." "I'm twenty-one, thank you; and Prince is but a couple of years older," said Mac, promptly resenting the slight put upon his manhood. "Yes; but he is like other young men, while you are a dear old bookworm. No one would ever mind what you did; so you may go to parties with me every night, and not a word would be said; or, if there was, I shouldn't mind since it is 'only Mac,'" answered Rose, smiling as she quoted a household word often used to excuse his vagaries. "Then I am nobody?" lifting his brows, as if the discovery surprised and rather nettled him.
  • 63. "Nobody in society as yet; but my very best cousin in private, and I've just proved my regard by making you my confidant, and choosing you for my knight," said Rose, hastening to soothe the feelings her careless words seemed to have ruffled slightly. "Much good that is likely to do me," grumbled Mac. "You ungrateful boy, not to appreciate the honor I've conferred upon you! I know a dozen who would be proud of the place: but you only care for compound fractures; so I won't detain you any longer, except to ask if I may consider myself provided with an escort for to- morrow night?" said Rose, a trifle hurt at his indifference; for she was not used to refusals. "If I may hope for the honor," and, rising, he made her a bow which was such a capital imitation of Charlie's grand manner that she forgave him at once, exclaiming with amused surprise,— "Why, Mac! I didn't know you could be so elegant!" "A fellow can be almost any thing he likes, if he tries hard enough," he answered, standing very straight, and looking so tall and dignified that Rose was quite impressed, and with a stately courtesy she retired, saying graciously,— "I accept with thanks. Good-morning, Doctor Alexander Mackenzie Campbell." When Friday evening came, and word was sent up that her escort had arrived, Rose ran down, devoutly hoping that he had not come in a velveteen jacket, top-boots, black gloves, or made any trifling mistake of that sort. A young gentleman was standing before the long mirror, apparently intent on the arrangement of his hair; and Rose paused suddenly as her eye went from the glossy broadcloth to the white-gloved hands, busy with an unruly lock that would not stay in place. "Why, Charlie, I thought—" she began with an accent of surprise in her voice, but got no further; for the gentleman turned and she
  • 64. beheld Mac in immaculate evening costume, with his hair parted sweetly on his brow, a superior posy at his button-hole, and the expression of a martyr upon his face. "Ah, don't you wish it was? No one but yourself to thank that it isn't he. Am I right? Dandy got me up, and he ought to know what is what," demanded Mac, folding his hands and standing as stiff as a ramrod. "You are so regularly splendid that I don't know you." "Neither do I." "I really had no idea you could look so like a gentleman," added Rose, surveying him with great approval. "Nor I that I could feel so like a fool." "Poor boy! he does look rather miserable. What can I do to cheer him up, in return for the sacrifice he is making?" "Stop calling me a boy. It will soothe my agony immensely, and give me courage to appear in a low-necked coat and a curl on my forehead; for I'm not used to such elegancies, and find them no end of a trial." Mac spoke in such a pathetic tone, and gave such a gloomy glare at the aforesaid curl, that Rose laughed in his face, and added to his woe by handing him her cloak. He surveyed it gravely for a minute, then carefully put it on wrong side out, and gave the swan's-down hood a good pull over her head, to the utter destruction of all smoothness to the curls inside. Rose uttered a cry and cast off the cloak, bidding him learn to do it properly, which he meekly did, and then led her down the hall without walking on her skirts more than three times by the way. But at the door she discovered that she had forgotten her furred overshoes, and bade Mac get them.
  • 65. "Never mind: it's not wet," he said, pulling his cap over his eyes and plunging into his coat, regardless of the "elegancies" that afflicted him. "But I can't walk on cold stones with thin slippers, can I?" began Rose, showing a little white foot. "You needn't, for—there you are, my lady;" and, unceremoniously picking her up, Mac landed her in the carriage before she could say a word. "What an escort!" she exclaimed in comic dismay, as she rescued her delicate dress from the rug in which he was about to tuck her up like a mummy. "It's 'only Mac,' so don't mind," and he cast himself into an opposite corner, with the air of a man who had nerved himself to the accomplishment of many painful duties, and was bound to do them or die. "But gentlemen don't catch up ladies like bags of meal, and poke them into carriages in this way. It is evident that you need looking after, and it is high time I undertook your society manners. Now, do mind what you are about, and don't get yourself or me into a scrape if you can help it," besought Rose, feeling that on many accounts she had gone farther and fared worse. "I'll behave like a Turveydrop: see if I don't." Mac's idea of the immortal Turveydrop's behavior seemed to be a peculiar one; for, after dancing once with his cousin, he left her to her own devices, and soon forgot all about her in a long conversation with Professor Stumph, the learned geologist. Rose did not care; for one dance proved to her that that branch of Mac's education had been sadly neglected, and she was glad to glide smoothly about with Steve, though he was only an inch or two taller than herself. She had plenty of partners, however, and plenty of
  • 66. chaperons; for all the young men were her most devoted, and all the matrons beamed upon her with maternal benignity. Charlie was not there; for when he found that Rose stood firm, and had moreover engaged Mac as a permanency, he would not go at all, and retired in high dudgeon to console himself with more dangerous pastimes. Rose feared it would be so; and, even in the midst of the gayety about her, an anxious mood came over her now and then, and made her thoughtful for a moment. She felt her power, and wanted to use it wisely; but did not know how to be kind to Charlie without being untrue to herself and giving him false hopes. "I wish we were all children again, with no hearts to perplex us and no great temptations to try us," she said to herself, as she rested a moment in a quiet nook while her partner went to get a glass of water. Right in the midst of this half-sad, half-sentimental reverie, she heard a familiar voice behind her say earnestly,— "And allophite is the new hydrous silicate of alumina and magnesia, much resembling pseudophite, which Websky found in Silesia." "What is Mac talking about!" she thought: and, peeping behind a great azalea in full bloom, she saw her cousin in deep converse with the professor, evidently having a capital time; for his face had lost its melancholy expression and was all alive with interest, while the elder man was listening as if his remarks were both intelligent and agreeable. "What is it?" asked Steve, coming up with the water, and seeing a smile on Rose's face. She pointed out the scientific tête-à-tête going on behind the azalea, and Steve grinned as he peeped, then grew sober and said in a tone of despair,— "If you had seen the pains I took with that fellow, the patience with which I brushed his wig, the time I spent trying to convince him that
  • 67. he must wear thin boots, and the fight I had to get him into that coat; you'd understand my feelings when I see him now." "Why, what is the matter with him?" asked Rose. "Will you take a look, and see what a spectacle he has made of himself. He'd better be sent home at once, or he will disgrace the family by looking as if he'd been in a row." Steve spoke in such a tragic tone that Rose took another peep and did sympathize with Dandy; for Mac's elegance was quite gone. His tie was under one ear, his posy hung upside down, his gloves were rolled into a ball, which he absently squeezed and pounded as he talked, and his hair looked as if a whirlwind had passed over it; for his ten fingers set it on end now and then, as they had a habit of doing when he studied or talked earnestly. But he looked so happy and wide awake, in spite of his dishevelment, that Rose gave an approving nod, and said behind her fan,— "It is a trying spectacle, Steve: yet, on the whole, I think his own odd ways suit him best; and I fancy we shall yet be proud of him, for he knows more than all the rest of us put together. Hear that now," and Rose paused, that they might listen to the following burst of eloquence from Mac's lips:— "You know Frenzel has shown that the globular forms of silicate of bismuth at Schneeburg and Johanngeorgenstadt are not isometric, but monoclinic in crystalline form; and consequently he separates them from the old eulytite, and gives them the new name Agricolite." "Isn't it awful? Let us get out of this before there's another avalanche, or we shall be globular silicates and isometric crystals in spite of ourselves," whispered Steve with a panic-stricken air; and they fled from the hail-storm of hard words that rattled about their ears, leaving Mac to enjoy himself in his own way.
  • 68. But when Rose was ready to go home, and looked about for her escort, he was nowhere to be seen; for the professor had departed, and Mac with him, so absorbed in some new topic that he entirely forgot his cousin, and went placidly home, still pondering on the charms of geology. When this pleasing fact dawned upon Rose, her feelings may be imagined. She was both angry and amused: it was so like Mac to go mooning off and leave her to her fate. Not a hard one, however; for, though Steve was gone with Kitty before her flight was discovered, Mrs. Bliss was only too glad to take the deserted damsel under her wing, and bear her safely home. Rose was warming her feet, and sipping the chocolate which Phebe always had ready for her, as she never ate suppers; when a hurried tap came at the long window whence the light streamed, and Mac's voice was heard softly asking to be let in "just for one minute." Curious to know what had befallen him, Rose bade Phebe obey his call; and the delinquent cavalier appeared, breathless, anxious, and more dilapidated than ever: for he had forgotten his overcoat; his tie was at the back of his neck now; and his hair as rampantly erect as if all the winds of heaven had been blowing freely through it, as they had; for he had been tearing to and fro the last half-hour trying to undo the dreadful deed he had so innocently committed. "Don't take any notice of me; for I don't deserve it: I only came to see that you were safe, cousin, and then go hang myself, as Steve advised," he began, in a remorseful tone, that would have been very effective, if he had not been obliged to catch his breath with a comical gasp now and then. "I never thought you would be the one to desert me," said Rose, with a reproachful look; thinking it best not to relent too soon, though she was quite ready to do it when she saw how sincerely distressed he was. "It was that confounded man! He was a regular walking encyclopædia; and, finding I could get a good deal out of him, I
  • 69. went in for general information, as the time was short. You know I always forget every thing else when I get hold of such a fellow." "That is evident. I wonder how you came to remember me at all," answered Rose, on the brink of a laugh: it was so absurd. "I didn't till Steve said something that reminded me: then it burst upon me, in one awful shock, that I'd gone and left you; and you might have knocked me down with a feather," said honest Mac, hiding none of his iniquity. "What did you do then?" "Do! I went off like a shot, and never stopped till I reached the Hopes"— "You didn't walk all that way?" cried Rose. "Bless you, no: I ran. But you were gone with Mrs. Bliss: so I pelted back again to see with my own eyes that you were safe at home," answered Mac, wiping his hot forehead, with a sigh of relief. "But it is three miles at least each way; and twelve o'clock, and dark and cold. O Mac! how could you!" exclaimed Rose, suddenly realizing what he had done, as she heard his labored breathing, saw the state of the thin boots, and detected the absence of an overcoat. "Couldn't do less, could I?" asked Mac, leaning up against the door and trying not to pant. "There was no need of half-killing yourself for such a trifle. You might have known I could take care of myself for once, at least, with so many friends about. Sit down this minute. Bring another cup, please, Phebe: this boy isn't going home till he is rested and refreshed after such a run as that," commanded Rose. "Don't be good to me: I'd rather take a scolding than a chair, and drink hemlock instead of chocolate if you happen to have any ready,"
  • 70. answered Mac, with a pathetic puff, as he subsided on to the sofa, and meekly took the draught Phebe brought him. "If you had any thing the matter with your heart, sir, a race of this sort might be the death of you: so never do it again," said Rose, offering her fan to cool his heated countenance. "Haven't got any heart." "Yes, you have, for I hear it beating like a trip-hammer, and it is my fault: I ought to have stopped as we went by, and told you I was all right." "It's the mortification, not the miles, that upsets me. I often take that run for exercise, and think nothing of it; but to-night I was so mad I made extra good time, I fancy. Now don't you worry, but compose your mind, and 'sip your dish of tea,' as Evelina says," answered Mac, artfully turning the conversation from himself. "What do you know about Evelina?" asked Rose, in great surprise. "All about her. Do you suppose I never read a novel?" "I thought you read nothing but Greek and Latin, with an occasional glance at Websky's pseudophites and the monoclinics of Johanngeorgenstadt." Mac opened his eyes wide at this reply, then seemed to see the joke, and joined in the laugh with such heartiness that Aunt Plenty's voice was heard demanding from above, with sleepy anxiety,— "Is the house afire?" "No, ma'am, every thing is safe, and I'm only saying good-night," answered Mac, diving for his cap. "Then go at once, and let that child have her sleep," added the old lady, retiring to her bed.
  • 71. Rose ran into the hall, and, catching up her uncle's fur coat, met Mac as he came out of the study, absently looking about for his own. "You haven't got any, you benighted boy! so take this, and have your wits about you next time, or I won't let you off so easily," she said, holding up the heavy garment, and peeping over it, with no sign of displeasure in her laughing eyes. "Next time! Then you do forgive me? You will try me again, and give me a chance to prove that I'm not a fool?" cried Mac, embracing the big coat with emotion. "Of course I will; and, so far from thinking you a fool, I was much impressed with your learning to-night, and told Steve that we ought to be proud of our philosopher." "Learning be hanged! I'll show you that I'm not a book-worm, but as much a man as any of them; and then you may be proud or not, as you like!" cried Mac, with a defiant nod, that caused the glasses to leap wildly off his nose, as he caught up his hat and departed as he came. A day or two later, Rose went to call upon Aunt Jane, as she dutifully did once or twice a week. On her way upstairs, she heard a singular sound in the drawing-room, and involuntarily stopped to listen. "One, two, three, slide! One, two, three, turn! Now then, come on!" said one voice, impatiently. "It's very easy to say 'come on;' but what the dickens do I do with my left leg while I'm turning and sliding with my right?" demanded another voice, in a breathless and mournful tone. Then the whistling and thumping went on more vigorously than before; and Rose, recognizing the voices, peeped through the half- open door to behold a sight which made her shake with suppressed laughter. Steve, with a red table-cloth tied round his waist, languished upon Mac's shoulder, dancing in perfect time to the air he whistled; for Dandy was a proficient in the graceful art, and plumed
  • 72. himself upon his skill. Mac, with a flushed face and dizzy eye, clutched his brother by the small of his back, vainly endeavoring to steer him down the long room without entangling his own legs in the table-cloth, treading on his partner's toes, or colliding with the furniture. It was very droll; and Rose enjoyed the spectacle, till Mac, in a frantic attempt to swing round, dashed himself against the wall, and landed Steve upon the floor. Then it was impossible to restrain her laughter any longer; and she walked in upon them, saying merrily,— "It was splendid! Do it again, and I'll play for you." Steve sprung up, and tore off the table-cloth in great confusion; while Mac, still rubbing his head, dropped into a chair, trying to look quite calm and cheerful as he gasped out,— "How are you, cousin? When did you come? John should have told us." "I'm glad he didn't, for then I should have missed this touching tableau of cousinly devotion and brotherly love. Getting ready for our next party, I see." "Trying to; but there are so many things to remember all at once,— keep time, steer straight, dodge the petticoats, and manage my confounded legs,—that it isn't easy to get on at first," answered Mac, wiping his hot forehead, with a sigh of exhaustion. "Hardest job I ever undertook; and, as I'm not a battering-ram, I decline to be knocked round any longer," growled Steve, dusting his knees, and ruefully surveying the feet that had been trampled on till they tingled; for his boots and broadcloth were dear to the heart of the dapper youth. "Very good of you, and I'm much obliged. I've got the pace, I think, and can practise with a chair to keep my hand in," said Mac, with such a comic mixture of gratitude and resignation that Rose went off again so irresistibly that her cousins joined her with a hearty roar.
  • 73. "As you are making a martyr of yourself in my service, the least I can do is to lend a hand. Play for us, Steve, and I'll give Mac a lesson, unless he prefers the chair." And, throwing off hat and cloak, Rose beckoned so invitingly that the gravest philosopher would have yielded. "A thousand thanks, but I'm afraid I shall hurt you," began Mac, much gratified, but mindful of past mishaps. "I'm not. Steve didn't manage his train well, for good dancers always loop theirs up. I have none at all: so that trouble is gone; and the music will make it much easier to keep step. Just do as I tell you, and you'll go beautifully after a few turns." "I will, I will! Pipe up, Steve! Now, Rose!" And, brushing his hair out of his eyes with an air of stern determination, Mac grasped Rose, and returned to the charge, bent on distinguishing himself if he died in the attempt. The second lesson prospered: for Steve marked the time by a series of emphatic bangs; Mac obeyed orders as promptly as if his life depended on it; and, after several narrow escapes at exciting moments, Rose had the satisfaction of being steered safely down the room, and landed with a grand pirouette at the bottom. Steve applauded, and Mac, much elated, exclaimed with artless candor,— "There really is a sort of inspiration about you, Rose. I always detested dancing before; but now, do you know, I rather like it." "I knew you would; only you mustn't stand with your arm round your partner in this way when you are done. You must seat and fan her, if she likes it," said Rose, anxious to perfect a pupil who seemed so lamentably in need of a teacher. "Yes, of course, I know how they do it;" and, releasing his cousin, Mac raised a small whirlwind round her with a folded newspaper, so full of grateful zeal that she had not the heart to chide him again.
  • 74. "Well done, old fellow. I begin to have hopes of you, and will order you a new dress-coat at once, since you are really going in for the proprieties of life," said Steve from the music-stool, with the approving nod of one who was a judge of said proprieties. "Now, Rose, if you will just coach him a little in his small-talk, he won't make a laughing-stock of himself as he did the other night," added Steve. "I don't mean his geological gabble: that was bad enough, but his chat with Emma Curtis was much worse. Tell her, Mac, and see if she doesn't think poor Emma had a right to think you a first- class bore." "I don't see why, when I merely tried to have a little sensible conversation," began Mac, with reluctance; for he had been unmercifully chaffed by his cousins, to whom his brother had betrayed him. "What did you say? I won't laugh if I can help it," said Rose, curious to hear; for Steve's eyes were twinkling with fun. "Well, I knew she was fond of theatres; so I tried that first, and got on pretty well till I began to tell her how they managed those things in Greece. Most interesting subject, you know?" "Very. Did you give her one of the choruses or a bit of Agamemnon, as you did when you described it to me?" asked Rose, keeping sober with difficulty as she recalled that serio-comic scene. "Of course not; but I was advising her to read Prometheus, when she gaped behind her fan, and began to talk about Phebe. What a 'nice creature' she was, 'kept her place,' 'dressed according to her station,' and that sort of twaddle. I suppose it was rather rude, but being pulled up so short confused me a bit, and I said the first thing that came into my head, which was that I thought Phebe the best- dressed woman in the room, because she wasn't all fuss and feathers like most of the girls." "O Mac! that to Emma, who makes it the labor of her life to be always in the height of the fashion, and was particularly splendid
  • 75. that night. What did she say?" cried Rose, full of sympathy for both parties. "She bridled and looked daggers at me." "And what did you do?" "I bit my tongue, and tumbled out of one scrape into another. Following her example, I changed the subject by talking about the Charity Concert for the orphans; and, when she gushed about the 'little darlings,' I advised her to adopt one, and wondered why young ladies didn't do that sort of thing, instead of cuddling cats and lapdogs." "Unhappy boy! her pug is the idol of her life, and she hates babies," said Rose. "More fool she! Well, she got my opinion on the subject, anyway, and she's very welcome; for I went on to say that I thought it would not only be a lovely charity, but excellent training for the time when they had little darlings of their own. No end of poor things die through the ignorance of mothers, you know," added Mac, so seriously that Rose dared not smile at what went before. "Imagine Emma trotting round with a pauper baby under her arm instead of her cherished Toto," said Steve, with an ecstatic twirl on the stool. "Did she seem to like your advice, Monsieur Malapropos?" asked Rose, wishing she had been there. "No, she gave a little shriek, and said, 'Good gracious, Mr. Campbell, how droll you are! Take me to mamma, please,' which I did with a thankful heart. Catch me setting her pug's leg again," ended Mac, with a grim shake of the head. "Never mind. You were unfortunate in your listener that time. Don't think all girls are so foolish. I can show you a dozen sensible ones, who would discuss dress reform and charity with you, and enjoy
  • 76. Greek tragedy if you did the chorus for them as you did for me," said Rose, consolingly; for Steve would only jeer. "Give me a list of them, please; and I'll cultivate their acquaintance. A fellow must have some reward for making a teetotum of himself." "I will with pleasure; and if you dance well they will make it very pleasant for you, and you'll enjoy parties in spite of yourself." "I cannot be a 'glass of fashion and a mould of form' like Dandy here, but I'll do my best: only, if I had my choice, I'd much rather go round the streets with an organ and a monkey," answered Mac, despondently. "Thank you kindly for the compliment," and Rose made him a low courtesy, while Steve cried,— "Now you have done it!" in a tone of reproach which reminded the culprit, all too late, that he was Rose's chosen escort. "By the gods, so I have!" and, casting away the newspaper with a gesture of comic despair, Mac strode from the room, chanting tragically the words of Cassandra,— "'Woe! woe! O Earth! O Apollo! I will dare to die; I will accost the gates of Hades, and make my prayer that I may receive a mortal blow!'"
  • 77. CHAPTER VII. PHEBE. While Rose was making discoveries and having experiences, Phebe was doing the same in a quieter way: but, though they usually compared notes during the bedtime tête-à-tête which always ended their day, certain topics were never mentioned; so each had a little world of her own into which even the eye of friendship did not peep. Rose's life just now was the gayest, but Phebe's the happiest. Both went out a good deal; for the beautiful voice was welcomed everywhere, and many were ready to patronize the singer who would have been slow to recognize the woman. Phebe knew this, and made no attempt to assert herself; content to know that those whose regard she valued felt her worth, and hopeful of a time when she could gracefully take the place she was meant to fill. Proud as a princess was Phebe about some things, though in most as humble as a child; therefore, when each year lessened the service she loved to give, and increased the obligations she would have refused from any other source, dependence became a burden which even the most fervent gratitude could not lighten. Hitherto the children had gone on together, finding no obstacles to their companionship in the secluded world in which they lived: now that they were women their paths inevitably diverged, and both reluctantly felt that they must part before long. It had been settled, when they went abroad, that on their return Phebe should take her one gift in her hand, and try her fortunes. On no other terms would she accept the teaching which was to fit her for the independence she desired. Faithfully had she used the facilities so generously afforded both at home and abroad, and now
  • 78. was ready to prove that they had not been in vain. Much encouraged by the small successes she won in drawing-rooms, and the praise bestowed by interested friends, she began to feel that she might venture on a larger field, and begin her career as a concert singer; for she aimed no higher. Just at this time, much interest was felt in a new asylum for orphan girls, which could not be completed for want of funds. The Campbells "well had borne their part," and still labored to accomplish the much-needed charity. Several fairs had been given for this purpose, followed by a series of concerts. Rose had thrown herself into the work with all her heart, and now proposed that Phebe should make her début at the last concert which was to be a peculiarly interesting one, as all the orphans were to be present, and were expected to plead their own cause by the sight of their innocent helplessness, as well as touch hearts by the simple airs they were to sing. Some of the family thought Phebe would object to so humble a beginning: but Rose knew her better, and was not disappointed; for, when she made her proposal, Phebe answered readily,— "Where could I find a fitter time and place to come before the public than here among my little sisters in misfortune? I'll sing for them with all my heart: only I must be one of them, and have no flourish made about me." "You shall arrange it as you like; and, as there is to be little vocal music but yours and the children's, I'll see that you have every thing as you please," promised Rose. It was well she did; for the family got much excited over the prospect of "our Phebe's début," and would have made a flourish if the girls had not resisted. Aunt Clara was in despair about the dress; because Phebe decided to wear a plain claret-colored merino with frills at neck and wrists, so that she might look as much as possible, like the other orphans in their stuff gowns and white aprons. Aunt
  • 79. Plenty wanted to have a little supper afterward in honor of the occasion; but Phebe begged her to change it to a Christmas dinner for the poor children. The boys planned to throw bushels of flowers, and Charlie claimed the honor of leading the singer in. But Phebe, with tears in her eyes, declined their kindly offers, saying earnestly, — "I had better begin as I am to go on, and depend upon myself entirely. Indeed, Mr. Charlie, I'd rather walk in alone; for you'd be out of place among us, and spoil the pathetic effect we wish to produce," and a smile sparkled through the tears, as Phebe looked at the piece of elegance before her, and thought of the brown gowns and pinafores. So, after much discussion, it was decided that she should have her way in all things, and the family content themselves with applauding from the front. "We'll blister our hands every man of us, and carry you home in a chariot and four: see if we don't, you perverse prima donna!" threatened Steve, not at all satisfied with the simplicity of the affair. "A chariot and two will be very acceptable as soon as I'm done. I shall be quite steady till my part is all over, and then I may feel a little upset; so I'd like to get away before the confusion begins. Indeed I don't mean to be perverse: but you are all so kind to me, my heart is full whenever I think of it; and that wouldn't do if I'm to sing," said Phebe, dropping one of the tears on the little frill she was making. No diamond could have adorned it better Archie thought, as he watched it shine there for a moment; and felt like shaking Steve for daring to pat the dark head with an encouraging,— "All right. I'll be on hand, and whisk you away while the rest are splitting their gloves. No fear of your breaking down. If you feel the least bit like it, though, just look at me; and I'll glare at you and shake my fist, since kindness upsets you."
  • 80. "I wish you would, because one of my ballads is rather touching, and I always want to cry when I sing it. The sight of you trying to glare will make me want to laugh, and that will steady me nicely: so sit in front, please, ready to slip out when I come off the last time." "Depend upon me!" And the little man departed, taking great credit to himself for his influence over tall, handsome Phebe. If he had known what was going on in the mind of the silent young gentleman behind the newspaper, Steve would have been much astonished; for Archie, though apparently engrossed by business, was fathoms deep in love by this time. No one suspected this but Rose; for he did his wooing with his eyes, and only Phebe knew how eloquent they could be. He had discovered what the matter was long ago,—had made many attempts to reason himself out of it; but, finding it a hopeless task, had given up trying, and let himself drift deliciously. The knowledge that the family would not approve only seemed to add ardor to his love and strength to his purpose: for the same energy and persistence which he brought to business went into every thing he did; and, having once made up his mind to marry Phebe, nothing could change his plan except a word from her. He watched and waited for three months, so that he might not be accused of precipitation, though it did not take him one to decide that this was the woman to make him happy. Her steadfast nature; quiet, busy ways; and the reserved power and passion betrayed sometimes by a flash of the black eyes, a quiver of the firm lips,— suited Archie, who possessed many of the same attributes himself: while the obscurity of her birth and isolation of her lot, which would have deterred some lovers, not only appealed to his kindly heart, but touched the hidden romance which ran like a vein of gold through his strong common-sense, and made practical, steady-going Archie a poet when he fell in love. If Uncle Mac had guessed what dreams and fancies went on in the head bent over his ledgers, and what emotions were fermenting in the bosom of his staid "right-hand man," he would have tapped his forehead, and suggested a lunatic asylum. The boys thought Archie had sobered down too soon. His
  • 81. mother began to fear that the air of the counting-room did not suit him: and Dr. Alec was deluded into the belief that the fellow really began to "think of Rose;" he came so often in the evening, seeming quite contented to sit beside her work-table, and snip tape, or draw patterns, while they chatted. No one observed that, though he talked to Rose on these occasions, he looked at Phebe, in her low chair close by, busy but silent; for she always tried to efface herself when Rose was near, and often mourned that she was too big to keep out of sight. No matter what he talked about, Archie always saw the glossy black braids on the other side of the table, the damask cheek curving down into the firm white throat, and the dark lashes, lifted now and then, showing eyes so deep and soft he dared not look into them long. Even the swift needle charmed him, the little brooch which rose and fell with her quiet breath, the plain work she did, and the tidy way she gathered her bits of thread into a tiny bag. He seldom spoke to her; never touched her basket, though he ravaged Rose's if he wanted string or scissors; very rarely ventured to bring her some curious or pretty thing when ships came in from China: only sat and thought of her; imagined that this was his parlor, this her work-table, and they two sitting there alone a happy man and wife. At this stage of the little evening drama, he would be conscious of such a strong desire to do something rash that he took refuge in a new form of intoxication, and proposed music, sometimes so abruptly that Rose would pause in the middle of a sentence and look at him, surprised to meet a curiously excited look in the usually cool, gray eyes. Then Phebe, folding up her work, would go to the piano, as if glad to find a vent for the inner life which she seemed to have no power of expressing except in song. Rose would follow to accompany her; and Archie, moving to a certain shady corner whence he could see Phebe's face as she sang, would give himself up to unmitigated rapture for half an hour. Phebe never sang so well as at such times: for the kindly atmosphere was like sunshine to a bird, criticisms were
  • 82. few and gentle, praises hearty and abundant; and she poured out her soul as freely as a spring gushes up when its hidden source is full. Always comely, with a large and wholesome growth, in moments such as these Phebe was beautiful with the beauty that makes a man's eye brighten with honest admiration, and thrills his heart with a sense of womanly nobility and sweetness. Little wonder, then, that the chief spectator of this agreeable tableau grew nightly more enamoured; and, while the elders were deep in whist, the young people were playing that still more absorbing game in which hearts are always trumps. Rose, having Dummy for a partner, soon discovered the fact, and lately had begun to feel as she fancied Wall must have done when Pyramus wooed Thisbe through its chinks. She was a little startled at first, then amused, then anxious, then heartily interested, as every woman is in such affairs, and willingly continued to be a medium, though sometimes she quite tingled with the electricity which seemed to pervade the air. She said nothing, waiting for Phebe to speak; but Phebe was silent, seeming to doubt the truth, till doubt became impossible, then to shrink as if suddenly conscious of wrong-doing, and seize every possible pretext for absenting herself from the "girls' corner," as the pretty recess was called. The concert plan afforded excellent opportunities for doing this; and evening after evening she slipped away to practise her songs upstairs, while Archie sat staring disconsolately at the neglected work-basket and mute piano. Rose pitied him, and longed to say a word of comfort, but felt shy,—he was such a reserved fellow,—so left him to conduct his quiet wooing in his own way, feeling that the crisis would soon arrive. She was sure of this, as she sat beside him on the evening of the concert; for while the rest of the family nodded and smiled, chatted and laughed in great spirits, Archie was as mute as a fish, and sat with his arms tightly folded, as if to keep in any unruly emotions
  • 83. which might attempt to escape. He never looked at the programme; but Rose knew when Phebe's turn came by the quick breath he drew, and the intent look that came into his eyes so absent before. But her own excitement prevented much notice of his; for Rose was in a flutter of hope and fear, sympathy and delight, about Phebe and her success. The house was crowded; the audience sufficiently mixed to make the general opinion impartial; and the stage full of little orphans with shining faces, a most effective reminder of the object in view. "Little dears, how nice they look!" "Poor things, so young to be fatherless and motherless." "It will be a disgrace to the city, if those girls are not taken proper care of." "Subscriptions are always in order, you know; and pretty Miss Campbell will give you her sweetest smile if you hand her a handsome check." "I've heard this Phebe Moore, and she really has a delicious voice: such a pity she won't fit herself for opera!" "Only sings three times to-night; that's modest I'm sure, when she is the chief attraction; so we must give her an encore after the Italian piece." "The orphans lead off, I see: stop your ears if you like; but don't fail to applaud, or the ladies will never forgive you." Chat of this sort went on briskly, while fans waved, programmes rustled, and ushers flew about distractedly; till an important gentleman appeared, made his bow, skipped upon the leader's stand, and with a wave of his bâton caused a general uprising of white pinafores, as the orphans led off with that much-enduring melody, "America," in shrill small voices, but with creditable attention to time and tune. Pity and patriotism produced a generous round of applause; and the little girls sat down, beaming with innocent satisfaction. An instrumental piece followed, and then a youthful gentleman, with his hair in picturesque confusion, and what his friends called a "musical brow," bounded up the steps, and, clutching a roll of music
  • 84. with a pair of tightly gloved hands, proceeded to inform the audience, in a husky tenor voice, that "It was a lovely violet." What else the song contained in the way of sense or sentiment it was impossible to discover; as the three pages of music appeared to consist of variations upon that one line, ending with a prolonged quaver, which flushed the musical brow, and left the youth quite breathless when he made his bow. "Now she's coming! O uncle, my heart beats as if it was myself!" whispered Rose, clutching Dr. Alec's arm with a little gasp, as the piano was rolled forward, the leader's stand pushed back, and all eyes turned toward the anteroom door. She forgot to glance at Archie, and it was as well perhaps; for his heart was thumping almost audibly, as he waited for his Phebe. Not from the anteroom, but out from among the children, where she had sat unseen in the shadow of the organ, came stately Phebe in her wine-colored dress, with no ornament but her fine hair and a white flower at her throat. Very pale, but quite composed, apparently; for she stepped slowly through the narrow lane of upturned faces, holding back her skirts, lest they should rudely brush against some little head. Straight to the front she went, bowed hastily, and, with a gesture to the accompanist, stood waiting to begin, her eyes fixed on the great gilt clock at the opposite end of the hall. They never wandered from that point while she sung; but, as she ended, they dropped for an instant on an eager, girlish countenance, bending from a front seat; then, with her hasty little bow, she went quickly back among the children, who clapped and nodded as she passed, well pleased with the ballad she had sung. Every one courteously followed their example; but there was no enthusiasm, and it was evident that Phebe had not produced a particularly favorable impression.
  • 85. "Never sang so badly in her life," muttered Charlie, irefully. "She was frightened, poor thing. Give her time, give her time," said Uncle Mac, kindly. "I saw she was, and I glared like a gorgon, but she never looked at me," added Steve, smoothing his gloves and his brows at the same time. "That first song was the hardest, and she got through much better than I expected," put in Dr. Alec, bound not to show the disappointment he felt. "Don't be troubled. Phebe has courage enough for any thing, and she'll astonish you before the evening's over," prophesied Mac, with unabated confidence; for he knew something that the rest did not. Rose said nothing, but, under cover of her burnous, gave Archie's hand a sympathetic squeeze; for his arms were unfolded now, as if the strain was over, and one lay on his knee, while with the other he wiped his hot forehead with an air of relief. Friends about them murmured complimentary fibs, and affected great delight and surprise at Miss Moore's "charming style," "exquisite simplicity," and "undoubted talent." But strangers freely criticised, and Rose was so indignant at some of their remarks she could not listen to any thing upon the stage, though a fine overture was played, a man with a remarkable bass voice growled and roared melodiously, and the orphans sang a lively air with a chorus of "Tra, la, la," which was a great relief to little tongues unused to long silence. "I've often heard that women's tongues were hung in the middle and went at both ends: now I'm sure of it," whispered Charlie, trying to cheer her up by pointing out the comical effect of some seventy-five open mouths, in each of which the unruly member was wagging briskly.
  • 86. Rose laughed and let him fan her, leaning from his seat behind with the devoted air he always assumed in public; but her wounded feelings were not soothed, and she continued to frown at the stout man on the left, who had dared to say with a shrug and a glance at Phebe's next piece, "That young woman can no more sing this Italian thing than she can fly, and they ought not to let her attempt it." Phebe did, however; and suddenly changed the stout man's opinion by singing it grandly; for the consciousness of her first failure pricked her pride and spurred her to do her best with the calm sort of determination which conquers fear, fires ambition, and changes defeat to success. She looked steadily at Rose now, or the flushed, intent face beside her; and throwing all her soul into the task let her voice ring out like a silver clarion, filling the great hall and setting the hearers' blood a-tingle with the exulting strain. That settled Phebe's fate as cantatrice; for the applause was genuine and spontaneous this time, and broke out again and again with the generous desire to atone for former coldness. But she would not return, and the shadow of the great organ seemed to have swallowed her up; for no eye could find her, no pleasant clamor win her back. "Now I can die content," said Rose, beaming with heart-felt satisfaction; while Archie looked steadfastly at his programme, trying to keep his face in order, and the rest of the family assumed a triumphant air, as if they had never doubted from the first. "Very well, indeed," said the stout man, with an approving nod. "Quite promising for a beginner. Shouldn't wonder if in time they made a second Cary or Kellogg of her." "Now you'll forgive him, won't you?" murmured Charlie, in his cousin's ear. "Yes; and I'd like to pat him on the head. But take warning and never judge by first appearances again," whispered Rose, at peace
  • 87. now with all mankind. Phebe's last song was another ballad; for she meant to devote her talent to that much neglected but always attractive branch of her art. It was a great surprise, therefore, to all but one person in the hall, when, instead of singing "Auld Robin Grey," she placed herself at the piano, and, with a smiling glance over her shoulder at the children, broke out in the old bird-song which first won Rose. But the chirping, twittering, and cooing were now the burden to three verses of a charming little song, full of spring-time and the awakening life that makes it lovely. A rippling accompaniment flowed through it all, and a burst of delighted laughter from the children filled up the first pause with a fitting answer to the voices that seemed calling to them from the vernal woods. It was very beautiful, and novelty lent its charm to the surprise; for art and nature worked a pretty miracle, and the clever imitation, first heard from a kitchen hearth, now became the favorite in a crowded concert room. Phebe was quite herself again; color in the cheeks now; eyes that wandered smiling to and fro; and lips that sang as gaily and far more sweetly than when she kept time to her blithe music with a scrubbing brush. This song was evidently intended for the children, and they appreciated the kindly thought; for, as Phebe went back among them, they clapped ecstatically, flapped their pinafores, and some caught her by the skirts with audible requests to "do it again, please; do it again." But Phebe shook her head and vanished; for it was getting late for such small people, several of whom "lay sweetly slumbering there," till roused by the clamor round them. The elders, however, were not to be denied, and applauded persistently, especially Aunt Plenty, who seized Uncle Mac's cane and pounded with it as vigorously as "Mrs. Nubbles" at the play.
  • 88. "Never mind your gloves, Steve; keep it up till she comes," cried Charlie, enjoying the fun like a boy; while Jamie lost his head with excitement, and standing up called "Phebe! Phebe!" in spite of his mother's attempts to silence him. Even the stout man clapped, and Rose could only laugh delightedly as she turned to look at Archie, who seemed to have let himself loose at last, and was stamping with a dogged energy funny to see. So Phebe had to come, and stood there meekly bowing, with a moved look on her face, that showed how glad and grateful she was, till a sudden hush came; then, as if inspired by the memory of the cause that brought her there, she looked down into the sea of friendly faces before her, with no trace of fear in her own, and sung the song that never will grow old. That went straight to the hearts of those who heard her: for there was something inexpressibly touching in the sight of this sweet- voiced woman singing of home for the little creatures who were homeless; and Phebe made her tuneful plea irresistible by an almost involuntary gesture of the hands which had hung loosely clasped before her; till, with the last echo of the beloved word, they fell apart and were half-out-stretched as if pleading to be filled. It was the touch of nature that works wonders; for it made full purses suddenly weigh heavily in pockets slow to open, brought tears to eyes unused to weep, and caused that group of red-gowned girls to grow very pathetic in the sight of fathers and mothers who had left little daughters safe asleep at home. This was evident from the stillness that remained unbroken for an instant after Phebe ended; and before people could get rid of their handkerchiefs she would have been gone, if the sudden appearance of a mite in a pinafore, climbing up the stairs from the anteroom, with a great bouquet grasped in both hands, had not arrested her. Up came the little creature, intent on performing the mission for which rich bribes of sugar-plums had been promised, and trotting
  • 89. Welcome to our website – the perfect destination for book lovers and knowledge seekers. We believe that every book holds a new world, offering opportunities for learning, discovery, and personal growth. That’s why we are dedicated to bringing you a diverse collection of books, ranging from classic literature and specialized publications to self-development guides and children's books. More than just a book-buying platform, we strive to be a bridge connecting you with timeless cultural and intellectual values. With an elegant, user-friendly interface and a smart search system, you can quickly find the books that best suit your interests. Additionally, our special promotions and home delivery services help you save time and fully enjoy the joy of reading. Join us on a journey of knowledge exploration, passion nurturing, and personal growth every day! ebookbell.com