tutorial: extending the zend server ui and web api

77
Extending the Zend Server UI and WebAPI Adding your own functionality to Zend Server Presented by Yonni Mendes with cats, spartans, pirates and Obama! Presented by Yonni Mendes, ZS6 UI Tech Leader Before we start, kindly visit avast.yonman.net and install the listed applications & stuff!

Upload: yonni-mendes

Post on 29-Jun-2015

292 views

Category:

Technology


4 download

DESCRIPTION

This is a Tutorial given at ZendCon 2013 about Zend Server 6's UI extensibility

TRANSCRIPT

Page 1: Tutorial: extending the zend server ui and web api

Extending the Zend Server UI and WebAPIAdding your own functionality to Zend ServerPresented by Yonni Mendes with cats, spartans, pirates and

Obama!

Presented by Yonni Mendes, ZS6 UI Tech Leader

Before we start, kindly visit avast.yonman.net and install the listed

applications & stuff!

Page 2: Tutorial: extending the zend server ui and web api

Setup: what you should have● Zend Studio, or other IDE of choice● Zend Server● AhoyWorld zpk deployable● Tutorial package

○ Skeletons○ Exercise snippets○ Complete code

● Recommended○ Chrome: Dev HTTP Client or similar○ Firefox: Firebug, live HTTP Headers or

similar○ Sqlite database manager of choice

Page 3: Tutorial: extending the zend server ui and web api

Who and What is a Yonni● A restaurant in Los Angeles,

California.Sadly, closed.

● A semi-famous male model● The UI Technological Leader for Zend

ServerAlso● A father● A gamer ● A technologistAsk me about the extra thingyou should know!

Stuff you need:

IDEZend ServerAhoy World

Chrome: Dev HTTP Firefox: Firebug, live HTTP Headers

Sqlite database manager of choice

Page 4: Tutorial: extending the zend server ui and web api

But first…

Some exercises to get the bloodflowing

Stand up please!

Stuff you need:

IDEZend ServerAhoy World

Chrome: Dev HTTP Firefox: Firebug, live HTTP Headers

Sqlite database manager of choice

Page 5: Tutorial: extending the zend server ui and web api

Topics• Create a basic 3rd party module

• Understanding ZS6o Under the Zend Server 6 hoodo Beasts and common practices of ZS6o Lets make our module do something

• Some more modules thatare out there

Stuff you need:

IDEZend ServerAhoy World

Chrome: Dev HTTP Firefox: Firebug, live HTTP Headers

Sqlite database manager of choice

Page 6: Tutorial: extending the zend server ui and web api

Why extend ZS6?● Zend Server’s unique position● Zend Server’s existing platform &

services

Stuff you need:

IDEZend ServerAhoy World

Chrome: Dev HTTP Firefox: Firebug, live HTTP Headers

Sqlite database manager of choice

And, for us, most importantly:

B.W.C

Page 7: Tutorial: extending the zend server ui and web api

The first steps:3rd party module

So you want to add some functionality...

Page 8: Tutorial: extending the zend server ui and web api

A few ZF2 buzzwordsThese are a few devices we will need● ServiceManager, Factories● Merged config arrays● Modules, Module manager

Page 9: Tutorial: extending the zend server ui and web api

Our scenario● We have an application, “Ahoy World!”

○ Deploy it (https://github.com/YonmaN/AhoyWorld)

● This application can be turned on and off

● We want to allow a user logged into Zend Server to control this toggle directly from the UI

● We also want to control this toggle from other remote sources via WebAPI

Page 10: Tutorial: extending the zend server ui and web api

Andy the admin’s req’s•Show some general app information•Access to toggle the application state

o Anywhere, Anytime, but not direct accesso No access to those Creative Type People over

at marketing

•Human UI and API access•Must be secure

o But: Do not create a new user authentication system, nor modify the existing one at Ahoy

•In fact, do not modify AhoyWorld at all

Page 11: Tutorial: extending the zend server ui and web api

Our Module: Avast● WebAPI will allow a client to:

○ Retrieve the current status of the site (on or off)

○ Toggle the current status of the site○ Rely on external code that’s included into the

module

● UI will display the deployment information about our application

● UI will only show a status indicator to the developer, nothing else

● UI will show a toggle button that allows the admin to turn the site on or off

Page 12: Tutorial: extending the zend server ui and web api

Functional requirements● ZF2 module, use ZF2 features, be ZF2

compliant● WebAPI will return both json and xml

output● UI page will be integrated in the ZS6

ACL● UI page will show up in the navigation

page● UI will be accessible only to a logged

in user

Page 13: Tutorial: extending the zend server ui and web api

Preparing to develop for ZS6• Switch off opcode cache in Zend

Servero Linux/mac:

/usr/local/zend/gui/lighttpd/etc/conf.d/optimizerplus.ini

o Windows: Through the UI (Configuration|Components)

• Restart Zend Servero Throught the UI (localhost:10081)o Command line (<install_dir>/bin/zendctl.sh

restart)

• Recommended:open <install_dir>/gui as a php project in the IDE

• Find the gui/vendor directory

Page 14: Tutorial: extending the zend server ui and web api

Warning! Confusion ahead!● Zend Studio copies files● Changes may not apply● You may have to make some changes

manually● Particularly, the vendor library

changes

Page 15: Tutorial: extending the zend server ui and web api

Creating a new module, the hard way● Create a new php project● zendframework/ZendSkeletonModule● Unzip into your workspace folder● Rename everything that needs

renaming

Page 16: Tutorial: extending the zend server ui and web api

Rename gotta rename● Folder names

○ in src

● File names○ Controller filename○ view scripts

● Controller class name

Yonni Guido Mendes
avast-00-initial
Page 17: Tutorial: extending the zend server ui and web api

Create a new module, the easy way● Create a new php project● Unzip avast.yonman.net/Avast-00-Initial.zip

Important: can you create a symbolic link?

Recommended: add the Zend GUI project as a library to the new module project, in ‘include path’

Page 18: Tutorial: extending the zend server ui and web api

Make a connection● In the Zend Server UI directory

o link to ./vendoro Add ‘Avast’ to application.config.php

• Open localhost:10081/ZendServer/Avast

Note case sensitive URLResult: error :(

Page 19: Tutorial: extending the zend server ui and web api

Necessary changes● Remove the entire router entry● Replace controllers entry

'invokables' => array(

'Avast' => 'Avast\Controller\AvastController',

),

Result: Still error!

Page 20: Tutorial: extending the zend server ui and web api

ACL entries and overrides• Pitfall: GUI will now return 404!

o Bug in the ACL systemo Missing resource called "skeleton"...o Therefore no controller is found ... ergo 404

• Fix: ACL requires SQL entrieso sqlite: var/db/gui.db or data/db/gui.db

INSERT OR IGNORE INTO GUI_ACL_RESOURCES VALUES(NULL,'route:Avast');

INSERT OR IGNORE INTO GUI_ACL_PRIVILEGES VALUES(3, last_insert_rowid(), '');

(Place in sqls in a dedicated sql file - acl.sqlite.entries.sql)

Result: localhost:10081/ZendServer/Avast should respond correctly Exercise Avast-01-acl

Page 21: Tutorial: extending the zend server ui and web api

Installation process summary• Unpack / link module into ./vendor

• Add module name to application.config.php

• Run SQLs

• Copy any config.php files into ./config/autoload

• Copy any public directory assets to the public direcotry (js, images, css)

Page 22: Tutorial: extending the zend server ui and web api

Bask in the glory of your Module!

Our module can now:● Display an empty page● Override navigation bar● Display app info & status indicator● Provide WebAPI control of Ahoy● Provide a UI button to control Ahoy

Page 23: Tutorial: extending the zend server ui and web api

Avast ye landlubbers!Lets make things interesting

Page 24: Tutorial: extending the zend server ui and web api

Lets grease those elbows!From here on:1.Describe a tool or ZS6 subsystem and

its use2.Show its usage in the ZS6 codebase3. Integrate it to some extent into your

moduleOur goal:1.Create a functioning, usable module

for ZS62.Module will provide functionality in

webapi3.Module will also provide a UI interface

to use the above functionality4.Have fun!

Page 25: Tutorial: extending the zend server ui and web api

Files you might want to watchLinux● gui/lighttpd/logs/zend_server_ui.log● gui/lighttpd/logs/php.logWindows● logs/zend_server_ui.log● logs/php.logYou can access both through the Zend

Server UI (Overview|Logs)

Page 26: Tutorial: extending the zend server ui and web api

ZF2: Navigation override• navigation.global.config.ph

p

• We wish to add a new Avast tab

• Tab should use Avast controller

Exercise: Override the navigation array in Avast/module.config.php

Ask me about ZS6.2!

Result: Navigation bar should show Avast tabs that can be used

Exercise Avast-02-navigation

Page 27: Tutorial: extending the zend server ui and web api

Calling the ZS databases• You’ll want to do this through our

mappers

• If you want to build a new mapper ...o ServiceManager and delegated Di

DbConnector::factory Driver Connection Adapter TableGateway Mapper

o Can also call ServiceManager->get('PDO')

Ask me about ZS6.2!

Page 28: Tutorial: extending the zend server ui and web api

Spotlight: Deployment\FilteredAccessMapper

● Wraps another mapper, which wrapper yet another, foreign mapper. Good times.

● Things of interest○ FilteredAccessMapper::getMasterApplication

sByIds○ Deployment\Application\Container

Ask me about the big dirty lie!

Page 29: Tutorial: extending the zend server ui and web api

An aside:Foreign code, a cruel mistress● Next we will implement

○ Avast UI has to show deployment information about “Ahoy World”

○ Use Deployment\FilteredAccessMapper class to retrieve and display data

● Use foreign code carefully to avoid errors

● Aggregate, don’t inherit (Ask me why )

● Avoid referring to foreign code directly

Page 30: Tutorial: extending the zend server ui and web api

Lets integrateRecommended approach:● Create an aggregating class for

FilteredAccessMapper● Implement findAhoyApplication() code in

class● Integrate your class into the controller

Can go the extra mile - wrap the application container that is returned

Page 31: Tutorial: extending the zend server ui and web api

Calling the database● Avast UI has to show deployment

information about “Ahoy World”● Use Deployment\FilteredAccessMapper

class to retrieve and display data

Exercise: Integrate a call to retrieve all applications. Find ‘Ahoy World’ application data.

Show this data in your view script.Ask me why there’s no native

“getApplicationByName” method!Exercise Avast-03-applications

Yonni Guido Mendes
Avast-03-Applications.zip
Page 32: Tutorial: extending the zend server ui and web api

Add a visual status indicatorRequirement:● UI will show a toggle status indicator

for Ahoy World

Thoughts on how to integrate with the AhoyWorld Application?

Page 33: Tutorial: extending the zend server ui and web api

Meanwhile, in AhoyWorld<ahoyworld-url>/StatusA restfull api returns the current

application status

JSON output:{"status":true}

Page 34: Tutorial: extending the zend server ui and web api

Add a visual status indicator...Requirement:● UI will show a toggle status indicator

for Ahoy World

Exercise:Add a static visual indicator for the

current state.Retrieve result without duplicating

functionalityResult: Visual indicator will display

correct infoExercise Avast-04-StatusIndicator

Page 35: Tutorial: extending the zend server ui and web api

Bask in the glory of your Module!

Our module can now:● Display an empty page● Override navigation bar● Display app info & status indicator● Provide WebAPI control of Ahoy● Provide a UI button to control Ahoy

Page 36: Tutorial: extending the zend server ui and web api

WebAPI ho!It’s not Zend Server if there’s no WebAPI

Page 37: Tutorial: extending the zend server ui and web api

WebAPI: How does one use it• Create a request using Zend\Http\Client

• Add an Accept headero application/vnd.zend.serverapi+jsono Can be X-Accept too (Ask me why!)

• Sign the request using SignatureGeneratoro Date should be a GMT with specific formato Use key obtained in the WebAPI section of UIo Machines may have to have their clocks

synched

Good news: We don’t need any of the above

Ask me Why!

Page 38: Tutorial: extending the zend server ui and web api

Tools: Request simulationHow about we see an example?● URL: localhost:10081/ZendServer/Api/getSystemInfo● Header: Accept: ….

○ To version or not to version?■ application/vnd.zend.serverapi+json;version=1.5

○ Output type?

● Get or post?● Signed request or session hijacking?● Inspecting the response

Page 39: Tutorial: extending the zend server ui and web api

WebAPI: avastAhoyStatus● WebAPI action to return the current

state of Ahoy’s master directive● No parameters● Output: json and xml

We want to create the avastAhoyStatus route & ACL

…but how do we do that?

Page 40: Tutorial: extending the zend server ui and web api

Create a WebAPI routeCreate a Literal route in module.config.php'webapi_routes' => array(

'AvastWebAPI' => array(

'type' => 'Zend\Mvc\Router\Http\Literal',

'options' => array(

'route' => '/Api/example',

'defaults' => array(

'controller' => 'AvastWebAPI',

'action' => 'avastAhoyStatus',

'versions' => array('1.5'),

),

))

)

...

Ask me about more route options!Tell me: what did I forget?

Page 41: Tutorial: extending the zend server ui and web api

More ACL entries and overrides• ACL requires SQL entries

o sqlite, db/gui.dbINSERT OR IGNORE INTO GUI_ACL_RESOURCES VALUES(NULL,'route:AvastWebAPI');

INSERT OR IGNORE INTO GUI_ACL_PRIVILEGES VALUES(3, last_insert_rowid(), '');

• These queries have to be reapplied after upgrades

• Controller names are case sensitive

Ask me about exceptions!

Page 42: Tutorial: extending the zend server ui and web api

avastAhoyStatus, cont’dReminder:● WebAPI action to return the current

state of Ahoy’s master directive● No parameters● Output: json and xml

We want to create a controller and view scripts

…but how do we do that?

Page 43: Tutorial: extending the zend server ui and web api

Lets add a WebAPI controller• Class WebAPIController

o Extends WebAPIActionController classo Add to controllers in module.config

(AvastWebAPI_1-5)o Controller names are case sensitive

• Action should be named as indicated by the route’s action parameter

Page 44: Tutorial: extending the zend server ui and web api

Lets add a WebAPI view script• Actions need View scripts - one for

each formato avast/web-api/1x5/<action>.pjson.phtmlo avast/web-api/1x5/<action>.pxml.phtmlo <action> has dashed inflection: avast-

ahoy-statuso Wrapped by layout.p<output>.phtml

automaticallyo Create a string that “nestles” within the

layout

• View script has to include “responseData” wrapper element in json and xml

Page 45: Tutorial: extending the zend server ui and web api

avastAhoyStatus, cont’d● WebAPI action to return the current

state of Ahoy’s master directive● No parameters● Output: json and xml

Exercise: Create the avastAhoyStatus webapi controller & action

Exercise: Create json and xml view scripts

Use dummy data for this exerciseResult: avastAhoyStatus responds to

requests

Page 46: Tutorial: extending the zend server ui and web api

avastAhoyStatus functionality● Add functionality to our webapi action● Retrieve a boolean status● Pass it out to the view script

Exercise: Use Integration Stub to retrieve status

Replace the dummy data from the previous exercise

Result: avastAhoyStatus responds correctly

Exercise Avast-05-avastAhoyStatus

Page 47: Tutorial: extending the zend server ui and web api

WebAPI: avastAhoyToggle● WebAPI action to return the updated

state of Ahoy’s master directive● Parameter: state● Output: json and xml

Exercise: Create the avastAhoyToggle route

Exercise: Create the avastAhoyToggle webapi

Exercise: Create json and xml output, similar or identical to avastAhoyStatus

Result: avastAhoyToggle responds to requests

Page 48: Tutorial: extending the zend server ui and web api

WebAPI: avastAhoyToggle ● Add functionality to our webapi action● Collect a parameter and validate it● Pass the parameter to a toggle method

Exercise: Use <ahoyworld-url>/Status/Toggle to change AhoyWorld’s status

Result: calls to avastAhoyToggle affect AhoyWorld

Exercise Avast-06-avastAhoyToggle

Page 49: Tutorial: extending the zend server ui and web api

Bask in the glory of your Module!

Our module can now:● Display an empty page● Override navigation bar● Display app info & status indicator● Provide WebAPI control of Ahoy● Provide a UI button to control Ahoy

Page 50: Tutorial: extending the zend server ui and web api

Lets make our UI do a jig

Shiver me timbers!

Page 51: Tutorial: extending the zend server ui and web api

Javascript!● WebAPI is everywhere in the UI● Different usage scenarios● Some tools we provide

○ zswebapi.js○ zgridPolling.js○ FragmentManager.js

Ask me about the exceptions to the rule!

Page 52: Tutorial: extending the zend server ui and web api

Integrate WebAPI into the UI● Status indicator● Button that will call our toggle webapi● Use toggle response to update

indicator

Exercise: use Request.WebAPI to call the toggle

Result: We have a button that lets the user affect AhoyWorld directly

Exercise Avast-07-webapiButton

Page 53: Tutorial: extending the zend server ui and web api

Requirements reminder● avastAhoyStatus should be accessible

to admin and developer alike● avastAhoyToggle should be accessible

to the admin only

Page 54: Tutorial: extending the zend server ui and web api

ACL in Zend Server• ACL is consulted for every request

• Two ACLs: Role/Identity, Edition/Licenseo Identity ACL initialized from the database

GUI_ACL_ROLES, GUI_ACL_RESOURCES, GUI_ACL_PRIVILEGES

Roles: administrator, developer, developerLimited

o License ACL initialized within the application

Page 55: Tutorial: extending the zend server ui and web api

ACL in Zend Server• ACL Resources are prefixed:

o route - to indicate this is an MVC controller resource

o data - to indicate this is a logical resourceo service - to indicate this is a system-wide

resource

• Privileges may allow particular actions

Page 56: Tutorial: extending the zend server ui and web api

ACL in Zend Server, cont'd• AclQuery class, registered services

o ZendServerAcl (AclQuery class)o ZendServerEditionAcl (Acl class)o ZendServerIdentityAcl (Acl class)

• AclQuery is used many places

• AclQuerierInterface sets ZendServerAclo ZendServer\..\PhpRenderer - for view usageo ZendServer\..\ActionController - inherited by

every controller in the ZS6 UI applicationo ZendServer\..\DefaultNavigationFactory for

navigation renderingo and others...

Page 57: Tutorial: extending the zend server ui and web api

ACL: limit access to WebAPI● avastAhoyStatus should be accessible

to admin and developer alike● avastAhoyToggle should be accessible

to the admin onlyExercise: Modify your module’s SQLs to

map these ACL directives. Apply the new directives immediately to your gui.db and acl.sqlite...sql

Result: Developer user gets an error for toggle

Ask me about the neat trick!

Exercise Avast-08-webapiAclLimit

Page 58: Tutorial: extending the zend server ui and web api

ACL: querying in MVC● Acl Queries can be performed in view

script○ PhpRenderer::isAllowed()○ PhpRenderer::isAllowedIdentity()○ PhpRenderer::isAllowedEdition()

● Acl Queries can also be used in controllers○ ActionController::isAclAllowed()○ ActionController::isAclAllowedIdentity(

)○ ActionController::isAclAllowedEdition()

Page 59: Tutorial: extending the zend server ui and web api

Parameters for isAllowed*For all of the previous methodsisAllowed(‘route:<controller>’,

‘<action>’)

Page 60: Tutorial: extending the zend server ui and web api

ACL: limit access to WebAPI● avastAhoyStatus should be accessible

to admin and developer alike● avastAhoyToggle should be accessible

to the admin onlyExercise:Add an isAclAllowed check in Controller

actionIf fails, throw a WebAPI\ExceptionException Code: INSUFFICIENT…Result: action doesn’t fail… what did we

forget?

Page 61: Tutorial: extending the zend server ui and web api

ACL: limit access, cont’d● avastAhoyStatus ... admin and

developer● avastAhoyToggle should be accessible

to the admin onlyExercise:Modify SQLs to map ACL directives.

Apply the new directives immediately to your gui.db

Result: Developer user gets an error for toggle

Ask me about the neat trick!

Exercise Avast-08-webapiAclLimit

Page 62: Tutorial: extending the zend server ui and web api

ACL: affecting presentation● Ahoy status indicator should be visible

to anyone● Ahoy toggle button should be available

only to an administrator

Exercise: Add ACL queries to the view script

Result: Ahoy toggle button does not get displayed at all to the developer

Exercise Avast-09-uiAclLimits

Page 63: Tutorial: extending the zend server ui and web api

Bask in the glory of your Module!

Our module can now:● Display an empty page● Override navigation bar● Display app info & status indicator● Provide WebAPI control of Ahoy● Provide a UI button to control Ahoy

Page 64: Tutorial: extending the zend server ui and web api

ExtrasShould we have the time & spirit...

Page 65: Tutorial: extending the zend server ui and web api

Add polling to the Avast page● zgridPolling, Request.WebAPI

○ Poll avastAhoyStatus○ Polling populates button label and indicator

label

● Polling removes the need to refresh the page after toggle button is pressed

Page 66: Tutorial: extending the zend server ui and web api

Calling the database: extended● Clusters require we show the

application status for every server, not just its aggregate.

● Show each server’s state for AhoyWorld

● Servers\Db\Mapper methods will assist

Exercise: Use the application’s servers list to cross reference with the available servers list

Page 67: Tutorial: extending the zend server ui and web api

Create an avastAppInfo WebAPI● Move the functionality of retrieving

application data form the UI into a webapi action

● This can be used for page display by polling avastAppInfo from the page

Page 68: Tutorial: extending the zend server ui and web api

PracticesComplex and silly things

Page 69: Tutorial: extending the zend server ui and web api

Logging• Zend Server UI log: zend_server_ui.logo Linux: <zend-server>/gui/lighttpd/logso Windows: <zend-server>\logso Written to by class ZendServer\Log\Log

• Verbosity change be changed in the Settingso Default value is notice and up

• Logs can be viewed in the Logs pageo Relies on GUI_AVAILABLE_LOGS tableo You can add logs, table is cleared during

upgrade

Page 70: Tutorial: extending the zend server ui and web api

Debugging the ZS UIWhen debugging your module inside

ZS6:● Log verbosity should stay on notice

○ Throw out messages in high levels like “alert”○ Debug level throws out LOTS of information

● UI is constantly polling, you may face lots of log entries that are unrelated to your own code○ zend_gui.debugModeEnabled = true adds the

request URI to the log entry○ Allows you to filter the entries

Page 71: Tutorial: extending the zend server ui and web api

...

IdentityFilter - Authentication• Simple Authentication

• Extended Authenticationo Identity Groupso Groups to Apps mappingo CustomAuth module

• Logino Authenticate user credentialso Create session Identity objecto Determine allowed applicationso End result: Identity has a list of

allowed applicationsU

ser

Gro

ups

List of

App

s

Group1

Group2

Group3

App 1

App 2

Zend Server

LDAPSession storage

Page 72: Tutorial: extending the zend server ui and web api

IdentityFilter - Use and usage• Classes implement

IdentityFilterInterfaceinterface IdentityFilterInterface {

public function filterAppIds($applicationIds, $emptyIsAll);

public function setAddGlobalAppId($addGlobalAppId);

}

• filterAppIdso Intersect identity applications with

$applicationIdso If empty applications, check $emptyIsAll

• setAddGlobalAppIdo Global application (Ask me what that is!)

Page 73: Tutorial: extending the zend server ui and web api

UI compounded services• Compound services

o Controllers call other controllers using forward->dispatch plugin

o MVC Failures are handled by dispatch.error event and event handlers

o Nested WebAPI calls in clusters (An example?)

• View scriptso Some initial data is rendered by calling a

relevant view script directlyo Using WebAPI from the Zend Server UI client

is easy! (ask me why!)

Page 74: Tutorial: extending the zend server ui and web api

Nested webapi requests

Somewhere in the internet...

WebAPI RequestZend

Server Webapi

Should I respond to this request?

Zend Server Webapi

ZS6 cluster

db

Do I have any friends?

Used by:• Logs display• Server info• Codetracing details view

Page 75: Tutorial: extending the zend server ui and web api

WebAPI validationSet of validation functions that throw

exceptions

• Integrated into all existing webapi actions

• ZendServer\Mvc\Controller\WebAPIActionController

• ZendServer\Mvc\Controller\ActionController

Page 76: Tutorial: extending the zend server ui and web api

Existing ZS6 Modules● Populated with code written by Zend

developers● No warranty and no SLA cover● Highlights

○ ZendServer-TokenAuthentication○ ZendServer-CustomAuth○ ZendServerNagiosPlugin

● External and peripheral integration○ ZendServerWebApiModule○ ZendServerSDK○ ZendServerDeploymentHelper

Page 77: Tutorial: extending the zend server ui and web api

Feedback: [email protected], @zendsui, https://joind.in/9049, linkedin.com/in/yonman

/usr/local/zend/gui/vendor/ThankYou/src/HadFun/Cya.php

Give me a wonderful review: https://joind.in/9049