Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Was wsServer helpful to you? tell us a little about it #30

Theldus started this conversation in General
Discussion options

Hi,
Developing open-source software can be a bit lonely when you (maintainer) don't get feedback about your project... and that's why I created this discussion. Feel free to talk about the project, what was helpful to you, suggestions...

A little about the project:

wsServer was born 7 years ago when I wanted to create a mini checkers game in C + HTML/JS. At that time, I have not found many user-friendly libraries, and I really wanted to focus on game logic, rather than spending much time trying to understand how a library works.

Ironically, this is how wsServer was born: I decided to invest some of my time and implement the minimum necessary to have a websocket server in C to get my little game up and running.

In the beginning, this project was just a baby, and there were a lot of bugs and cases that I hadn't tested. After receiving occasional contributions over the years, I decided to give it a little more love and take the project more seriously, making it more robust and useful to more people.

Currently, the code has grown over 5x more (300 versus 1500 LOC, src/ws.c), supports 4 different OSs (Linux, Windows, macOS, and FreeBSD), and is compliant (or should be) with RFC 6455, but the main ideas remain the same:

  • Simple to use: with 1 minute you can clone, build and run the examples =).
  • Comprehensible code: wsServer strives to be written in the best possible way, which allows the user to understand how the server implements the protocol and how it is possible to modify the source code in their own way.
  • No external dependencies: wsServer only requires a C99 compatible compiler and libc.
  • Not resource-hungry: Its simplicity allows it to easily run in a variety of environments such as VPS, Desktop, Smartphones, Raspberry Pi, and so on.

However, its simplicity comes with some cost: Not suitable for environments with hundreds or thousands of simultaneous accesses. Despite this, wsServer manages to have very acceptable performance and is on par with other competitors.

What are your thoughts?

Whenever I see a fork, a new issue, or even a PR, I ask myself things like:

  • What do you use this library for?
  • What is your use case?
  • Do you use/used wsServer for any project? What was the project? (if open source, share it here :-D)

Given everything I've said about the project's goals, I'd also like to know:

  • What did you like (or not)?
  • What could be improved?

If you want to discuss a more specific thing, feel free to create an issue as well.

You must be logged in to vote

Replies: 13 comments 20 replies

Comment options

It's the first project of websocket webserver I get run successfully after a night, so excited, thanks so much ! so nice !

You must be logged in to vote
2 replies
Comment options

Theldus Oct 3, 2021
Maintainer Author

One of the most important points of wsServer is its ease of use, so I'm happy that I've been able to keep up with the goal.

It bothers me a lot that other libraries require large chunks of code for a simple 'echo'.

Comment options

It bothers me a lot that other libraries require large chunks of code for a simple 'echo'.

I can't agree it any more ! Besides, your code is elegant and comments-friendly.🍭

Comment options

works great

You must be logged in to vote
1 reply
Comment options

Theldus Jan 14, 2022
Maintainer Author

Thanks =)

Comment options

Thanks for wsServer. I built and have tested with your example on an Intel Phi card. In example, how would you code as echo web socket server?

You must be logged in to vote
1 reply
Comment options

Theldus Mar 23, 2022
Maintainer Author

I built and have tested with your example on an Intel Phi card

An Intel Xeon Phi card? Wow, that's very unexpected, but also awesome 😊

In example, how would you code as echo web socket server?

Well, I believe the example file already does that for you... for each message that a client sends, the example will display it on the screen and also send it to all connected clients.

How does this differ from your use case?

Comment options

Sorry I missed it before. Thanks again for the code. From: Davidson Francis ***@***.*** Sent: Tuesday, March 22, 2022 8:38 PM To: Theldus/wsServer Cc: troglobytor; Comment Subject: Re: [Theldus/wsServer] Was wsServer helpful to you? tell us a little about it (Discussion #30) I built and have tested with your example on an Intel Phi card An Intel Xeon Phi card? Wow, that's very unexpected, but also awesome 😊 In example, how would you code as echo web socket server? Well, I believe the example file already does that for you... for each message that a client sends, the example will display it on the screen and also send it to all connected clients. How does this differ from your use case? — Reply to this email directly, view it on GitHub <#30 (reply in thread)> , or unsubscribe <https://github.com/notifications/unsubscribe-auth/AB3F2LQSFVYXMJNXAXKWRKTVBJYVZANCNFSM5CZ42UYQ> . You are receiving this because you commented. <https://github.com/notifications/beacon/AB3F2LVPTFWGEH5LAKVQ4MTVBJYVZA5CNFSM5CZ42UY2YY3PNVWWK3TUL52HS4DFWFCGS43DOVZXG2LPNZBW63LNMVXHJKTDN5WW2ZLOORPWSZGOAASOR5Y.gif> Message ID: ***@***.***>
You must be logged in to vote
0 replies
Comment options

Thanks for providing this tool, it is so simple and smart, and I like it :-)
Used it to build HTML/Javascript based UI for a small project.

I came across a challenge with the broadcast option in ws_sendframe_txt() which I like to describe here.
Sorry but I'm not familiar with GitHub and how to send a PR so I have to describe it this way.
Created my account right now, just to write this comment.

Besides broadcast = true, it needs to populate also a valid file descriptor value, otherwise the broadcast loop will not execute ..?

 ...
	output = SEND(fd, response, idx_response);
	if (output != -1 && broadcast)
	{
 ... broadcasting ...
 }
 ...

My current application is a UI for a power meter, and I simply want to send push updates to all connected clients.
So broadcast is quite perfect, and my only challenge was to get a valid file descriptor to reliably start broadcasting.
Did not see an API function to retrieve the connected clients etc. (and I didn't like to manage this redundant in my application by onopen() / onclose() tracking etc.).
btw.: I use ws_socket(&evs, 8088, 1); to start the server and my client update logic is in the code following this line ...

As a quick and dirty solution I created a new API function ( simply using get_client_index(int fd) as template).

int ws_get_first_client_fd(void) 
{ 
	int i;
	int fd = -1;
	pthread_mutex_lock(&mutex);
	for (i = 0; i < MAX_CLIENTS; i++)
		if ((fd = client_socks[i].client_sock) > 0)
			break;
	pthread_mutex_unlock(&mutex);
	return (fd);
}

This works for me, and I'm fine with that.

Suggestions:
Ideally enhance (fix?) ws_sendframe_txt() in a way that broadcasting works also with an invalid file descriptor (e.g. -1).
My first thought would be to simply ignore the file descriptor parameter when broadcast is selected and determine an appropriate one internally.

Best regards
Werner

You must be logged in to vote
9 replies
Comment options

Just a thought:
WSS servicing possibly multiple client collections, derived collections, overrides for how to handle base client functions.
Clients collection, clients, client_add, client_remove. clients_active, clients_with_attribute, clients_broadcast, ... Thread and/or process safe add/remove locking one client. Clients like a group and client like a user. Perhaps returning a list ignoring any lock failures if inconsequential.

Comment options

Theldus Apr 3, 2022
Maintainer Author

In my opinion the locking of the client list does not really solve the race condition behind.
Because any client connection may break even when/while the the list locked.
The lock just prevents an update of the list, isn't it?

Yes, you're completely right...

Assuming the ws_sendframe_txt(ws_client, "message"); in the list iteration loop fails
the clientlist requires an update to reflect new state?

If the client list is a copy of the original/internal, yes, if it's the same, no, not required.

a onclose() callback may occur?

Yes... an on_close() call can occur at any time. Unless you are inside an event, as each callback reflects a client thread, you will not receive a new callback (for that client) unless you return from the previous callback; but nothing prevents the server from calling callbacks from other clients... What I meant is that a thread inside an event/callback will not be interrupted by another event, since each event runs inside its own thread that corresponds to a client.

The side effect of this is that while inside an event for a given client, the server is unable to handle new messages and events from that client, until it returns from the current event. I think the main loop makes this clear.

Maybe I should make this clear in the docs: long processing or infinite loops inside an event is a big 'no no'. For this, the user can dispatch new threads or stack the new messages (for further processing) in some data structure.

In my mind, the provided client list should be intended to reflect the most recent known state, which means it can change at any time.
This helps the application programm to know if there were clients registered, and If a client is not marked as disconnected, it's worth to give communication a try (otherwise need to wait for an onopen() callback or recheck after a while).

I totally agree with you, which is why I'm scratching my head thinking of some viable way to do this. So you suggest sharing the internal list of clients directly, right? because it's the only way I think it's possible to maintain the current state of each client as things change.

Thus I feel you should not return from API back to application while any pending locks.

I think I know what you're saying, but I'm not sure... the server is always getting new connections, firing events and etc, so I'm not quite sure how to "return from API back to application while any pending locks".

What I can think of, is to protect each access to the client list (and also to a particular client) with a global lock (or not), so ws_sendframe* will not be able to send a message to a specific 'client_t', while that same client is being changed at the same time on the server.

Working with index makes iteration a snap ... for(int i=0; i < maxclients; i++){ ... }
One compromise approach may be to allocate a list with a fixed number of clients, but the number of list items is made selectable.
E.g. you allocate initially a list with size MAX_CLIENTS, and offer an new API function ws_realloc_client_list(int maxclients) which may be optionally used for advanced demands.

Of course, a linked list is the more sophisticated state of the art :-)
And iteration is also quite simple with while(ws_client_con_ptr->next != NULL){ ... }.
Benefit is that MAX_CLIENTS is no longer existent.

Maybe it's better (i.e., simpler) to keep a fixed list of clients: my initial thought of removing it was to avoid the loops that find the client's internal structure from fd (like the get_client_index() method does)... but by adopting a 'client_conn_t' this is solved.

A linked list makes it more complicated to share the current clients list since the list (and the ->next pointers) can be changed at any time.

About the 'MAX_CLIENTS' resizing, it's a good idea. Maybe I'll consider adding this in the future when these other issues are addressed.

I agree with your 'insecure feeling' when sharing the internal client list.
Can this mitigated a bit by declaring the pointed to items from outside API as const types?

One thing that came to my mind was using 'opaque types' (in ws.h), so the user is unable to see what's inside the structure and access it directly, which makes things like client[idx]->something impossible. Any useful access to client data I can provide via some API. So I think this sounds better to me than using const or something...

Or will this fool the C compiler optimizer in a way that the application does not always see the last recent update.
(Just thinking of volatile declared variables when they are modified by interrupt routines.)

Volatile and const together sounds contradictory to me... I'd rather hide implementation details than try to block/prevent access.


Hi @troglobytor, thanks for joining the discussion,

WSS servicing possibly multiple client collections, derived collections, overrides for how to handle base client functions.
Clients collection, clients, client_add, client_remove. clients_active, clients_with_attribute, clients_broadcast, ... Thread and/or process safe add/remove locking one client. Clients like a group and client like a user. Perhaps returning a list ignoring any lock failures if inconsequential.

I don't know if I understand exactly what you mean, but at the moment I just want to provide some easy way to iterate over the clients and maybe retrieve information about them through some routines in the API.

What you propose seems to be a bit out of scope for wsServer at the moment.

Comment options

Sorry, sometimes I get too excited. Does some attribute change while compiling the list and does it matter if it does change? For those that don't matter then almost any method would suffice, but if it does, then an immediate action rather than continue building the list would reduce but not completely eliminate a change of attribute action error.

Comment options

Before things are getting too complex and sophisticated ... I would like to come back to my initial challenge / use case.
This is already solved with the indend change of ws_sendframe_txt(ws_client_conn_t *client, const char *message) where broadcasting takes place with NULL as client parameter.
In my distinct use case, there is no longer a need for a client list (as I only like to broadcast, I do not care about the clients ...).
The request for a client list was brought up by me, because a valid file descriptor is required by current version.

Now I ask myself about any other use cases where a client list may be helpful ...

  • Iterating over the list and talk to all clients - better done with a broadcast
  • Counting the number of connected clients - easy done with incrementing/decrementing a counter by onopen() / onclose()
  • Special cases where I need to maintain individual application level states per client - need anyway application code triggered by onXXX() callbacks and probably a dedicated client list managed by the application itself.

I cannot immediately sketch a use case for a generic client list, which is worth the efforts and considerations around (but there may be several ones).
So I have to withdraw my feature request :-)

@Theldus I guess you have already some use case in mind ... ?

If it's hard to find a use case, you may like to stick to your slogan (design goal ?) "Why to complicate if things can be simple?"

One thing that came to my mind was using 'opaque types' (in ws.h), so the user is unable to see what's inside the structure and access it directly, which makes things like client[idx]->something impossible. Any useful access to client data I can provide via some API. So I think this sounds better to me than using const or something...

Yes the client pointer may be seen as just some abstract pointer to a client (without any further info behind), just an API reference?
Then you are free on what you like to return with some ws_client_info(client *) function.
I could even imagine that the client pointer must not be used in any other way, than as a parameter to certain API functions. This gives API an option to check whether the given pointer is valid etc.
If the application tries to access pointers in already free()'d allocated memory, strange things may occur which are hard to find ...

Or will this fool the C compiler optimizer in a way that the application does not always see the last recent update.
(Just thinking of volatile declared variables when they are modified by interrupt routines.)

Volatile and const together sounds contradictory to me... I'd rather hide implementation details than try to block/prevent access.

You are right, that was also my concern ...

I would like to emphasize again that I like wsServer most because its simple to use, and I was really surprised how easy it was to implement websocket communication in my small project.

Comment options

Theldus Apr 4, 2022
Maintainer Author

Sorry, sometimes I get too excited. Does some attribute change while compiling the list and does it matter if it does change? For those that don't matter then almost any method would suffice, but if it does, then an immediate action rather than continue building the list would reduce but not completely eliminate a change of attribute action error.

@troglobytor No worries =). Do you say the attributes of the client itself? they may change if I create/allocate a new list, but they remain fixed (or better said, updated) if I share the internal list of clients.


This is already solved with the indend change of ws_sendframe_txt(ws_client_conn_t *client, const char *message) where broadcasting takes place with NULL as client parameter.
In my distinct use case, there is no longer a need for a client list (as I only like to broadcast, I do not care about the clients ...).

@tinkering4fun Yes... you're right... I thought a client list was needed because this was already asked in issue #34 (although a broadcast without knowing a user beforehand would also solve the issue back then).

Now I ask myself about any other use cases where a client list may be helpful ...

Iterating over the list and talk to all clients - better done with a broadcast

Yes...

Counting the number of connected clients - easy done with incrementing/decrementing a counter by onopen() / onclose()

Also yes, although it requires some care for the increment/decrement to be atomic (multiples on_open()/on_close() can occur simultaneously).

Special cases where I need to maintain individual application level states per client - need anyway application code triggered by onXXX() callbacks and probably a dedicated client list managed by the application itself.

Also yes, like a websocket chat room I just saw here. I also need to reinforce the care that the addition/removal of clients in the list must be atomic as well.

@Theldus I guess you have already some use case in mind ... ?
If it's hard to find a use case, you may like to stick to your slogan (design goal ?) "Why to complicate if things can be simple?"

I don't exactly have an example at hand. I believe that most cases can be managed by the user himself, by maintaining a list of clients through on_open()/on_close() events.

And I think you've convinced me... most cases can be worked outside the library.

I think I just need to provide other example files addressing these cases so that the user experience is smoother with the library. I'm also planning to write a friendly markdown of documentation, explaining some use cases and so on.

I would like to emphasize again that I like wsServer most because its simple to use, and I was really surprised how easy it was to implement websocket communication in my small project.

Thank you. A few years ago when I needed a C library to write a small WebSocket server, all the ones I found seemed very complicated, with a lot of code for very little.

C doesn't have to be complicated, and that's what motivated me to write this library.


@tinkering4fun and @troglobytor huge thanks for the feedback, it helped me a lot to clarify things and direct me where I should work next in wsServer.

I will start working on the points discussed here:

  • 'ws_client_t' instead of fd
  • ws_sendframe* broadcast field will be removed

and in the future:

  • markdown docs, friendly explaining how to use wsServer and etc
  • more example files

and I'll let you know as soon as they're on the master. Again, thank you very much =).

Comment options

Just wanted to chime in and say:

  1. Thank you for your work on wsServer
  2. My use-case seems very similar to that of @tinkering4fun - I want to have the server regularly push data to connected clients

And since you were interested, here is a bit more detail: I'm working on a minimalist set of tools that will enable the monitoring of multiple machines in a simple dashboard. For that, I want to have one WebSocket server running on each machine, which accepts a connection from the machine running the dashboard. I would then push updates to the dashboard whenever any of the vitals have changed. The fact that wsServer is written in C, its simplicity, easy setup and the lack of dependencies is what makes it a great fit for me, as some of those machines have very low specs and I can't afford to install much on them.

You must be logged in to vote
1 reply
Comment options

Theldus Apr 22, 2022
Maintainer Author

Hi @domsson,

Thank you for your work on wsServer

You're welcome =)

My use-case seems very similar to that of @tinkering4fun - I want to have the server regularly push data to connected clients

And since you were interested, here is a bit more detail: I'm working on a minimalist set of tools that will enable the monitoring of multiple machines in a simple dashboard. For that, I want to have one WebSocket server running on each machine, which accepts a connection from the machine running the dashboard. I would then push updates to the dashboard whenever any of the vitals have changed. The fact that wsServer is written in C, its simplicity, easy setup and the lack of dependencies is what makes it a great fit for me, as some of those machines have very low specs and I can't afford to install much on them.

Looks like an interesting use case. I'm always curious about what people use this project for, so I'm glad to see it being used in the wild like this. @tinkering4fun's issue is now resolved, so I hope you don't run into the same problems as him.

Don't hesitate to create an issue or discussion if you have any questions about wsServer =).

Comment options

Hi

We are getting connect() API failed issue with toyws web client and throwing "Unable to connect!" message.

We used following steps to setup wsServer

  • built wsServer using GCC and run echo server on Linux machine.
  • toyws client built using emsdk and converted to JS - "emcc tws_test.c toyws.c -o test.js" , nodejs used to run client

Please suggest me what is worng

You must be logged in to vote
1 reply
Comment options

Theldus Aug 16, 2022
Maintainer Author

Hi @NathanMani, thanks for giving wsServer a try,

built wsServer using GCC and run echo server on Linux machine.

ok

toyws client built using emsdk and converted to JS - "emcc tws_test.c toyws.c -o test.js" , nodejs used to run client

Why are you doing this? I mean, you can compile and use toyws by simply replacing 'emcc' with gcc/clang/etc like:

$ gcc tws_test.c toyws.c -o test
$ ./test

I believe you basically have three options:

  • a) Since you are using Node, you certainly already have native support or there is some famous WebSocket client library (like this one) for Node.js, use it.

  • b) If you want to use ToyWS as a Node.js library, you need to compile it as a Node.js module and then import it. Since toyws.c is quite simple this shouldn't be that complicated, but I still recommend the first option. Compiling a C code for WebAssembly and using it directly in Node as a library sounds like the wrong way to do it.

  • c) Do not use Node at all: since ToyWS can work standalone, I can't see the applicability of using it together with Node.

Comment options

You have no idea how long i've been looking for a code like this 😀.
thanks very very very much

You must be logged in to vote
1 reply
Comment options

Theldus Oct 15, 2022
Maintainer Author

Thank you for your kind words =).

Comment options

Amazing and well written code.
Just what I was looking for to understand WebSocket and start using it.
Now days is quite impossible find library that doesn't depends from libraries, that depends from libraries, ...

You must be logged in to vote
1 reply
Comment options

Theldus Jun 2, 2023
Maintainer Author

Hi @pb-now,
Thanks.

Now days is quite impossible find library that doesn't depends from libraries, that depends from libraries, ...

I know that feeling, and so I've put a lot of effort into making wsServer simple enough to work out-of-the-box in a variety of environments, no extra deps or anything, just build it and start using it =).

Unfortunately it's not perfect, and some things annoys me, like: use of threads (some systems don't have threads, like Minix), (ab)use of mutexes I don't like either, code a little messy in some places... but I plan to address that some day...

Comment options

My project is making an automated system that has status and control messages sent thru websocket to a webpage. I have a semi working prototype using python that is having issues when a client disconnects. I haven't done command line C in decades

I am working on a Le Potato AML-S905X-CC with Ubuntu 22.04.1 Jammy LTS. Running headless thru a putty connection. I followed the instructions CMake instructions here, all went well, no errors or complaints. The echo is available as advertised and executable. Execution returns to the ubuntu prompt in about a second. No error message a sub optimal operation.

The only clue I have is the system log contains the following:
Jan 10 09:56:07 potato wpa_supplicant[370]: wlx3c3300306567: CTRL-EVENT-BEACON-LOSS

immediately after posting this I saw the echo.html file. Attempting to use this results with the following message: Connection closed: wasClean: false, evCode: 1006. Of coarse I don't think echo is running.

You must be logged in to vote
1 reply
Comment options

Theldus Jan 16, 2024
Maintainer Author

Hi @cyberbilly,
Thanks for giving wsServer a try,

Execution returns to the ubuntu prompt in about a second. No error message a sub optimal operation.

When running the echo example, it should keep running indefinitely... can you see the return value? like:

$ ./examples/echo/echo
$ echo $?
0

If you are having a segfault or something like that, you can try to enable core dump and view it with GDB to inspect the stack trace, and etc...

The only clue I have is the system log contains the following:
Jan 10 09:56:07 potato wpa_supplicant[370]: wlx3c3300306567: CTRL-EVENT-BEACON-LOSS

This doesn't seem related to wsServer, but rather to your WiFi, from a quick Google search: [Solved] Unstable Wifi connection

Comment options

echo $? yields 0. There are no errors that I can find, it just does nothing... I think the wifi loss is caused by doing a power reset to the board, Wifi stability is not a problem. My python websocket server runs fine.

I followed the core dump link provided, I didn't find any core files.

You must be logged in to vote
1 reply
Comment options

Theldus Jan 18, 2024
Maintainer Author

Hi @cyberbilly,

I really don't know what's going on... it wasn't supposed to return immediately, and even if it did, it should have at least printed some error message.

I need more information about the execution flow to have any idea, please apply this patch and build wsServer as follows:

$ git clone https://github.com/Theldus/wsServer
$ cd wsServer/
$ wget https://gist.githubusercontent.com/Theldus/8fe6827ff6db21d331da72384da42334/raw/f71cec777c0ecaab2ce64879aa6d3eed4f891d4d/backtrace.diff
$ patch -p1 < backtrace.diff 
patching file src/ws.c
$ mkdir build
$ cd build
$ CFLAGS="-finstrument-functions" LDFLAGS="-rdynamic" cmake .. -DCMAKE_BUILD_TYPE=Debug
$ make

and then run the example echo file, which should then display all invoked functions:

$ ./examples/echo/echo 
 >./examples/echo/echo(main+0) [0x4024ec]
 >./examples/echo/echo(ws_socket+0) [0x404f24]
 >./examples/echo/echo(do_bind_socket+0) [0x404d70]
 <./examples/echo/echo(do_bind_socket+0) [0x404d70]
Waiting for incoming connections...
 >./examples/echo/echo(ws_accept+0) [0x404945]

I'm assuming that your environment supports GCC/Clang and uses glibc as well, otherwise the above procedure will not work.


Please let me know if you were able to apply and build wsServer with this patch and your output, I'm very curious about it...

Comment options

Hi @Theldus ,
First of all, thank you for creating this project! Even a beginner like me can finally experiment with a WebSocket server!

May I ask for some advice? I’d like to create a server that connects to a DSP processor to read its status and send commands to it. At the same time, I want the server to accept connections from different types of clients. Some of these clients should receive specific updates about the processor’s status, while others should not.

I was thinking of using thread_loop=1 and after connect and interact with the processor. Then, I thought about creating a structure for the clients where I can define who they are and what types of messages they want to receive from the processor (I was considering using cJSON to simplify the client-server data exchange). When I receive a status update from the processor, I plan to loop through the connected clients and send the update only to those who need it.

What do you think? Is there perhaps a smarter way to achieve this?

Thank you so much! Cheers!
Guido

You must be logged in to vote
1 reply
Comment options

Theldus Jan 18, 2025
Maintainer Author

Hi @keygee,
Thanks for giving wsServer a try.
I think your approach is good, it makes sense to me, from what I understand.

I wouldn't worry too much about having the best approach right from the start, but about being correct: if you can receive these status updates and send them correctly to the clients, the rest you can worry about later ;-).

Comment options

Yes. Useful. Working on porting to JavaScript for use as a WebSocket server in the browser using WICG Direct Sockets. I have some code now that doesn't read past 65536 because continuation frame parsing was not implemented in the code I began with. This implementation works. Just echoed 7 MB.

You must be logged in to vote
0 replies
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

AltStyle によって変換されたページ (->オリジナル) /